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 Nov 24, 2022
1 parent 8b1bcca commit 790c776
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 33 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ If used with DRF, `django-filter` is an additional requirement.
1. Install using pip:

```shell
pip install git+https://github.com/anexia/drf-attachments@main
pip install git+https://github.com/anexia/drf-attachments@main[drf]
```

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

```
pip install git+https://github.com/anexia-it/drf-attachments@main
```

2. Integrate `drf_attachments` and `django_userforeignkey` into your `settings.py`
Expand Down Expand Up @@ -69,6 +75,12 @@ class PhotoAlbumAdmin(admin.ModelAdmin):
]
```

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/"
```

`ReadOnlyAttachmentInlineAdmin` is useful when attachments should be provided only by REST API. You may consider
extending the classes in order to handle additional permission checks.

Expand Down
21 changes: 1 addition & 20 deletions drf_attachments/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
UUIDField,
)
from django.utils.translation import gettext_lazy as _
from django_userforeignkey.request import get_current_request
from rest_framework.exceptions import ValidationError
from rest_framework.reverse import reverse

from drf_attachments.config import config
from drf_attachments.models.managers import AttachmentManager
Expand Down Expand Up @@ -112,18 +110,6 @@ def is_image(self):
and self.meta["mime_type"].startswith("image")
)

@property
def download_url(self):
# Note: The attachment-download URL is auto-generated by the AttachmentViewSet
relative_url = reverse("attachment-download", kwargs={"pk": self.id})

# Make sure NOT to throw an exception in any case, otherwise the serializer will not provide the property
request = get_current_request()
if not request:
return relative_url

return request.build_absolute_uri(relative_url)

@property
def default_context(self):
return config.default_context()
Expand Down Expand Up @@ -202,12 +188,7 @@ def validate_context(self):
context=self.context,
valid_contexts=", ".join(self.valid_contexts),
)
raise ValidationError(
{
"context": error_msg,
},
code="invalid",
)
raise ValidationError(error_msg, code="invalid")

def validate_file(self):
"""
Expand Down
7 changes: 7 additions & 0 deletions drf_attachments/models/querysets.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ def delete(self):

return result

def get_names_list(self):
"""
Return the names of all files within the queryset as list
"""
names = list(self.values_list("name", flat=True))
return ", ".join(names)

def __filter_by_callable(self, callable_) -> QuerySet:
if callable_:
return callable_(self)
Expand Down
17 changes: 15 additions & 2 deletions drf_attachments/rest/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django_userforeignkey.request import get_current_request
from rest_framework import serializers
from rest_framework.fields import ChoiceField, FileField, ReadOnlyField
from rest_framework.fields import ChoiceField, FileField, ReadOnlyField, SerializerMethodField
from rest_framework.reverse import reverse

from drf_attachments.config import config
from drf_attachments.models.models import Attachment
Expand All @@ -16,6 +18,7 @@ class AttachmentSerializer(serializers.ModelSerializer):
to their own respective serializers.
"""

download_url = SerializerMethodField(read_only=True)
file = FileField(write_only=True, required=True)
content_object = config.get_content_object_field()
context = ChoiceField(choices=config.context_choices(values_list=False))
Expand All @@ -33,12 +36,17 @@ class Meta:
"file",
)

def get_download_url(self, obj):
request = get_current_request()
relative_url = reverse("attachment-download", kwargs={"pk": obj.id})
return request.build_absolute_uri(relative_url)


class AttachmentSubSerializer(serializers.ModelSerializer):
"""Sub serializer for nested data inside other serializers"""

# pk is read-only by default
download_url = ReadOnlyField()
download_url = SerializerMethodField(read_only=True)
name = ReadOnlyField()
context = ChoiceField(
choices=config.context_choices(include_default=False, values_list=False),
Expand All @@ -53,3 +61,8 @@ class Meta:
"name",
"context",
)

def get_download_url(self, obj):
request = get_current_request()
relative_url = reverse("attachment-download", kwargs={"pk": obj.id})
return request.build_absolute_uri(relative_url)
12 changes: 10 additions & 2 deletions drf_attachments/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from django.utils import timezone
from rest_framework.reverse import reverse

__all__ = [
"AttachmentFileStorage",
Expand All @@ -27,7 +28,14 @@ def url(self, name):
if not attachment:
return ""

return attachment.download_url
return reverse("attachment-download", kwargs={"pk": attachment.id})


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):
Expand All @@ -44,4 +52,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: 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",
"django-userforeignkey>=0.4",
],
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 790c776

Please sign in to comment.