Django – make ModelForm (of an ImageField) accept only certain types of images

Question:

I am using Pillow 2.3.0 with Django and I have an ImageField in my models.py like so:

class UserImages(models.Model):
    user = models.ForeignKey(User)
    photo = models.ImageField(upload_to=get_file_path)

and I have a form like so:

class UploadImageForm(forms.ModelForm):
    class Meta:
        model = UserImages
        fields = ['photo']

How do I make it so that the UploadImageForm only accepts png’s and jpeg’s? What I tried is adding this clean method to the form:

def clean_photo(self):
    photo = self.cleaned_data.get('photo', False)
    if photo:
        fileType = photo.content_type
        if fileType in settings.VALID_IMAGE_FILETYPES: #png and jpeg
            return photo
    raise forms.ValidationError('FileType not supported: only upload jpegs and pngs.')

but according to this documentation (https://docs.djangoproject.com/en/1.5/topics/http/file-uploads/#uploadedfile-objects) it says that “You’ll still need to validate that the file contains the content that the content-type header claims – ‘trust but verify’.” How do I verify that the user did infact upload the file type which he claimed to upload?

Asked By: SilentDev

||

Answers:

The ImageField validators already verify that the uploaded file is a valid image file supported by Pillow. This is enough to prevent any malicious code injection – the worst that can happen is that someone uploads a file with a different extension than its file format.

I generally don’t verify if an uploaded image is indeed the format it portraits to be. As long as it is a valid image file, and the file extension is in my allowed extension, I accept the uploaded file.

You can, however, easily override the clean_photo method to determine and verify the actual file type:

from django.utils.image import Image

def clean_photo(self):
    photo = self.cleaned_data.get(['photo'])
    if photo:
        format = Image.open(photo.file).format
        photo.file.seek(0)
        if format in settings.VALID_IMAGE_FILETYPES:
            return photo
    raise forms.ValidationError(...)

The photo.file.seek(0) part is important, without it you will run into problems when saving the file!!

Note that format is not necessarily equal to the content type or the extension: in the case of .png it is 'PNG', and in the case of .jpg it’s 'JPEG'. I don’t know if other formats follow suit; you’ll have to test that for yourself.

Image.open does not load the entire file content, just the header, so this method is quite fast. Using timeit and a random .png file, I’ve run the following code 1,000,000 times:

format = Image.open(g.photo).format
g.photo.seek(0)

The average time for this code was 0.0001 seconds, so yeah, lightning fast.

Answered By: knbk