Only accept a certain file type in FileField, server-side

How can I restrict FileField to only accept a certain type of file (video, audio, pdf, etc.) in an elegant way, server-side?


Solution 1:

One very easy way is to use a custom validator.

In your app's validators.py:

def validate_file_extension(value):
    import os
    from django.core.exceptions import ValidationError
    ext = os.path.splitext(value.name)[1]  # [0] returns path+filename
    valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.png', '.xlsx', '.xls']
    if not ext.lower() in valid_extensions:
        raise ValidationError('Unsupported file extension.')

Then in your models.py:

from .validators import validate_file_extension

... and use the validator for your form field:

class Document(models.Model):
    file = models.FileField(upload_to="documents/%Y/%m/%d", validators=[validate_file_extension])

See also: How to limit file types on file uploads for ModelForms with FileFields?.

Warning

For securing your code execution environment from malicious media files

  1. Use Exif libraries to properly validate the media files.
  2. Separate your media files from your application code execution environment
  3. If possible use solutions like S3, GCS, Minio or anything similar
  4. When loading media files on client side, use client native methods (for example if you are loading the media files non securely in a browser, it may cause execution of "crafted" JavaScript code)

Solution 2:

Django in version 1.11 has a newly added FileExtensionValidator for model fields, the docs is here: https://docs.djangoproject.com/en/dev/ref/validators/#fileextensionvalidator.

An example of how to validate a file extension:

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

class MyModel(models.Model):
    pdf_file = models.FileField(upload_to='foo/',
                                validators=[FileExtensionValidator(allowed_extensions=['pdf'])])

Note that this method is not safe. Citation from Django docs:

Don’t rely on validation of the file extension to determine a file’s type. Files can be renamed to have any extension no matter what data they contain.

There is also new validate_image_file_extension (https://docs.djangoproject.com/en/dev/ref/validators/#validate-image-file-extension) for validating image extensions (using Pillow).

Solution 3:

A few people have suggested using python-magic to validate that the file actually is of the type you are expecting to receive. This can be incorporated into the validator suggested in the accepted answer:

import os
import magic
from django.core.exceptions import ValidationError

def validate_is_pdf(file):
    valid_mime_types = ['application/pdf']
    file_mime_type = magic.from_buffer(file.read(1024), mime=True)
    if file_mime_type not in valid_mime_types:
        raise ValidationError('Unsupported file type.')
    valid_file_extensions = ['.pdf']
    ext = os.path.splitext(file.name)[1]
    if ext.lower() not in valid_file_extensions:
        raise ValidationError('Unacceptable file extension.')

This example only validates a pdf, but any number of mime-types and file extensions can be added to the arrays.

Assuming you saved the above in validators.py you can incorporate this into your model like so:

from myapp.validators import validate_is_pdf

class PdfFile(models.Model):
    file = models.FileField(upload_to='pdfs/', validators=(validate_is_pdf,))

Solution 4:

You can use the below to restrict filetypes in your Form

file = forms.FileField(widget=forms.FileInput(attrs={'accept':'application/pdf'}))