File Upload with Django - Building REST API with Django (Part 2)

File Upload with Django - Building REST API with Django (Part 2)

If you have ever wanted to perform file upload in Django and you don’t really understand the code you see in the Django Rest Framework documentation, this article is for you.

I will try to make this article as simple as possible and I will restrain from using the classes provided in the documentation. This is going to be a manual implementation.

In this article, we are going to talk about file upload with Django. This article is a continuation of the Rest API with Django article. In there, we talked about how to build REST API with Django.

We are going to create a new Django app with the command:

django-admin startapp books

Go to the settings.py file, the INSTALLED_APPS variable should look like this:

INSTALLED_APPS = [
...
# Installed apps
'rest_framework',
# Custom apps
'accounts',
'books'
]

First, let’s create our Book model in book/models.py:

from django.db import models
import os
from accounts.models import User
# Create your models here.


class Book(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)
    description = models.TextField()
    image = models.ImageField(upload_to="book-cover/")
    file = models.FileField(upload_to="book-file/")

    def __str__(self):
        return self.name

    def delete(self, *args, **kwargs):
        super().save(*args, **kwargs)
        os.system(f"rm media/{self.image}")
        os.system(f"rm media/{self.file}")

From above we can see that the Book model is linked to the User model by the ForeignKey model attribute, we also have an ImageField and a FileField. We are overriding the delete method because we want to also delete the files from the system when the model is deleted. Our files will be saved in the media folder and the command rm media/{self.image} is a Linux command to delete files.

Since we are handling files and we want to save them to the media folder, add the following to the end of your settings.py file.

MEDIA_ROOT  = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/

Do not forget to include this new app url in the main urls.py file.

urlpatterns = [
    ...
    path('api/books/', include('books.urls', namespace='books'))
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

For this part, we are not going to use serializers. Let us write our views.py for the books app. If you don’t know about serializers, you can check them out here :

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from accounts.models import User
from .models import Book


@csrf_exempt
def create_book(request):
    if request.method == 'POST':
        user_info = request.META.get('AUTH_USER')
        author_email = user_info.get('email')
        image = request.FILES.get('image')
        file = request.FILES.get('file')
        if image and file:
            data = {
                'author': User.objects.get(email=author_email),
                'name': request.POST.get('name'),
                'description': request.POST.get('description'),
                'image': image,
                'file': file
            }
            print(data)
            book = Book.objects.create(**data)
            # print(book.name, book.description, book.author.email)
            return JsonResponse({"data": {
                "id": book.id,
                "name": book.name,
                "description": book.description,
                "author_email": book.author.email,
                "image": book.image.url,
                "file": book.file.url
            }})
        else:
            return JsonResponse({"errors": "The image and file are required fields"})
    else:
        return JsonResponse({"errors": "You must send a POST request"})

@csrf_exempt
def delete_book(request):
    if request.method == 'POST':
        book_id = request.POST.get('id')
        user_info = request.META.get('AUTH_USER')
        author_email = user_info.get('email')
        book = Book.objects.get(id=book_id)
        if book.author.email != author_email:
            return JsonResponse({"errors": "You cannot delete this book"})
        else:
            book.delete()
            return JsonResponse({"message": "Book deleted successfully"})
    else:
        return JsonResponse({"errors": "You must send a POST request"})

We get the author information for our header, how this is done has already been explained in the previous article.

The request.POST object contains the text fields of the request. The request.FILE contains on the file fields sent with the request. We get the file fields and check if they are not None, then we create then with the Book.objects.create command and return the appropriate response.

For the delete_book function, we get the author information from the header and also the book id from the POST request. The author information is used to validate whether the user that wants to delete the book is the author of the book. So when book.delete() gets called, the particular record is deleted and also the image and file are deleted from the system.

Conclusion

DjangoRestFramework offers parsers for file upload and stuffs like this, you can read about it here . This is meant to be a detailed method on file upload and also deleting files.