Skip to content

Commit

Permalink
SIWKKCB-89: DRF support optional
Browse files Browse the repository at this point in the history
  • Loading branch information
anx-abruckner committed Oct 11, 2024
1 parent ff18bb5 commit ad9dce1
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Install dependencies and package
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-test.txt
pip install django~=${{ matrix.django-version }}.0
pip install djangorestframework~=${{ matrix.djangorestframework-version }}.0
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ If used with DRF, `django-filter` is an additional requirement.

1. Install using pip:

```shell
pip install drf-attachments[drf]
```

or to install without DRF dependencies (no REST endpoints available from scratch)

```shell
pip install drf-attachments
```
Expand Down Expand Up @@ -140,6 +146,12 @@ def attachment_context_translations():
}
```

4. Optionally define a custom DIR as root for your attachments ("attachments" by default)
```python
# settings.py
ATTACHMENT_UPLOAD_ROOT_DIR = "your/custom/attachments/root/"
```

## Usage

Attachments accept any other Model as content_object and store the uploaded files in their respective directories
Expand Down
27 changes: 16 additions & 11 deletions drf_attachments/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
UUIDField,
)
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
from django.core.exceptions import ValidationError

from drf_attachments.config import config
from drf_attachments.models.fields import DynamicStorageFileField
Expand Down Expand Up @@ -99,6 +99,16 @@ class Meta:
verbose_name_plural = _("attachments")
ordering = ("creation_date",)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
try:
# if DRF is installed, use their ValidationError
from rest_framework.exceptions import ValidationError as DrfValidationError
self.validation_error_class = DrfValidationError
except ImportError:
# otherwise use default django ValidationError
self.validation_error_class = ValidationError

def __str__(self):
return f"{self.content_type} | {self.object_id} | {self.context_label} | {self.name}"

Expand Down Expand Up @@ -184,12 +194,7 @@ def validate_context(self):
context=self.context,
valid_contexts=", ".join(self.valid_contexts),
)
raise ValidationError(
{
"context": error_msg,
},
code="invalid",
)
raise self.validation_error_class(error_msg, code="invalid")

def validate_file(self):
"""
Expand All @@ -214,7 +219,7 @@ def _validate_file_mime_type(self):
mime_type=self.meta["mime_type"],
valid_mime_types=", ".join(self.valid_mime_types),
)
raise ValidationError(
raise self.validation_error_class(
{
"file": error_msg,
},
Expand All @@ -236,7 +241,7 @@ def _validate_file_extension(self):
extension=self.meta["extension"],
valid_extensions=", ".join(self.valid_extensions),
)
raise ValidationError(
raise self.validation_error_class(
{
"file": error_msg,
},
Expand All @@ -257,7 +262,7 @@ def _validate_file_size(self):
size=self.file.size,
min_size=self.min_size,
)
raise ValidationError(
raise self.validation_error_class(
{
"file": error_msg,
},
Expand All @@ -272,7 +277,7 @@ def _validate_file_size(self):
size=self.file.size,
max_size=self.max_size,
)
raise ValidationError(
raise self.validation_error_class(
{
"file": error_msg,
},
Expand Down
9 changes: 8 additions & 1 deletion drf_attachments/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ def url(self, name):
return get_admin_attachment_url(attachment.pk)


def attachment_upload_root_dir():
"""
Extract ATTACHMENT_UPLOAD_ROOT_DIR from the settings (if defined)
"""
return getattr(settings, "ATTACHMENT_UPLOAD_ROOT_DIR", "attachments")


def attachment_upload_path(attachment, filename):
"""
If not defined otherwise, a content_object's attachment files will be uploaded as
Expand All @@ -47,4 +54,4 @@ def attachment_upload_path(attachment, filename):
"""
filename, file_extension = os.path.splitext(filename)
month_directory = timezone.now().strftime("%Y%m")
return f"attachments/{month_directory}/{str(uuid1())}{file_extension}"
return f"{attachment_upload_root_dir()}/{month_directory}/{str(uuid1())}{file_extension}"
4 changes: 4 additions & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# load base requirements
-r requirements.txt

rest-framework-generic-relations>=2.0.0
2 changes: 0 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ black>=22.6.0,<22.7

# TestApp dependencies
django>=3.2,<4
djangorestframework>=3.13,<4
python-magic>=0.4.18
rest-framework-generic-relations>=2.0.0
django-filter>=21.1,<22

# fix importlib version to avoid "AttributeError: 'EntryPoints' object has no attribute 'get'" with flake8
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
author_email="[email protected]",
install_requires=[
"python-magic>=0.4.18",
"rest-framework-generic-relations>=2.0.0",
"content-disposition>=1.1.0",
],
classifiers=[
Expand All @@ -39,4 +38,7 @@
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
extras_require={
"drf": ["rest-framework-generic-relations>=2.0.0"],
},
)
15 changes: 8 additions & 7 deletions tests/testapp/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,19 @@ def test_download(self):

# get attachment from main serializer
response = self.client.get(path=f"/api/attachment/")
attachment_response = response.json()[0]
# TODO: download_url is not provided by main serializer. intentional?
main_attachment_response = response.json()[0]
main_download_url = main_attachment_response["download_url"]
self.assertIsNotNone(main_download_url)
self.assertGreater(len(main_download_url), 0)

# get attachment from sub-serializer
response = self.client.get(path=f"/api/photo_album/{self.photo_album.pk}/")
attachment_response = response.json()["attachments"][0]
download_url = attachment_response["download_url"]
self.assertIsNotNone(download_url)
self.assertGreater(len(download_url), 0)
sub_attachment_response = response.json()["attachments"][0]
sub_download_url = sub_attachment_response["download_url"]
self.assertEqual(main_download_url, sub_download_url)

# download
response = self.client.get(download_url)
response = self.client.get(sub_download_url)

# check response
self.assertEqual(
Expand Down

0 comments on commit ad9dce1

Please sign in to comment.