Skip to content

Commit

Permalink
Merge pull request #7 from anexia/SIANXKE-307-dynamic-file-storage
Browse files Browse the repository at this point in the history
Add dynamic storage file field
  • Loading branch information
nezhar authored Mar 31, 2023
2 parents 8b1bcca + ed04825 commit d1e070c
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 15 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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+
30 changes: 19 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/`.
Expand Down
20 changes: 20 additions & 0 deletions drf_attachments/migrations/0003_alter_attachment_file.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
11 changes: 11 additions & 0 deletions drf_attachments/models/fields.py
Original file line number Diff line number Diff line change
@@ -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",
]


Expand All @@ -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)
3 changes: 2 additions & 1 deletion drf_attachments/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down
12 changes: 11 additions & 1 deletion drf_attachments/rest/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand All @@ -46,14 +55,15 @@ 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}"
else:
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,
)

0 comments on commit d1e070c

Please sign in to comment.