Skip to main content

Django Pictures Logo

Django Pictures

Responsive cross-browser image library using modern codes like AVIF & WebP.

  • responsive web images using the picture tag
  • native grid system support
  • serve files with or without a CDN
  • placeholders for local development
  • migration support
  • async image processing via Django Tasks
  • DRF support

PyPi Version Test Coverage GitHub License

Sponsors

Sponsors

Usage

Before you start, it can be a good idea to understand the fundamentals of responsive images.

Once you get a feeling how complicated things can get with all device types, you'll probably find a new appreciation for this package, and are ready to adopt in your project :)

# models.py
from django.db import models
from pictures.models import PictureField


class Profile(models.Model):
    title = models.CharField(max_length=255)
    picture = PictureField(
        upload_to="avatars", width_field="picture_width", height_field="picture_height"
    )
    picture_width = models.PositiveIntegerField(editable=False)
    picture_height = models.PositiveIntegerField(editable=False)
<!-- template.html -->
{% load pictures %}
{% picture profile.picture img_alt="Spiderman" img_loading="lazy" picture_class="my-picture" m=6 l=4 %}

The keyword arguments m=6 l=4 define the columns the image should take up in a grid at a given breakpoint. So in this example, the image will take up six columns on medium screens and four columns on large screens. You can define your grid and breakpoints as you want, refer to the grid columns and breakpoints sections.

The template above will render into:

<picture class="my-picture">
  <source type="image/webp"
          srcset="/media/testapp/profile/image/800w.webp 800w, /media/testapp/profile/image/100w.webp 100w, /media/testapp/profile/image/200w.webp 200w, /media/testapp/profile/image/300w.webp 300w, /media/testapp/profile/image/400w.webp 400w, /media/testapp/profile/image/500w.webp 500w, /media/testapp/profile/image/600w.webp 600w, /media/testapp/profile/image/700w.webp 700w"
          sizes="(min-width: 0px) and (max-width: 991px) 100vw, (min-width: 992px) and (max-width: 1199px) 33vw, 600px">
  <img src="/media/testapp/profile/image.jpg" alt="Spiderman" width="800" height="800" loading="lazy">
</picture>

Note that arbitrary attributes can be passed to either the <picture> or <img> element by prefixing parameters to the {% picture %} tag with picture_ or img_ respectively.

Setup

Installation

python3 -m pip install django-pictures

Settings

# settings.py
INSTALLED_APPS = [
    # ...
    "pictures",
]

# the following are defaults, but you can override them
PICTURES = {
    "BREAKPOINTS": {
        "xs": 576,
        "s": 768,
        "m": 992,
        "l": 1200,
        "xl": 1400,
    },
    "GRID_COLUMNS": 12,
    "CONTAINER_WIDTH": 1200,
    "FILE_TYPES": ["AVIF"],
    "PIXEL_DENSITIES": [1, 2],
    "USE_PLACEHOLDERS": True,
    "QUEUE_NAME": "pictures",
    "BACKEND": "default",
    "PROCESSOR": "pictures.tasks.process_picture",
}

If you have either Dramatiq or Celery installed, we will default to async image processing. You will need workers to listen to the pictures queue.

# settings.py
TASKS = {
    "default": {
        # ...
        "QUEUES": [
            # ...
            "pictures",  # add the new pictures queue here
        ]
    },
}

Placeholders

This library comes with dynamically created placeholders to simplify local development. To enable them, add the following to enable the PICTURES["USE_PLACEHOLDERS"] setting and add the following URL configuration:

# urls.py
from django.urls import include, path
from pictures.conf import app_settings

urlpatterns = [
    # ...
]

if app_settings.USE_PLACEHOLDERS:
    urlpatterns += [
        path("_pictures/", include("pictures.urls")),
    ]

Legacy use-cases (email)

Although the picture-tag is adequate for most use-cases, some remain, where a single img tag is necessary. Notably in email, where most clients do support WebP but not srcset. The template tag img_url returns a single size image URL. In addition to the ratio, you will need to define the file_type as well as the width (absolute width in pixels).

{% load pictures %}
<img src="{% img_url profile.picture ratio='3/2' file_type='webp' width=800 %}" alt="profile picture">

Config

Aspect ratios

You can specify the aspect ratios of your images. Images will be cropped to the specified aspect ratio. Aspect ratios are specified as a string with a slash between the width and height. For example, 16/9 will crop the image to 16:9.

