Validate image size in django admin

Viewed 19856

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?

5 Answers

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"))

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)
Related