Skip to content

Commit

Permalink
Remove dependency django_userforeignkey, dedicated admin download att…
Browse files Browse the repository at this point in the history
…achment view
  • Loading branch information
nezhar committed Oct 20, 2023
1 parent 3f8dd00 commit b78151b
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 31 deletions.
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,14 @@ If used with DRF, `django-filter` is an additional requirement.
pip install git+https://github.com/anexia/drf-attachments@main
```

2. Integrate `drf_attachments` and `django_userforeignkey` into your `settings.py`
2. Integrate `drf_attachments` into your `settings.py`

```python
INSTALLED_APPS = [
# ...
'django_userforeignkey',
'drf_attachments',
# ...
]

MIDDLEWARE = [
# ...
'django_userforeignkey.middleware.UserForeignKeyMiddleware',
]
```

3. Configure attachment settings
Expand Down
29 changes: 28 additions & 1 deletion drf_attachments/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from content_disposition import rfc5987_content_disposition
from django.contrib import admin
from django.contrib.admin import AdminSite
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.contenttypes.admin import GenericTabularInline
from django.forms import ChoiceField, ModelForm
from django.urls import NoReverseMatch, reverse
from django.http import StreamingHttpResponse
from django.urls import NoReverseMatch, reverse, path
from django.utils.safestring import mark_safe

from drf_attachments.config import config
Expand Down Expand Up @@ -71,6 +75,29 @@ def content_object(obj):
except NoReverseMatch:
return entity

def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
"<path:object_id>/download/",
self.admin_site.admin_view(self.download_view),
name="drf_attachments_attachment_download"
),
]
return custom_urls + urls

def download_view(self, request, object_id):
attachment = Attachment.objects.get(pk=object_id)
response = StreamingHttpResponse(
attachment.file,
content_type=attachment.get_mime_type(),
)
response["Content-Disposition"] = rfc5987_content_disposition(
(attachment.name if attachment.name else str(attachment.pk)) + attachment.get_extension()
)

return response


class BaseAttachmentInlineAdmin(GenericTabularInline, AttachmentAdminMixin):
model = Attachment
Expand Down
15 changes: 0 additions & 15 deletions drf_attachments/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@
CASCADE,
CharField,
DateTimeField,
FileField,
ForeignKey,
JSONField,
Model,
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.fields import DynamicStorageFileField
Expand Down Expand Up @@ -113,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
21 changes: 21 additions & 0 deletions drf_attachments/rest/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.urls import reverse
from rest_framework import serializers

__all__ = [
"DownloadURLField",
]


class DownloadURLField(serializers.Field):
def __init__(self, *args, **kwargs):
super().__init__(read_only=True, *args, **kwargs)
def get_attribute(self, instance):
request = self.context.get('request')
relative_url = reverse("attachment-download", kwargs={"pk": instance.pk})

if request is None:
return relative_url
return request.build_absolute_uri(relative_url)

def to_representation(self, value):
return value
6 changes: 4 additions & 2 deletions drf_attachments/rest/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from drf_attachments.config import config
from drf_attachments.models.models import Attachment
from drf_attachments.rest.fields import DownloadURLField


__all__ = [
"AttachmentSerializer",
Expand All @@ -19,6 +21,7 @@ class AttachmentSerializer(serializers.ModelSerializer):
file = FileField(write_only=True, required=True)
content_object = config.get_content_object_field()
context = ChoiceField(choices=config.context_choices(values_list=False))
download_url = DownloadURLField()

class Meta:
model = Attachment
Expand All @@ -33,12 +36,11 @@ class Meta:
"file",
)


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

# pk is read-only by default
download_url = ReadOnlyField()
download_url = DownloadURLField()
name = ReadOnlyField()
context = ChoiceField(
choices=config.context_choices(include_default=False, values_list=False),
Expand Down
4 changes: 3 additions & 1 deletion drf_attachments/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"attachment_upload_path",
]

from drf_attachments.utils import get_admin_attachment_url


class AttachmentFileStorage(FileSystemStorage):
"""
Expand All @@ -27,7 +29,7 @@ def url(self, name):
if not attachment:
return ""

return attachment.download_url
return get_admin_attachment_url(attachment.pk)


def attachment_upload_path(attachment, filename):
Expand Down
13 changes: 13 additions & 0 deletions drf_attachments/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os

import magic
from django.urls import reverse


def get_mime_type(file):
Expand Down Expand Up @@ -30,3 +31,15 @@ def remove_file(file_path, raise_exceptions=False):
# forward the thrown exception
raise
# just continue if deletion of old file was not possible and no exceptions should be raised


def get_api_attachment_url(attachment_pk):
return reverse("attachment-download", kwargs={"pk": attachment_pk})

def get_admin_attachment_url(attachment_pk):
return reverse(
"admin:drf_attachments_attachment_download",
kwargs={
"object_id": attachment_pk
}
)
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ django>=3.2,<4
djangorestframework>=3.13,<4
python-magic>=0.4.18
rest-framework-generic-relations>=2.0.0
django-userforeignkey>=0.4
django-filter>=21.1,<22

# fix importlib version to avoid "AttributeError: 'EntryPoints' object has no attribute 'get'" with flake8
importlib-metadata<5.0
importlib-metadata<5.0
content-disposition
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
install_requires=[
"python-magic>=0.4.18",
"rest-framework-generic-relations>=2.0.0",
"django-userforeignkey>=0.4",
],
classifiers=[
"Development Status :: 5 - Production/Stable",
Expand Down
2 changes: 0 additions & 2 deletions tests/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"django_userforeignkey",
"drf_attachments",
"testapp",
]
Expand All @@ -39,7 +38,6 @@
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django_userforeignkey.middleware.UserForeignKeyMiddleware",
]

ROOT_URLCONF = "core.urls"
Expand Down

0 comments on commit b78151b

Please sign in to comment.