Django rest framework image upload

Last updated: Feb. 1, 2024

Django rest framework image upload

Django provides a versatile and secure approach for image uploading through Django Rest Framework (DRF). Two commonly used methods for image uploading in DRF are

  1. multipart/form-data:
  2. Base64

In this article I explained how to upload image multipart/form-data using the Django Rest Framework.

Features:

  • Single image upload by multipart/form-data
  • Also JavaScript implementation
    • Use Axios library for POST request
    • Use Ajax for post request
  • Secure:
    • CSRF protection
    • File validation

 

Setup django project for image uploading

Install required packages:

Make sure you have Django and Django Rest Framework installed:

pip install django djangorestframework Pillow

 

Configure Django settings:

Add 'rest_framework' and 'yourapp' (replace with your app name) to INSTALLED_APPS in your settings.py:

# yourproject/settings.py

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'yourapp',
]

 

Configure Django MEDIA_ROOT and MEDIA_URL:

Configure your Django settings to define the MEDIA_ROOT and MEDIA_URL, specifying where uploaded media files will be stored and how they will be accessed.

# yourproject/settings.py

MEDIA_URL = "/media/"

MEDIA_ROOT = os.path.join(BASE_DIR, "media")

 

Model for Image Upload:

Define a model in your models.py file to handle image uploads. Use ImageField for storing images:

# yourapp/models.py

from django.db import models

class ImageModel(models.Model):
    image = models.ImageField(upload_to='images/')
    description = models.TextField(blank=True)

 

Don't forget to run migrations:

python manage.py makemigrations
python manage.py migrate

 

Serializer:

Create a serializer in your serializers.py file to convert your model instances to JSON and vice versa:

# yourapp/serializers.py

from rest_framework import serializers
from .models import ImageModel

class ImageModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = ImageModel
        fields = ['image', 'description']

 

Views:

Create a view in your views.py file to handle image uploads:

You can use either a class-based view or a function-based view of your choice. For class base view, you need to handle parser classes, where function base view is very linear. For a function-based view, there is no need for extra hassle.

Class base view

# yourapp/views.py

from rest_framework import generics
from rest_framework.parsers import MultiPartParser, FormParser
from .models import ImageModel
from .serializers import ImageModelSerializer

# For class base view
class ImageUploadView(generics.CreateAPIView):
    queryset = ImageModel.objects.all()
    serializer_class = ImageModelSerializer
    parser_classes = [MultiPartParser, FormParser]

Note: The MultiPartParser and FormParser are used to handle multipart/form-data requests.

OR:

Function base view

from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import ImageModel
from .serializers import ImageModelSerializer

@api_view(["POST"])
def upload_image_view(request):
    serializer = ImageModelSerializer(data=request.data)
    if serializer.is_valid():
        return Response(
            data={
                "response": "success",
                "msg": "Successfully image uploaded"
            }
        )
    else:
        return Response(
            data=serializer.errors
            status=400
        )

 

URL Configuration:

Configure your URLs in urls.py to include the DRF view:

Class base view

# yourapp/urls.py

from django.urls import path
from .views import ImageUploadView

urlpatterns = [
    path('upload/', ImageUploadView.as_view(), name='image-upload'),
    # Add more patterns as needed
]

OR:

Function base view

# yourapp/urls.py

from django.urls import path
from .views import upload_image_view

urlpatterns = [
    path('upload/', upload_image_view, name='image-upload'),
    # Add more patterns as needed
]

 

Include these URLs in your project's urls.py:

# yourproject/urls.py

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('yourapp.urls')),
]

 

Runserver:

Run the development server:

python manage.py runserver

Visit http://localhost:8000/api/upload/ in your browser or use a tool like curl or Postman to send POST requests with image files.

 

Implement frontend with JavaScript for image uploading

To handle image uploading using Django forms and JavaScript Axios, you can create a simple form in your Django templates and use Axios to send the form data to your DRF API endpoint. Here's a step-by-step guide:

