================= django-imagefield ================= .. image:: https://github.com/matthiask/django-imagefield/workflows/Tests/badge.svg :target: https://github.com/matthiask/django-imagefield .. image:: https://readthedocs.org/projects/django-imagefield/badge/?version=latest :target: https://django-imagefield.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status Heavily based on `django-versatileimagefield `_, but with a few important differences: - The amount of code is kept at a minimum. django-versatileimagefield has several times as much code (without tests). - Generating images on-demand inside rendering code is made hard on purpose. Instead, images are generated when models are saved and also by running the management command ``process_imagefields``. - django-imagefield does not depend on a fast storage or a cache to be and stay fast, at least as long as the image width and height is saved in the database. An important part of this is never determining whether a processed image exists in the hot path at all (except if you ``force`` it). - django-imagefield fails early when image data is incomplete or not processable by Pillow_ for some reason. - django-imagefield allows adding width, height and PPOI (primary point of interest) fields to the model by adding ``auto_add_fields=True`` to the field instead of boringly and verbosingly adding them yourself. Replacing existing uses of django-versatileimagefield requires the following steps: - ``from imagefield.fields import ImageField as VersatileImageField, PPOIField`` - Specify the image sizes by either providing ``ImageField(formats=...)`` or adding the ``IMAGEFIELD_FORMATS`` setting. The latter overrides the former if given. - Convert template code to access the new properties (e.g. ``instance.image.square`` instead of ``instance.image.crop.200x200`` when using the ``IMAGEFIELD_FORMATS`` setting below). - When using django-imagefield with a PPOI, make sure that the PPOI field is also added to ``ModelAdmin`` or ``InlineModelAdmin`` fieldsets, otherwise you'll just see the image, but no PPOI picker. Contrary to django-versatileimagefield the PPOI field is editable itself, which avoids apart from other complexities a pitfall with inline form change detection. - Add ``"imagefield"`` to ``INSTALLED_APPS``. If you used e.g. ``instance.image.crop.200x200`` and ``instance.image.thumbnail.800x500`` before, you should add the following setting: .. code-block:: python IMAGEFIELD_FORMATS = { # image field path, lowercase 'yourapp.yourmodel.image': { 'square': ['default', ('crop', (200, 200))], 'full': ['default', ('thumbnail', (800, 500))], # The 'full' spec is equivalent to the following format # specification in terms of image file produced (the # resulting file name is different though): # 'full': [ # 'autorotate', 'process_jpeg', 'process_png', # 'process_gif', 'autorotate', # ('thumbnail', (800, 500)), # ], # Note that the exact list of default processors may # change in the future. }, } After running ``./manage.py process_imagefields --all`` once you can now use use ``instance.image.square`` and ``instance.image.thumbnail`` in templates instead. Note that the properties on the ``image`` file do by design not check whether thumbs exist. Installation ============ Install from PyPI: ``pip install django-imagefield``. For faster image processing with pyvips (optional):: pip install django-imagefield[vips] Then add ``imagefield`` to your project's ``INSTALLED_APPS``:: # settings.py INSTALLED_APPS = [ ... "imagefield", ... ] Image Processing Backends ========================== django-imagefield supports two image processing backends: **Pillow (default)** The default backend using the Pillow library. Provides 100% backward compatibility with existing code. No configuration needed. **pyvips (optional, faster)** An optional backend using the libvips library through pyvips. Offers significantly better performance: - Significantly faster image processing - More memory-efficient image handling - Improved handling of large images To use the pyvips backend: 1. Install the optional dependency:: pip install django-imagefield[vips] 2. Configure the backend in your settings:: # settings.py IMAGEFIELD_BACKEND = "vips" # default is "pillow" Both backends support all the same features and processors. You can switch between backends without changing your code or reprocessing existing images. Backend Behavior Differences ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ While both backends provide the same API, there are some subtle differences in how images are processed: **ICC Color Profiles** - **Pillow**: Explicitly preserves ICC profiles via ``preserve_icc_profile`` processor - **vips**: Automatically preserves ICC profiles during image operations **JPEG Color Space Handling** - **Pillow**: Converts all non-RGB images (including grayscale) to RGB - **vips**: Preserves grayscale images natively, only converts CMYK and images with transparency. Results in smaller file sizes for grayscale JPEGs. **PNG Indexed Color Handling** - **Pillow**: Converts palette mode ("P") images to RGBA - **vips**: Converts images with < 3 bands (indexed/palette) to RGBA These differences are generally transparent and result in equivalent or improved output quality. The vips backend is optimized for better performance and smaller file sizes where possible. Custom Processors ----------------- When writing custom processors, you work directly with the native image objects of your chosen backend: **Pillow backend** - processors receive ``PIL.Image.Image`` objects:: from imagefield.processing_pillow import register_pillow from PIL import ImageDraw, ImageFont @register_pillow def add_watermark(get_image, text="© Copyright"): def processor(image, context): image = get_image(image, context) # Use full PIL API draw = ImageDraw.Draw(image) font = ImageFont.truetype("arial.ttf", 36) draw.text((10, 10), text, font=font, fill=(255, 255, 255, 128)) return image return processor **pyvips backend** - processors receive ``pyvips.Image`` objects:: from imagefield.processing_vips import register_vips import pyvips @register_vips def add_watermark(get_image, text="© Copyright"): def processor(image, context): image = get_image(image, context) # Use full pyvips API text_img = pyvips.Image.text(text, font="sans 36", rgba=True) return image.composite(text_img, 'over', x=10, y=10) return processor For processors that only manipulate context (like changing format or quality), you can register them for both backends:: from imagefield.processing_pillow import register_pillow try: from imagefield.processing_vips import register_vips except ImportError: register_vips = lambda fn: fn @register_pillow @register_vips def force_quality(get_image, quality=95): def processor(image, context): context.save_kwargs["quality"] = quality return get_image(image, context) return processor Usage ===== Once ``imagefield`` is added to ``INSTALLED_APPS``, add ``ImageField`` instances to your Django models in the usual way:: from django.db import models from imagefield.fields import ImageField class ImageModel(models.Model): image = ImageField( upload_to="images", formats={ "thumb": ["default", ("crop", (300, 300))], "desktop": ["default", ("thumbnail", (300, 225))], }, auto_add_fields=True, ) * ``formats`` determines the sizes of the processed images created. * ``auto_add_fields`` will add ``image_width``, ``image_height``, and ``image_ppoi`` fields automatically, if not present on the model. (The field names used are customisable. See the ``ImageField`` constructor for details.) A widget for selecting the PPOI is automatically used in the Django Admin. To use an ``ImageField`` in your own Django Form, you should ensure that the ``image_ppoi`` field is added the form:: from django.form import modelform_factory form_cls = modelform_factory(ImageModel, fields=['image', 'image_ppoi']) You should make sure to add the ``form.media`` to your page template's ````. Retrieve the image URL in your template like, ``instance.image.thumb``. Template Usage ============== Access image URLs in templates by using the format names defined in ``formats`` or ``IMAGEFIELD_FORMATS``: .. code-block:: html Thumbnail Desktop
Image IMAGEFIELD_FORMATS Structure ============================= The ``IMAGEFIELD_FORMATS`` setting maps field paths to format specifications: .. code-block:: python IMAGEFIELD_FORMATS = { # Key: "app_label.model_name.field_name" (lowercase) "yourapp.yourmodel.image": { # Value: Dict of format_name -> processor_list "format_name": [processor1, processor2, ...], }, } Each format specification is a list of processors. Processors can be: - **String**: A processor name without arguments, e.g. ``"autorotate"`` - **Tuple**: A processor with arguments, e.g. ``("thumbnail", (800, 600))`` - **"default"**: Shorthand for common processors (autorotate, process_jpeg, process_png, process_gif, preserve_icc_profile) Common processor arguments: - ``("thumbnail", (width, height))``: Resize to fit within bounding box, preserving aspect ratio - ``("crop", (width, height))``: Crop to exact dimensions, centered on PPOI Example: .. code-block:: python IMAGEFIELD_FORMATS = { "blog.post.header_image": { "thumbnail": ["default", ("thumbnail", (400, 300))], "square": ["default", ("crop", (200, 200))], }, } Forms ===== The form widget builds on top of the default Django image field which allows resetting the value of the field; it additionally shows a preview image, and if there's a linked PPOI field, a PPOI picker. The default preview is a max. 300x300 thumbnail. You can customize this by adding a ``preview`` format spec to the list of formats. Customizing the Widget ~~~~~~~~~~~~~~~~~~~~~~ To use a different widget while keeping the preview and PPOI functionality, pass the widget **class** (not an instance) in your ModelForm: .. code-block:: python from django import forms from myapp.models import MyModel class MyModelForm(forms.ModelForm): class Meta: model = MyModel fields = ['image'] widgets = { 'image': forms.FileInput, # ✓ Pass the class, not FileInput() } To completely disable the preview and PPOI widget, override the field entirely with a regular Django ``forms.ImageField``: .. code-block:: python class MyModelForm(forms.ModelForm): image = forms.ImageField() # Replaces the auto-generated field class Meta: model = MyModel fields = ['image'] See test_custom_widget_via_meta_widgets_ and test_custom_widget_without_preview_ in the test suite for working examples. .. _test_custom_widget_via_meta_widgets: https://github.com/feincms/django-imagefield/blob/main/tests/testapp/test_imagefield.py#L538 .. _test_custom_widget_without_preview: https://github.com/feincms/django-imagefield/blob/main/tests/testapp/test_imagefield.py#L520 Django REST Framework ===================== To serialize image fields with their various formats in DRF, use ``SerializerMethodField``: .. code-block:: python from rest_framework import serializers class MyModelSerializer(serializers.ModelSerializer): image_urls = serializers.SerializerMethodField() class Meta: model = MyModel fields = ['id', 'image_urls'] def get_image_urls(self, obj): if not obj.image: return None return { 'thumb': obj.image.thumb, 'desktop': obj.image.desktop, } This returns a dictionary of URLs for each defined format. File Deletion ============= When an image field is cleared or a model instance is deleted, django-imagefield automatically removes all generated/processed images. However, the original uploaded file is not automatically deleted, following Django's default behavior to prevent accidental data loss. If you need to delete original files, you can implement a custom signal handler. See `Django's documentation on signals `_ for details on handling the ``post_delete`` signal. Image processors ================ django-imagefield uses an image processing pipeline modelled after Django's middleware. The following processors are available out of the box: - ``autorotate``: Autorotates an image by reading the EXIF data. - ``process_jpeg``: Converts non-RGB images to RGB, activates progressive encoding and sets quality to a higher value of 90. - ``process_png``: Converts PNG images with palette to RGBA. - ``process_gif``: Preserves transparency and palette data in resized images. - ``preserve_icc_profile``: As the name says. - ``thumbnail``: Resizes images to not exceed a bounding box. - ``crop``: Crops an image to the given dimensions, also takes the PPOI (primary point of interest) information into account if provided. - ``default``: The combination of ``autorotate``, ``process_jpeg``, ``process_gif``, ``process_png`` and ``preserve_icc_profile``. Additional default processors may be added in the future. It is recommended to use ``default`` instead of adding the processors one-by-one. Processors can be specified either using their name alone, or if they take arguments, using a tuple where the first entry is the processors' name and the rest are positional arguments. You can easily register your own processors or even override built-in processors if you want to: .. code-block:: python from imagefield.processing import register # You could also write a class with a __call__ method, but I really # like the simplicity of functions. @register def my_processor(get_image, ...): def processor(image, context): # read some information from the image... # or maybe modify it, but it's mostly recommended to modify # the image after calling get_image image = get_image(image, context) # modify the image, and return it... modified_image = ... # maybe modify the context... return modified_image return processor The processor's name is taken directly from the registered object. An example processor which converts images to grayscale would look as follows: .. code-block:: python from PIL import ImageOps from imagefield.processing import register @register def grayscale(get_image): def processor(image, context): image = get_image(image, context) return ImageOps.grayscale(image) return processor Now include ``"grayscale"`` in the processing spec for the image where you want to use it. The processing context ====================== The ``context`` is a namespace with the following attributes (feel free to add your own): - ``processors``: The list of processors. - ``name``: The name of the resulting image relative to its storages' root. - ``extension``: The extension of the source and target. - ``ppoi``: The primary point of interest as a list of two floats between 0 and 1. - ``save_kwargs``: A dictionary of keyword arguments to pass to ``PIL.Image.save``. The ``ppoi``, ``extension``, ``processors`` and ``name`` attributes cannot be modified when running processors anymore. Under some circumstances ``extension`` and ``name`` will not even be there. If you want to modify the extension or file type, or create a different processing pipeline depending on facts not known when configuring settings you can use a callable instead of the list of processors. The callable will receive the fieldfile and the context instance and must at least set the context's ``processors`` attribute to something sensible. Just as an example here's an image field which always returns JPEG thumbnails: .. code-block:: python from imagefield.processing import register @register def force_jpeg(get_image): def processor(image, context): image = get_image(image, context) context.save_kwargs["format"] = "JPEG" context.save_kwargs["quality"] = 90 return image return processor def jpeg_processor_spec(fieldfile, context): context.extension = ".jpg" context.processors = [ "force_jpeg", "autorotate", ("thumbnail", (200, 200)), ] class Model(...): image = ImageField(..., formats={"thumb": jpeg_processor_spec}) Of course you can also access the model instance through the field file by way of its ``fieldfile.instance`` attribute and use those informations to customize the pipeline. Settings ======== django-imagefield supports a few settings to customize aspects of its behavior. The default settings are as follows: .. code-block:: python # Automatically generate and delete images when saving and deleting models. # Can either be a boolean or a list of "app.model.field" strings. It's # recommended to set this to False for some types of batch processing since # updating the images may slow things down a lot. IMAGEFIELD_AUTOGENERATE = True # The image field doesn't generally need a cache, but it's definitely # useful for admin thumbnails and the versatile image proxy. The timeout # can be configured here. By default, a random duration between 170 and # 190 days is used, so that the cache doesn't expire at the same time for # all images when running several server processes. IMAGEFIELD_CACHE_TIMEOUT = lambda: randint(170 * 86400, 190 * 86400) # See above. IMAGEFIELD_FORMATS = {} # Whether images should be deeply validated when saving them. It can be # useful to opt out of this for batch processing. IMAGEFIELD_VALIDATE_ON_SAVE = True # Errors while processing images lead to exceptions. Sometimes it's # desirable to only log those exceptions but fall back to the original # image. This setting let's you do that. Useful when you have many images # which haven't been verified by the image field. IMAGEFIELD_SILENTFAILURE = False # Add support for instance.image.crop.WxH and instance.image.thumbnail.WxH # An easier path to migrate away from django-versatileimagefield. IMAGEFIELD_VERSATILEIMAGEPROXY = False # How many folders and subfolders are created for processed images. The # default value is 1 for backwards compatibility, it's recommended to # increase the value to 2 or 3. IMAGEFIELD_BIN_DEPTH = 1 Development =========== django-imagefield uses pre-commit_ to keep the code clean and formatted. The easiest way to build the documentation and run the test suite is also by using tox_: .. code-block:: bash tox -e docs # Open docs/build/html/index.html tox -l # To show the available combinations of Python and Django .. _documentation: https://django-imagefield.readthedocs.io/en/latest/ .. _Pillow: https://pillow.readthedocs.io/en/latest/ .. _tox: https://tox.readthedocs.io/ .. _pre-commit: https://pre-commit.com/