27

I see a lot of people with Django apps that have image uploads are automatically resizing the images after they are uploaded. That is well and good for some cases, but I don't want to do this. Instead, I simply want to force the user to upload a file that is already the proper size.

I want to have an ImageField where I force the user to upload an image that is 100x200. If the image they upload is not exactly that size, I want the admin form to return as invalid. I would also like to be able to do the same thing for aspect ratios. I want to force the user to upload an image that is 16:9 and reject any upload that does not conform.

I already know how to get the width and height of the image, but I can't do that server-side until after the image is already uploaded, and the form is submitted successfully. How can I check this earlier, if possible?

Apreche
  • 30,042
  • 8
  • 41
  • 52
  • Take a look at [django-vimage](https://github.com/manikos/django-vimage) library. I have just created it and tries to solve such scenarios. Greetings :) – nik_m Apr 27 '18 at 17:14

5 Answers5

61

The right place to do this is during form validation.
A quick example (will edit/integrate with more info later):

from django.core.files.images import get_image_dimensions
from django.contrib import admin
from django import forms

class myForm(forms.ModelForm):
   class Meta:
       model = myModel
   def clean_picture(self):
       picture = self.cleaned_data.get("picture")
       if not picture:
           raise forms.ValidationError("No image!")
       else:
           w, h = get_image_dimensions(picture)
           if w != 100:
               raise forms.ValidationError("The image is %i pixel wide. It's supposed to be 100px" % w)
           if h != 200:
               raise forms.ValidationError("The image is %i pixel high. It's supposed to be 200px" % h)
       return picture

class MyAdmin(admin.ModelAdmin):
    form = myForm

admin.site.register(Example, MyAdmin)
Flimm
  • 136,138
  • 45
  • 251
  • 267
Agos
  • 18,542
  • 11
  • 56
  • 70
  • I was too slow, here's the related link: http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute – monkut Dec 09 '09 at 17:21
  • 1
    While this solution is cool for reference, the OP said he wants to check the dimensions of the image BEFORE form submission. So it has to be something client side. Or am I misreading his last paragraph? – cethegeek Dec 09 '09 at 17:58
  • I don't necessarily mean before form submission, as long as it's before the model is saved. – Apreche Dec 09 '09 at 18:17
  • Well, then I think you found your winner! :-) – cethegeek Dec 09 '09 at 18:54
  • 1
    Even if a pre-submission validator is used, you can never trust code run on the client so need to double-check. – Nick T Mar 20 '14 at 01:09
  • get_image_dimension, highly useful ! Just apply it to request.FILES['image_field_name'] and bam! We get dimensions without saving the image already. Thanks. – bad_keypoints Apr 16 '15 at 09:22
  • 1
    really good solution.Thanks a lot. Looking at the `if not picture`. Can we apply this for the content-type of the image field? So instead of `if not picture`, it can be `if not picture.content_type == "jpg" ` – Taiwotman Aug 27 '17 at 13:52
6

use this function in your model file,

from django.core.exceptions import ValidationError

def validate_image(fieldfile_obj):
    filesize = fieldfile_obj.file.size
    megabyte_limit = 2.0
    if filesize > megabyte_limit*1024*1024:
        raise ValidationError("Max file size is %sMB" % str(megabyte_limit))

class Company(models.Model):
    logo = models.ImageField("Logo", upload_to=upload_logo_to,validators=[validate_image], blank=True, null=True,help_text='Maximum file size allowed is 2Mb')
M.javid
  • 6,387
  • 3
  • 41
  • 56
Muhammad Taqi
  • 5,356
  • 7
  • 36
  • 61
  • 1
    This does not work. Referring to the file in this way from a validator, before the file has been saved, simply returns `*** FileNotFoundError: [Errno 2] No such file or directory: '/srv/www/media/80329e39-5c5c-4e79-8e32-51087ae00f6a'`. Perhaps it would work with files that Django does not save as `InMemoryUploadedFile` objects, where they are in `/tmp` or somewhere similar as an actual file. I have not tested that, however. – Bryson Jan 28 '16 at 04:25
2

We can also use a class with a __call__() method to provide some parameters.

As suggested in Writing validators - Django Docs:

You can also use a class with a __call__() method for more complex or configurable validators. RegexValidator, for example, uses this technique. If a class-based validator is used in the validators model field option, you should make sure it is serializable by the migration framework by adding deconstruct() and __eq__() methods.

Here is a working example:

try:
    from collections.abc import Mapping
except ImportError:
    from collections import Mapping

from django.utils.translation import ugettext_lazy as _
from django.utils.deconstruct import deconstructible


@deconstructible
class ImageValidator(object):
    messages = {
        "dimensions": _(
            'Allowed dimensions are: %(width)s x %(height)s.'
        ),
        "size": _(
            "File is larger than > %(size)skB."
        )
    }

    def __init__(self, size=None, width=None, height=None, messages=None):
        self.size = size
        self.width = width
        self.height = height
        if messages is not None and isinstance(messages, Mapping):
            self.messages = messages

    def __call__(self, value):
        # _get_image_dimensions is a method of ImageFile
        # https://docs.djangoproject.com/en/1.11/_modules/django/core/files/images/
        if self.size is not None and value.size > self.size:
            raise ValidationError(
                self.messages['size'],
                code='invalid_size',
                params={
                    'size': float(self.size)/1024,
                    'value': value,
                }
            )
        if (self.width is not None and self.height is not None and
                (value.width != self.width or value.height != self.height)):
            raise ValidationError(
                self.messages['dimensions'],
                code='invalid_dimensions',
                params={
                    'width': self.width,
                    'height': self.height,
                    'value': value,
                }
            )

    def __eq__(self, other):
        return (
            isinstance(other, self.__class__) and
            self.size == other.size and
            self.width == other.width and
            self.height == other.height
        )

And than in model:


class MyModel(models.Model):

    ...

    banner = models.ImageField(
        upload_to='uploads/', verbose_name=_("Banner"),
        max_length=255, null=True, blank=True,
        validators=[ImageValidator(size=256000, width=1140, height=425)],
        help_text=_("Please use our recommended dimensions: 1140 x 425 PX, 250 KB MAX"))
NKSM
  • 5,422
  • 4
  • 25
  • 38
0

Update and Models.py version of the above answer

from django.core.exceptions import ValidationError
from django.core.files.images import get_image_dimensions

class Model(models.Model):
 photo = models.ImageField()

 def clean(self):

    if not self.photo:
        raise ValidationError("No image!")
    else:
        w, h = get_image_dimensions(self.photo)
        if w != 200:
            raise ValidationError("The image is %i pixel wide. It's supposed to be 200px" % w)
        if h != 200:
            raise ValidationError("The image is %i pixel high. It's supposed to be 200px" % h)
Azmol
  • 399
  • 4
  • 6
-1

This may be a dupe of/very similar to this question:

Using jQuery, Restricting File Size Before Uploading

I don't think there is a way to do it the way you want. Agos solution is the way to go...

Edit: One of the answers in the linked SO question talks about using flash to do it. So while it may be possible to do with some other technology, I don't think you can do it with straight javascript.

Community
  • 1
  • 1
cethegeek
  • 6,286
  • 35
  • 42
  • Another possible technology could be a Java applet. I've seen quite a number of uploaders tackle the matter that way. It's also useful in case you actually want to allow big/plenty of uploads. – Agos Dec 09 '09 at 18:53