Create a Django form for image upload:

# yourapp/forms.py

from django import forms

class ImageUploadForm(forms.Form):
    image = forms.ImageField()
    description = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), required=False)

 

Create a Django view to render the form:

# yourapp/views.py

from django.shortcuts import render
from .forms import ImageUploadForm

def upload_image(request):
    form = ImageUploadForm()
    return render(request, 'upload_image.html', {'form': form})

 

Create a Django template for the form:

<!-- yourapp/templates/upload_image.html -->

<form id="upload-form" method="post" enctype="multipart/form-data" onsubmit="uploadImage(event)">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="button">Upload Image</button>
</form>

<script>
    function uploadImage(event) {
    	event.preventDefault();
        var form = document.getElementById('upload-form');
        var formData = new FormData(form);

        // Use Axios to send a POST request
        axios.post('/api/upload/', formData, {
            headers: {
                'Content-Type': 'multipart/form-data',
            },
        })
        .then(function (response) {
            // Handle success, e.g., redirect to success page
            window.location.href = '/success/';
        })
        .catch(function (error) {
            // Handle error, e.g., display an error message
            console.error('Error uploading image:', error);
        });
    }
</script>

In this template, a JavaScript function (uploadImage(event)) is triggered when the "Upload Image" button is clicked. The function uses Axios to send a POST request with the form data to the DRF API endpoint.

Ajax is a set of techniques using native browser technologies for asynchronous communication, while Axios is a modern JavaScript library that simplifies and enhances the process of making HTTP requests, offering a more developer-friendly and feature-rich API compared to native Ajax.

Also you can use Ajax for uploading image:

<!-- yourapp/templates/upload_image.html -->

<form id="upload-form" method="post" enctype="multipart/form-data" onsubmit="uploadImage(event)">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="button">Upload Image</button>
</form>

<script>
    function uploadImage(event) {
        var form = document.getElementById('upload-form');
        var formData = new FormData(form);

        // Create an XMLHttpRequest object
        var xhr = new XMLHttpRequest();

        // Configure it as a POST request to your DRF API endpoint
        xhr.open('POST', '/api/upload/', true);

        // Set the required headers for form data
        xhr.setRequestHeader('X-CSRFToken', '{{ csrf_token }}');

        // Set the callback function to handle the response
        xhr.onload = function () {
            if (xhr.status === 201) {
                // Handle success, e.g., redirect to success page
                window.location.href = '/success/';
            } else {
                // Handle error, e.g., display an error message
                console.error('Error uploading image:', xhr.responseText);
            }
        };

        // Send the form data
        xhr.send(formData);
    }
</script>

 

Update your Django URLs:

# yourapp/urls.py

from django.urls import path
from .views import upload_image

urlpatterns = [
    path('upload/', upload_image, name='upload_image'),
    # Add more patterns as needed
]

Include these URLs in your project's urls.py.

 

Run your Django development server:

python manage.py runserver

Visit http://localhost:8000/yourapp/upload/ in your browser and use the form to upload an image.

 

Image uploading security concern:

When implementing image uploading in Django Rest Framework (DRF), there are several security considerations to keep in mind to ensure the robustness and safety of your application. Here are key security concerns related to DRF image uploading:

CSRF Protection:

  • Concern: CSRF (Cross-Site Request Forgery) attacks involve malicious actors tricking users into performing unintended actions on a web application where they are authenticated.
  • Mitigation: Ensure that CSRF protection is in place by including a CSRF token in your DRF views. You can use the @csrf_protect decorator for function-based views or the CsrfExemptMixin for class-based views if CSRF protection is not required for your specific use case.

File Upload Validation:

  • Concern: Validate uploaded files to ensure they are of the expected type, size, and format. Malicious users might attempt to upload files containing executable code or other types of exploits.
  • Mitigation: Implement server-side validation for file uploads. Use Django validators and model field options to limit acceptable file types and sizes.

