A common use case in any web application is to allow users to upload files such as images, videos, PDFs, etc. But left unrestricted, this could lead to all sorts of problems including users uploading files that are too large for the system or application to handle.
File size restrictions can be placed on the upload in multiple ways including with JavaScript, web server configuration changes and within your application code. I definitely recommend using all of the above, but for this post, we’ll look at how to handle this in Django by subclassing Django’s FileField and ImageField form fields and adding some extra logic to the clean method.
First, define a max upload size within your settings (in bytes).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Limit uploads to 5MB | |
MAX_UPLOAD_SIZE = 5242880 |
Next, define the new fields (I usually stick these in app/forms/fields.py):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django import forms | |
from django.template.defaultfilters import filesizeformat | |
from django.utils.translation import ugettext_lazy as _ | |
from django.conf import settings | |
class RestrictedFileField(forms.FileField): | |
def __init__(self, *args, **kwargs): | |
self.content_types = kwargs.pop('content_types', None) | |
self.max_upload_size = kwargs.pop('max_upload_size', None) | |
if not self.max_upload_size: | |
self.max_upload_size = settings.MAX_UPLOAD_SIZE | |
super(RestrictedFileField, self).__init__(*args, **kwargs) | |
def clean(self, *args, **kwargs): | |
data = super(RestrictedFileField, self).clean(*args, **kwargs) | |
try: | |
if data.content_type in self.content_types: | |
if data.size > self.max_upload_size: | |
raise forms.ValidationError(_('File size must be under %s. Current file size is %s.') % (filesizeformat(self.max_upload_size), filesizeformat(data.size))) | |
else: | |
raise forms.ValidationError(_('File type (%s) is not supported.') % data.content_type) | |
except AttributeError: | |
pass | |
return data | |
class RestrictedImageField(forms.ImageField): | |
def __init__(self, *args, **kwargs): | |
self.max_upload_size = kwargs.pop('max_upload_size', None) | |
if not self.max_upload_size: | |
self.max_upload_size = settings.MAX_UPLOAD_SIZE | |
super(RestrictedImageField, self).__init__(*args, **kwargs) | |
def clean(self, *args, **kwargs): | |
data = super(RestrictedImageField, self).clean(*args, **kwargs) | |
try: | |
if data.size > self.max_upload_size: | |
raise forms.ValidationError(_('File size must be under %s. Current file size is %s.') % (filesizeformat(self.max_upload_size), filesizeformat(data.size))) | |
except AttributeError: | |
pass | |
return data |
You can now make use of these new fields from within your form class. Note that content_types is required for RestrictedFileField while max_upload_size is optional for both fields (defaults to whatever you specified for MAX_UPLOAD_SIZE in your settings).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
image = RestrictedFileField(content_types=['video/quicktime', 'application/pdf']) # limit to QuickTime and PDF | |
image = RestrictedImageField(max_upload_size=2621440) # limit to 2.5MB |
And remember that the content-type is still user supplied (i.e. it’s a header coming from whatever submitted the form), so be sure to verify that the uploaded file contains the content-type you’re expecting.
Great article
works like a charm, but in admin there’s no more clear checkbox,