# models.py
from django.db import models
from pictures.models import PictureField


class Profile(models.Model):
    title = models.CharField(max_length=255)
    picture = PictureField(
        upload_to="avatars",
        aspect_ratios=[None, "1/1", "3/2", "16/9"],
        width_field="picture_width",
        height_field="picture_height",
    )
    picture_width = models.PositiveIntegerField(editable=False)
    picture_height = models.PositiveIntegerField(editable=False)
# template.html
{% load pictures %}
{% picture profile.picture img_alt="Spiderman" ratio="16/9" m=6 l=4 %}

If you don't specify an aspect ratio or None in your template, the image will be served with the original aspect ratio of the file.

You may only use aspect ratios in templates that have been defined on the model. The model aspect_ratios will default to [None], if other value is provided.

Breakpoints

You may define your own breakpoints they should be identical to the ones used in your CSS library. This can be achieved by overriding the PICTURES["BREAKPOINTS"] setting.

Grid columns

Grids are so common in web design that they even made it into CSS. We default to 12 columns, but you can override this setting, via the PICTURES["GRID_COLUMNS"] setting.

Container width

Containers are commonly used to limit the maximum width of layouts, to promote better readability on larger screens. We default to 1200px, but you can override this setting, via the PICTURES["CONTAINER_WIDTH"] setting.

You may also set it to None, should you not use a container.

File types

