In another “note to self” installation in this series, I couldn’t quite find the answer to the question: “how do you validate file extensions in a form upload with Django?”. There’s some other ways with ModelForms
, but I wasn’t using those.
Really, you and I both know that, especially from a security perspective, it’s pretty laughable to only check the file extension. Some other ways we could be accomplishing this:
accept
attribute, which is easy to bypass, but if it’s an honest user’s mistake, it’ll stop them right theremagic
, which will do a much better job at detecting what exactly is going on inside that file, for the more security-conscious amongst usI didn’t opt for either of these because reasons.
Just to quickly cover the basics, if you’re uploading a file in a Django app, you’re probably doing that through a form. And if you’re using a form, you’re probabably using fields. Specifically in the case of the file upload scenario, a forms.FileField
. Subsequently, in your views.py
you’re (hopefully) calling the form.is_valid()
function before doing any processing. That’s where we’ll come in.
If none of the above made sense to you, you should really be reading up on forms in Django.
If it did make sense, you’ll know that when you call is_valid()
, Django first cleans, then validates the form, before passing it onto you. Think of proper type casting, dealing with tokens etc. We’re going to subclass the FileField
class and add our own validation routine to it.
You can do this wherever you like, though it probably makes most sense to do this in forms.py
if this is the extent of your edits. If you’re going further in that, you may want to re-org. Subclassing is standard Python stuff and as simple as this:
from django import forms
class CsvFileField(forms.FileField):
def validate(self, value):
pass
Remember, by the time our custom validation function rolls around, Django will already have applied cleaning and its standard validation routines. That means that the value
argument you see below is an UploadedFile
, you can read more about those here. Long story short, we can grab its name
attribute to check the file extension:
import os
class CsvFileField(forms.FileField):
def validate(self, value):
# First run the parent class' validation routine
super().validate(value)
# Run our own file extension check
file_extension = os.path.splitext(value.name)[1]
if file_extension != '.csv':
raise ValidationError(
('Invalid file extension'),
code='invalid'
)
Two things to note:
magic
library and do some decent file-extension checking