diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a884998..6b846ad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort args: [ "--profile", "black", "--filter-files" ] - repo: https://github.com/psf/black - rev: 22.6.0 # Replace by any tag/version: https://github.com/psf/black/tags + rev: 23.3.0 # Replace by any tag/version: https://github.com/psf/black/tags hooks: - id: black language_version: python3 # Should be a command that runs python3.6.2+ diff --git a/README.md b/README.md index 5eb2e11..1958f84 100644 --- a/README.md +++ b/README.md @@ -146,17 +146,6 @@ def attachment_context_translations(): } ``` -### Auto-formatter setup -We use isort (https://github.com/pycqa/isort) and black (https://github.com/psf/black) for local auto-formatting and for linting in the CI pipeline. -The pre-commit framework (https://pre-commit.com) provides GIT hooks for these tools, so they are automatically applied before every commit. - -Steps to activate: -* Install the pre-commit framework: `pip install pre-commit` (for alternative installation options see https://pre-commit.com/#install) -* Activate the framework (from the root directory of the repository): `pre-commit install` - -Hint: You can also run the formatters manually at any time with the following command: `pre-commit run --all-files` - - ## Usage Attachments accept any other Model as content_object and store the uploaded files in their respective directories @@ -273,6 +262,25 @@ To manage file uploads for any existing model you must create a one-to-many "att router.register(r"attachment", AttachmentViewSet) ``` +## Storage settings +Change the directory where attachments will be stored by setting the `storage_location` in `AttachmentMeta` within the model class: + ```python + from django.conf import settings + + class AttachmentMeta: + storage_location = 'path/to/another/directory' # default is settings.PRIVATE_ROOT + ``` + +## Auto-formatter setup +We use isort (https://github.com/pycqa/isort) and black (https://github.com/psf/black) for local auto-formatting and for linting in the CI pipeline. +The pre-commit framework (https://pre-commit.com) provides GIT hooks for these tools, so they are automatically applied before every commit. + +Steps to activate: +* Install the pre-commit framework: `pip install pre-commit` (for alternative installation options see https://pre-commit.com/#install) +* Activate the framework (from the root directory of the repository): `pre-commit install` + +Hint: You can also run the formatters manually at any time with the following command: `pre-commit run --all-files` + ## Download endpoint By default, a generic download endpoint is provided to authenticated users, e.g. `http://0.0.0.0:8000/api/attachment/5b948d37-dcfb-4e54-998c-5add35701c53/download/`. diff --git a/drf_attachments/migrations/0003_alter_attachment_file.py b/drf_attachments/migrations/0003_alter_attachment_file.py new file mode 100644 index 0000000..9f20a3b --- /dev/null +++ b/drf_attachments/migrations/0003_alter_attachment_file.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.16 on 2023-01-19 12:17 + +from django.db import migrations +import drf_attachments.models.fields +import drf_attachments.storage + + +class Migration(migrations.Migration): + + dependencies = [ + ('drf_attachments', '0002_attachment_object_id_types'), + ] + + operations = [ + migrations.AlterField( + model_name='attachment', + name='file', + field=drf_attachments.models.fields.DynamicStorageFileField(storage=drf_attachments.storage.AttachmentFileStorage(), upload_to=drf_attachments.storage.attachment_upload_path, verbose_name='file'), + ), + ] diff --git a/drf_attachments/models/fields.py b/drf_attachments/models/fields.py index 03078cb..8a5b503 100644 --- a/drf_attachments/models/fields.py +++ b/drf_attachments/models/fields.py @@ -1,7 +1,10 @@ +from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation +from django.db.models import FileField __all__ = [ "AttachmentRelation", + "DynamicStorageFileField", ] @@ -10,3 +13,11 @@ class AttachmentRelation(GenericRelation): def __init__(self, *args, **kwargs): super().__init__("drf_attachments.attachment", *args, **kwargs) + + +class DynamicStorageFileField(FileField): + def pre_save(self, model_instance, add): + meta = getattr(model_instance.content_object, "AttachmentMeta", None) + storage_location = getattr(meta, "storage_location", settings.PRIVATE_ROOT) + self.storage.location = storage_location + return super().pre_save(model_instance, add) diff --git a/drf_attachments/models/models.py b/drf_attachments/models/models.py index db84cc1..1b0cefe 100644 --- a/drf_attachments/models/models.py +++ b/drf_attachments/models/models.py @@ -19,6 +19,7 @@ from rest_framework.reverse import reverse from drf_attachments.config import config +from drf_attachments.models.fields import DynamicStorageFileField from drf_attachments.models.managers import AttachmentManager from drf_attachments.storage import AttachmentFileStorage, attachment_upload_path from drf_attachments.utils import get_extension, get_mime_type, remove_file @@ -65,7 +66,7 @@ class Attachment(Model): null=False, ) - file = FileField( + file = DynamicStorageFileField( verbose_name=_("file"), upload_to=attachment_upload_path, # DO NOT CHANGE UPLOAD METHOD NAME (keep migrations sane) storage=AttachmentFileStorage(), diff --git a/drf_attachments/rest/views.py b/drf_attachments/rest/views.py index d9933f6..57e8526 100644 --- a/drf_attachments/rest/views.py +++ b/drf_attachments/rest/views.py @@ -37,6 +37,15 @@ def get_serializer(self, *args, **kwargs): def get_queryset(self): return Attachment.objects.viewable() + def get_storage_path(self): + attachment = self.get_object() + meta = getattr(attachment.content_object, "AttachmentMeta", None) + storage_location = getattr(meta, "storage_location", None) + if storage_location: + return f"{storage_location}/{attachment.file.name}" + else: + return attachment.file.path + @action( detail=True, methods=["GET"], @@ -46,6 +55,7 @@ def download(self, request, format=None, *args, **kwargs): """Downloads the uploaded attachment file.""" attachment = self.get_object() extension = attachment.get_extension() + storage_path = self.get_storage_path() if attachment.name: download_file_name = f"{attachment.name}{extension}" @@ -53,7 +63,7 @@ def download(self, request, format=None, *args, **kwargs): download_file_name = f"attachment_{attachment.pk}{extension}" return FileResponse( - open(attachment.file.path, "rb"), + open(storage_path, "rb"), as_attachment=True, filename=download_file_name, )