Django: handle form validation with combined POST and FILES data

If you have a Django forms.Form with two or more fields to validate that require information from each other you can test these in the forms' clean method.

However, if you need to test a condition that relies upon normal form data in addition to uploaded file data, the code below will let you do so:

forms.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""Allows checking of form POST data as well as uploaded files when validating
a standard Django `forms.Form`.

The short version is that you check `Form().files` in the `clean()` method, 
assuming the form has been bound to the request.
"""

from django import forms


class BannerUploadForm(forms.Form):
    banner = forms.FileField()
    placeholder_text = forms.CharField()

    def clean(self):
        cleaned_data = super(BannerUploadForm, self).clean()

        text = cleaned_data.get('placeholder_text')

        if not text and not 'banner' in self.files:
            raise forms.ValidationError('Please upload a banner image or '
                                        'add placeholder text.')

        return cleaned_data
upload.html
1
2
3
4
5
6
7
8
{% extends 'base.html' %}

{% block content %}
<form method="post" action="{% url upload-banner %}" enctype="multipart/form-data">{% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Upload">
</form>
{% endblock content %}
urls.py
1
url(r'^banner/upload/$', 'banners.views.upload_banner', name='upload-banner')
views.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from django.shortcuts import render

from .forms import BannerUploadForm


def upload_banner(request):
    template_name = 'banners/upload.html'
    context = {}

    if request.method == 'POST':
        # request.FILES must be bound to form else form.files will be empty
        form = BannerUploadForm(request.POST, request.FILES)

        if form.is_valid():
            # your handler here
            template_name = 'banners/success.html'
    else:
        form = BannerUploadForm()

    context['form'] = form
    
    return render(request, template_name, context)