A first look at Django – custom attachment storage model

Foreword

Django comes with a field named FileField for processing file uploads. However, sometimes we need more control, such as defining the file’s storage path, file name, and file type. In this article, we will explore how to customize the Django attachment storage model.

Create an attachment application

python manage.py startapp attachment

Then, in the project’s settings.py file, register the app into the INSTALLED_APPS list, as follows:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'drf_yasg2',
    'django_filters',
    'account.apps.AccountConfig',
    'oauth',
    'attachment'
]

Create model

DefinitionAttachment

from django.db import models

# Create your models here.
from rest_framework.reverse import reverse

from CodeVoyager.mixins import BaseModelMixin
import uuid


class BlobField(models.Field):
    description = 'Blob'

    def db_type(self, connection):
        return 'mediumblob'


class Attachment(BaseModelMixin):
    file_id = models.UUIDField(auto_created=True, default=uuid.uuid4, editable=False)
    file_name = models.CharField('filename', max_length=200, unique=True)
    mime_type = models.CharField('MIME type', max_length=100)
    file_size = models.PositiveIntegerField('File length')
    blob = BlobField('File content')

    class Meta:
        verbose_name = 'attachment'
        verbose_name_plural = verbose_name

    def get_url(self, request):
        return reverse('attachment:download', request=request, kwargs={<!-- -->'attachment_id': self.file_id})

</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">
Field name Type Purpose
file_id UUIDField The unique identifier of the stored file
file_name CharField The name of the stored file, that is, the original file name
mime_type CharField MIME type of stored file
file_size PositiveIntegerField The size of the stored file (in bytes)
blob Custom BlobField The binary of the stored file Content, that is, the actual data of the file

Apply changes to database

python manage.py makemigrations
python manage.py migrate

Custom Django storage

Define storage class

#!/usr/bin/python
# -*- coding: utf-8 -*-

from django.core.files.base import ContentFile, File
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible


@deconstructible
class AttachmentStorage(Storage):
    """Attachment Storage"""

    def __init__(self, model=None):
        from .models import Attachment
        self.model = Attachment

    def _open(self, file_id, mode='rb'):
        instance = self.model.objects.get(file_id=file_id)
        file = ContentFile(instance.blob)
        file.filename = instance.file_name
        file.mimetype = instance.mime_type
        return file

    def _save(self, name, content: File):
        blob = content.read()
        mime_type = getattr(content, 'content_type', 'text/plain')
        self.model.objects.create(
            file_name=name,
            blob=blob,
            file_size=content.size,
            mime_type=mime_type
        )
        return name

    def exists(self, name):
        return self.model.objects.filter(file_name=name).exists()


attachment_storage = AttachmentStorage()

</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">
Method name Parameters Return value Purpose
_open file_id, mode='rb' ContentFile Opens the file for reading, obtained from the Attachment model based on the given file_id File records and returns a ContentFile object.
_save name, content: File File name Save the file, take the file name and file content as parameters, create AttachmentModel records and saves file information to the database.
exists name Boolean value (True or False) Checks whether the file exists, querying the Attachment model based on the given file name, returning True if the file exists, otherwise returning False.

These methods together form the AttachmentStorage class, which is used to handle the storage and access of attachment files. The _open method is used to read the file, the _save method is used to save the file, and the exists method is used to check whether the file exists. Please note that the initialization method __init__ accepts a model parameter.

Define views

Upload view

class AttachmentUploadView(APIView):
    permission_classes = (permissions.IsAdminUser,)

    def post(self, request, version):
        try:
            file = request.FILES['file']
        except MultiValueDictKeyError:
            raise ValidationError('Parameter error')
        name = attachment_storage.save(file.name, file)
        attachment = get_object_or_404(Attachment, file_name=name)
        return JsonResponse({<!-- -->'download_url': attachment.get_url(request)}, status=status.HTTP_201_CREATED)
  • Function: Process the uploading of attachments.
  • Function: When a POST request is received, the view attempts to obtain a file named ‘file’ from the request and then saves the file using a custom storage backend attachment_storage . Next, it looks for an attachment record in the database that matches the file name and returns a JSON response containing the download link. The main purpose of this view is to allow users to upload attachments and provide download links for uploaded attachments.

Download view

class AttachmentDownloadView(APIView):
    permission_classes = (permissions.IsAuthenticated,)

    def get(self, request, version, attachment_id=None):
        attachment = attachment_storage.open(attachment_id)
        response = HttpResponse(attachment, content_type=attachment.mimetype)
        response['Content-Disposition'] = 'attachment;filename={name}'.format(name=attachment.filename).encode('utf-8')
        return response
  • Function: Process the download operation of attachments.
  • Function: When a GET request is received, this view uses the passed attachment_id parameter to open the corresponding attachment. It then creates an HTTP response object containing the attachment’s contents, sets the response’s content type to the attachment’s MIME type, and sets the response header Content-Disposition to specify the attachment’s file name. Finally, it returns an HTTP response containing the attachment content. The main purpose of this view is to allow users to download attachments by providing their unique identifier.

Content-Disposition is an HTTP response header that instructs the browser how to handle received files. Specifically, the value of the Content-Disposition header tells the browser how it should handle the content of the response, usually for file download operations.

Registration view

#!/usr/bin/python
# -*- coding: utf-8 -*-
from django.urls import re_path, path

from .views import AttachmentUploadView, AttachmentDownloadView

app_name = 'attachment'

urlpatterns = [
    re_path(r'upload', AttachmentUploadView.as_view(), name='upload'),
    path(r'download/<uuid:attachment_id>', AttachmentDownloadView.as_view(), name='download'),
]

Use swagger to test the interface

Upload

Download

Conclusion

When developing web applications, file uploading and downloading are one of the common functions, but security also requires special attention. With proper security configuration, you can protect applications and user data from potential threats. In actual projects, we can add some important security measures, including file type verification, file size limits, CSRF protection, storage path security and other key measures to ensure the security of file upload and download functions.