AVIF (WebP's successor) is the best and most efficient image format available today. It is part of Baseline 2024 and is supported by all major browsers. Additionally, most modern devices will have hardware acceleration for AVIF decoding. This will not only reduce network IO but speed up page rendering.

Should you still serve IE11, use add JPEG to the list. But, beware, this may drastically increase your storage needs.

Pixel densities

Unless you really care that your images hold of if you hold your UHD phone very close to your eyeballs, you should be fine, serving at the default 1x and 2x densities.

Async image processing

Images are processed on a separate queue by default, you will need to add the pictures queue to your task backend configuration. You can override the queue name, via the PICTURES["QUEUE_NAME"] setting.

If you want to run image processing on a separate backend, you can set the PICTURES["BACKEND"] setting to something other than default.

You may also override the processor to explicitly use Celery, Dramatiq or Django RQ until they provide proper integration with Django Tasks.

# settings.py
PICTURES = {
    "PROCESSOR": "pictures.tasks.celery_process_picture",  # use Celery
    # or
    "PROCESSOR": "pictures.tasks.dramatiq_process_picture",  # use Dramatiq
    # or
    "PROCESSOR": "pictures.tasks.rq_process_picture",  # use Django RQ
}

Pre Django 6.0

If you have either Dramatiq or Celery installed, we will default to async image processing. You will need workers to listen to the pictures queue. You can override the queue name, via the PICTURES["QUEUE_NAME"] setting.

You can also override the processor, via the PICTURES["PROCESSOR"] setting. The default processor is pictures.tasks.process_picture. It is called with the storage, file_name, sender, new and old keyword arguments.

Signals

Image processing emits a picture_processed signal after successful completion. The sender is the PictureField instance, and receivers get the processed file_name plus the rendered new and obsolete old picture files.

# models.py
from django.db import models
from django.dispatch import receiver
from pictures.models import PictureField
from pictures.signals import picture_processed


class Profile(models.Model):
    title = models.CharField(max_length=255)
    # the file name index speeds up the lookup in the signal handler below
    picture = PictureField(upload_to="avatars", db_index=True)
    picture_processed = models.BooleanField(editable=False, default=False)


@receiver(picture_processed, sender=Profile._meta.get_field("picture"))
def mark_picture_processed(sender, file_name, **kwargs):
    sender.model.objects.filter(**{sender.name: file_name}).update(
        picture_processed=True
    )

Validators

The library ships with validators to restrain image dimensions:

from django.db import models
from pictures.models import PictureField
from pictures.validators import MaxSizeValidator, MinSizeValidator


class Profile(models.Model):
    picture = PictureField(
        upload_to="avatars",
        validators=[
            MinSizeValidator(400, 300),  # At least 400x300 pixels
            MaxSizeValidator(4096, 4096),  # At most 4096x4096 pixels
        ],
        width_field="picture_width",
        height_field="picture_height",
    )
    picture_width = models.PositiveIntegerField(editable=False)
    picture_height = models.PositiveIntegerField(editable=False)


# Use `None` to limit only one dimension: `MaxSizeValidator(2048, None)` limits only width.

Migrations

Django doesn't support file field migrations, but we do. You can auto create the migration and replace Django's AlterField operation with AlterPictureField. That's it.

You can follow the example in our test app, to see how it works.

Contrib

Django Rest Framework (DRF)

We do ship with a read-only PictureField that can be used to include all available picture sizes in a DRF serializer.

from rest_framework import serializers
from pictures.contrib.rest_framework import PictureField


class PictureSerializer(serializers.Serializer):
    picture = PictureField()

The response can be restricted to a fewer aspect ratios and file types, by providing the aspect_ratios and file_types arguments to the DRF field.

from rest_framework import serializers
from pictures.contrib.rest_framework import PictureField


class PictureSerializer(serializers.Serializer):
    picture = PictureField(aspect_ratios=["16/9"], file_types=["AVIF"])

You also may provide optional GET parameters to the serializer to specify the aspect ratio and breakpoints you want to include in the response. The parameters are prefixed with the fieldname_ to avoid conflicts with other fields.

curl http://localhost:8000/api/path/?picture_ratio=16%2F9&picture_m=6&picture_l=4
# %2F is the url encoded slash
{
  "other_fields": "…",
  "picture": {
    "url": "/path/to/image.jpg",
    "width": 800,
    "height": 800,
    "ratios": {
      "1/1": {
        "sources": {
          "image/webp": {
            "100": "/path/to/image/1/100w.webp",
            "200": "…"
          }
        },
        "media": "(min-width: 0px) and (max-width: 991px) 100vw, (min-width: 992px) and (max-width: 1199px) 33vw, 25vw"
      }
    }
  }
}

Note that the media keys are only included, if you have specified breakpoints.

Django Cleanup

PictureField is compatible with Django Cleanup, which automatically deletes its file and corresponding SimplePicture files.

external image processing (via CDNs)

This package is designed to accommodate growth, allowing you to start small and scale up as needed. Should you use a CDN, or some other external image processing service, you can set this up in two simple steps:

  1. Override PICTURES["PROCESSOR"] to disable the default processing.
  2. Override PICTURES["PICTURE_CLASS"] implement any custom behavior.
# settings.py
PICTURES = {
    "PROCESSOR": "pictures.tasks.noop",  # disable default processing and do nothing
    "PICTURE_CLASS": "path.to.MyPicture",  # override the default picture class
}

The MyPictureclass should implement the url property, which returns the URL of the image. You may use the Picture class as a base class.

Available attributes are:

  • parent_name - name of the source file uploaded to the PictureField
  • aspect_ratio - aspect ratio of the output image
  • width - width of the output image
  • file_type - format of the output image
# path/to.py
from pathlib import Path
from pictures.models import Picture


class MyPicture(Picture):
    @property
    def url(self):
        return (
            f"https://cdn.example.com/{Path(self.parent_name).stem}"
            f"_{self.aspect_ratio}_{self.width}w.{self.file_type.lower()}"
        )

Metadata

Release files for django-pictures 1.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-pictures 1.8.0
File Size Uploaded
django_pictures-1.8.0.tar.gz 24.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-pictures 1.8.0
File Interpreter ABI Platform
django_pictures-1.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 48.7 kB

Release files / django_pictures-1.8.0.tar.gz

Download URL django_pictures-1.8.0.tar.gz
Size 24.6 kB
Tags Source
SHA-256 checksum
How to use checksums
08c7417d72ae795e88b099bf78101ff2df9e385115d6a4a64f9c41bcb00e5d88
BLAKE2b-256 checksum
How to use checksums
ab924a04680a10e2092e22286b6a3b2f481d762769ca74b27b0b9c184654f5ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / django_pictures-1.8.0-py3-none-any.whl

Download URL django_pictures-1.8.0-py3-none-any.whl
Size 24.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
795e889b591373ab99eee8455a2dd839561c72745a9e461d646ec70112c1d5be
BLAKE2b-256 checksum
How to use checksums
a16142ed0f06dabd90ed647979d08349777243ebd20a7f90e94a66715c621064
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

2.0.0

2 release files

1.9.0

2 release files

This release

1.8.0 This release

2 release files

1.7.5

2 release files

1.7.4

2 release files

1.7.3

2 release files

1.7.2

2 release files

1.7.1

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.5

2 release files

1.5.4

2 release files

1.5.3

2 release files

1.5.2

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.4

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page