Image upload with CSRF protection for security:

The CsrfExemptMixin is a mixin that exempts a Django class-based view from CSRF protection. It's useful when you have an API endpoint that doesn't require CSRF protection, such as for image uploads. Below is an example of how you can use the CsrfExemptMixin in your Django Rest Framework (DRF) views:

Define the CsrfExemptMixin:

# mixins.py

from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

class CsrfExemptMixin:
    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

 

Use CsrfExemptMixin in Your DRF View:

# views.py

from rest_framework import generics
from rest_framework.parsers import MultiPartParser, FormParser
from .models import ImageModel
from .serializers import ImageModelSerializer
from .mixins import CsrfExemptMixin  # Import the CsrfExemptMixin

class ImageUploadView(CsrfExemptMixin, generics.CreateAPIView):
    queryset = ImageModel.objects.all()
    serializer_class = ImageModelSerializer
    parser_classes = [MultiPartParser, FormParser]

    def perform_create(self, serializer):
        # Custom validation logic can be added here if needed
        serializer.save()

For function base view, you just need to add the @csrf_protect decorator to the view function.

from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import ImageModel
from .serializers import ImageModelSerializer
from django.views.decorators.csrf import csrf_protect


@api_view(["POST"])
@csrf_protect
def upload_image_view(request):
    serializer = ImageModelSerializer(data=request.data)
    if serializer.is_valid():
        return Response(
            data={
                "response": "success",
                "msg": "Successfully image uploaded"
            }
        )
    else:
        return Response(
            data=serializer.errors
            status=400
        )

 

Image uploading file Validation for security:

To enhance security for file uploads in Django, you can implement server-side validation to ensure that uploaded files meet certain criteria such as file type, size, and format. Here's an example of how you can perform file upload validation in a Django Rest Framework (DRF) view:

Assuming you have a model named ImageModel with an ImageField for storing uploaded images, here's how you can validate and restrict file uploads:

Update Your Model:

Make sure to include validators for the ImageField in your model:

# models.py

from django.db import models
from django.core.validators import FileExtensionValidator, MaxFileSizeValidator

class ImageModel(models.Model):
    image = models.ImageField(
        upload_to='images/',
        validators=[
            FileExtensionValidator(allowed_extensions=['jpg', 'jpeg', 'png']),
            MaxFileSizeValidator(limit_bytes=2 * 1024 * 1024),  # Limit file size to 2 MB
        ]
    )
    description = models.TextField(blank=True)

In this example:

  • FileExtensionValidator restricts the allowed file extensions.
  • MaxFileSizeValidator restricts the maximum file size.

 

Update Your Serializer:

If you're using a serializer to handle image uploads, make sure to include the validators attribute for the ImageField:

# serializers.py

from rest_framework import serializers
from .models import ImageModel

class ImageModelSerializer(serializers.ModelSerializer):
    image = serializers.ImageField(
        validators=[
            FileExtensionValidator(allowed_extensions=['jpg', 'jpeg', 'png']),
            MaxFileSizeValidator(limit_bytes=2 * 1024 * 1024),  # Limit file size to 2 MB
        ]
    )

    class Meta:
        model = ImageModel
        fields = ['id', 'image', 'description']

 

Update Your DRF View:

Ensure that your DRF view handles the file upload validation. Here's a simplified example using a CreateAPIView:

# views.py

from rest_framework import generics
from rest_framework.parsers import MultiPartParser, FormParser
from .models import ImageModel
from .serializers import ImageModelSerializer

class ImageUploadView(generics.CreateAPIView):
    queryset = ImageModel.objects.all()
    serializer_class = ImageModelSerializer
    parser_classes = [MultiPartParser, FormParser]

    def perform_create(self, serializer):
        # Custom validation logic can be added here if needed
        serializer.save()

There is no need to change the function base view.