Multi image upload Django rest framework

Last updated: Feb. 4, 2024

Multi image upload Django rest framework

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 multiple image multipart/form-data using the Django Rest Framework.

Features:

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

 

Setup django project for multi image uploading

Install Required Libraries:

pip install django djangorestframework Pillow

 

Create a Django Project:

django-admin startproject yourprojectname
cd yourprojectname

 

 Create a Django App:

python manage.py startapp yourappname

 

Configure settings.py:

Update yourprojectname/settings.py with the following configurations:

# yourproject/settings.py

# Add 'yourappname' to the 'INSTALLED_APPS' list
INSTALLED_APPS = [
    # ...
    'rest_framework',
    'yourappname',
]
# yourproject/settings.py

# Django Rest Framework settings
REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.MultiPartParser',
        'rest_framework.parsers.FormParser',
    ),
}
# yourproject/settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


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:

# yourappname/models.py

from django.db import models

class ImageModel(models.Model):
    image = models.ImageField(upload_to='images/')

 

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:

# yourappname/serializers.py

from rest_framework import serializers
from .models import ImageModel

class ImageModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = ImageModel
        fields = ('image')

 

Views:

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

Function Base View

# yourappname/views.py

from rest_framework.decorators import parser_classes
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework import status
from .models import ImageModel
from .serializers import ImageModelSerializer

@parser_classes((MultiPartParser, FormParser))
def multi_image_upload_view(request):
    if request.method == 'POST':
        serializer = ImageModelSerializer(data=request.data, many=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    return Response({'message': 'Invalid request method.'}, status=status.HTTP_400_BAD_REQUEST)

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

OR:

Class Base View

# yourappname/views.py

from rest_framework import generics, status
from rest_framework.response import Response
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 create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

Configure URLs:

Update yourappname/urls.py with the following:

For function base view:

# yourappname/urls.py

from django.urls import path
from .views import multi_image_upload_view

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

 

For class base view:

# yourappname/urls.py

from django.urls import path
from .views import ImageUploadView

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

 

Update yourprojectname/urls.py to include your app's URLs:

# yourprojectname/urls.py

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

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

 

Run Development Server:

python manage.py runserver

Now, you can visit http://localhost:8000/api/multiupload/ in your browser or use a tool like curl, Postman, or the browsable API to test the multiple image upload.

 

Implement frontend with JavaScript for multi 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:

Create a Django form in yourappname/forms.py:

# yourappname/forms.py

from django import forms

class ImageUploadForm(forms.Form):
    images = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))

 

Create a Django view to render the form:

# yourappname/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) {
    	var form = document.getElementById('upload-form');

    	const formData = new FormData(form);

	
    	axios.post('http://localhost:8000/api/multiupload/', formData)
        	.then(response => {
            	 // Handle success, e.g., redirect to success page
            	window.location.href = '/success/';
        	})
        	.catch(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.

<!-- 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');
        const formData = new FormData(form);

    	// Create a new XMLHttpRequest object
    	const xhr = new XMLHttpRequest();

	
    	// Set up a callback function to handle the response
    	xhr.onreadystatechange = function () {
        	if (xhr.readyState === 4) {
            	if (xhr.status === 200) {
                	const responseDiv = document.getElementById('response');
                	const responseData = JSON.parse(xhr.responseText);

	
                	// Handle success, e.g., redirect to success page
            		window.location.href = '/success/';	responseDiv.innerHTML = `<h2>Upload Failed</h2><p>${responseData.errors}</p>`;
                	}
            	} else {
                	console.error('Error:', xhr.statusText);

            	}
        	}
    	};

	
    	// Open a POST request to the specified URL
    	xhr.open('POST', 'http://localhost:8000/api/multiupload/', true);

	
    	// 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:

# yourappname/views.py

from rest_framework import generics, status
from rest_framework.response import Response
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 create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

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

# yourappname/views.py

from rest_framework.decorators import parser_classes
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework import status
from .models import ImageModel
from .serializers import ImageModelSerializer

from django.views.decorators.csrf import csrf_protect


@api_view(["POST"])
@csrf_protect
@parser_classes((MultiPartParser, FormParser))
def multi_image_upload_view(request):
    if request.method == 'POST':
        serializer = ImageModelSerializer(data=request.data, many=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    return Response({'message': 'Invalid request method.'}, status=status.HTTP_400_BAD_REQUEST) 

 

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
        ]
    )

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 = ['image']