DEV Community

loading...
Cover image for Photologue : Nice Image Management for Django with DRF Integration

Photologue : Nice Image Management for Django with DRF Integration

Javid Mougamadou
Simple is better than complex (PEP20)
・4 min read

Concepts

A powerful image management and gallery application for the Django web framework. Upload photos, group them into galleries, apply effects such as watermarks.

Alt Text

Dependencies

  • Django.
  • Pillow.
  • Django-sortedm2m. (Automatically installed with django-photologue)

Installation

The easiest way to install Photologue is with pip; this will give you the latest version available on PyPi:

pip install django-photologue
Enter fullscreen mode Exit fullscreen mode

Getting Started

Prerequisites

Ensure that you have set MEDIA_URL and MEDIA_ROOT in setting.py :

#settings.py

MEDIA_URL = /media/
MEDIA_ROOT = /data/media/
Enter fullscreen mode Exit fullscreen mode

Configuration

1) Add photologue, sortedm2m, django.contrib.sites in INSTALLED_APPS and SITE_ID=1.

#settings.py
SITE_ID = 1

INSTALLED_APPS = [
     ...

    'django.contrib.sites',

    # Django Image Library - Photologue
    'photologue',
    'sortedm2m',
]
Enter fullscreen mode Exit fullscreen mode

2) Apply photologue migrations

python manage.py migrate photologue
Enter fullscreen mode Exit fullscreen mode

3) Register photologue urls in urls.py

urlpatterns = [
    ...
    # Photologue Urls
    path('photologue/', include('photologue.urls', namespace='photologue')),
]
Enter fullscreen mode Exit fullscreen mode

Models

Add the photologue Photo field in your model :

image = models.ForeignKey(
    'photologue.Photo',
    null=True,
    blank=True,
    on_delete=models.SET_NULL,
)
Enter fullscreen mode Exit fullscreen mode

Then apply migrations.

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Example of model :

class BlogPost(models.Model):
    uuid = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        help_text="The uuid4 primary key of the post",
    )
    title = models.CharField(
        max_length=255,
        help_text="The title of the post",
    )
    content = models.TextField(
        blank=True,
        help_text="The content of the post",
    )
    publishers = models.ManyToManyField(
        'auth.User',
        related_name='posts',
        help_text="The maintainers of the pods",
    )
    preview_image = models.ForeignKey(
        'photologue.Photo',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        help_text="The preview image of the appx",
    )

    def __str__(self):
        return f'Appx: {self.name}'
Enter fullscreen mode Exit fullscreen mode

Gallery

You can add a Gallery field which is a M2M of Photo field.

gallery = models.ForeignKey(
    'photologue.Gallery',
    null=True,
    blank=True,
    on_delete=models.SET_NULL,
)
Enter fullscreen mode Exit fullscreen mode

Photo Sizes

Go to the Django Admin Interface, then you can create new PhotoSize instances.
When a Photo instance will be created, an image will be created for each photo sizes with automatic crop and quality compression.

Here is examples of photo sizes :

  • thumbnail (100 x 100)
  • small (320 x 200)
  • medium (640 x 400)
  • large (960 x 600)

Alt Text

Alt Text

Accessors

The base of Photologue is the Photo model. When an instance is created, methods will be automatically added in order to retrieve photos at various photosizes. E.g. if you have an instance of Photo called image, then the following methods will have been added automatically:

preview_image.get_small_url()
preview_image.get_medium_url()
preview_image.get_large_url()
preview_image.get_raw_url()
Enter fullscreen mode Exit fullscreen mode

These can be used in a custom template to display a thumbnail, e.g.:

<a href="{{ preview_image.image.url }}">
    <img src="{{ preview_image.get_raw_url }}" alt="{{ preview_image.title }}">
</a>
Enter fullscreen mode Exit fullscreen mode

Utils

Here is an function that take an image and produce a Photo instance :

from datetime import datetime
from django.utils.text import slugify
from photologue.models import Photo


def create_photo(image):
    title = f'{datetime.now()}'
    slug = slugify(title)
    photo = Photo.objects.create(
        title=title,
        slug=slug,
        image=image,
    )
    return photo

Enter fullscreen mode Exit fullscreen mode

DRF Integration

Mixins

You can define this method create_mixin_image_serializer() which produces a MixinSerializer based on a image field name.

from rest_framework import serializers
from photologue.models import PhotoSize
from collections import OrderedDict


def _get_image_factory(image_field_name, photo_size):
    def get_image(self, obj):
        request = self.context['request']
        if request and hasattr(obj, image_field_name):
            image = getattr(obj, image_field_name)
            if image is not None:
                get_url_method = getattr(image, f'get_{photo_size}_url')
                url = get_url_method()
                return request.build_absolute_uri(url)
        return None
    return get_image


def create_mixin_image_serializer(image_field_name):
    photo_sizes = PhotoSize.objects\
        .filter(id__gte=3)\
        .values_list('name', flat=True)

    class ImageSerializerMixin(serializers.ModelSerializer):
        pass

    ImageSerializerMixin._declared_fields = OrderedDict([
        (f'{image_field_name}_{photo_size}', serializers.SerializerMethodField())
        for photo_size in photo_sizes
    ])

    for photo_size in photo_sizes:
        setattr(
            ImageSerializerMixin,
            f'get_{image_field_name}_{photo_size}',
            _get_image_factory(image_field_name, photo_size),
        )
    return ImageSerializerMixin

Enter fullscreen mode Exit fullscreen mode

Serializers

1) You can instanciate a mixin image serializer with the field preview_image :

PreviewImageSerializerMixin = create_mixin_image_serializer('preview_image')
Enter fullscreen mode Exit fullscreen mode

Otherwise you can manually create the mixin image serializer :

class PreviewImageSerializerMixin(serializers.ModelSerializer):
    preview_image_small = serializers.SerializerMethodField()
    preview_image_medium = serializers.SerializerMethodField()
    preview_image_large = serializers.SerializerMethodField()
    preview_image_raw = serializers.SerializerMethodField()

   def get_preview_image_small(self, obj):
       request = self.context['request']
       if request and hasattr(obj, 'preview_image'):
           image = getattr(obj, 'preview_image')
           if image is not None:
               url = image.get_small_url()
               return request.build_absolute_uri(url)
       return None

   def get_preview_image_medium(self, obj):
       request = self.context['request']
       if request and hasattr(obj, 'preview_image'):
           image = getattr(obj, 'preview_image')
           if image is not None:
               url = image.get_medium_url()
               return request.build_absolute_uri(url)
       return None

   def get_preview_image_large(self, obj):
       request = self.context['request']
       if request and hasattr(obj, 'preview_image'):
           image = getattr(obj, 'preview_image')
           if image is not None:
               url = image.get_large_url()
               return request.build_absolute_uri(url)
       return None

   def get_preview_image_raw(self, obj):
       request = self.context['request']
       if request and hasattr(obj, 'preview_image'):
           image = getattr(obj, 'preview_image')
           if image is not None:
               url = image.get_raw_url()
               return request.build_absolute_uri(url)
       return None

Enter fullscreen mode Exit fullscreen mode

2) Here is an exemple of serializer :

class BlogPostSerializer(PreviewImageSerializerMixin):
    """
    Serializer for BlogPost instance
    """
    class Meta:
        model = BlogPost
        fields = ('uuid', 'title', 'content',
                  'preview_image_small', 'preview_image_medium',
                  'preview_image_large', 'preview_image_raw')

Enter fullscreen mode Exit fullscreen mode

3) Define create() and update() in case of uploading image :

    def create(self, validated_data):
        preview_image = validated_data.pop('preview_image', None)
        instance = super(BlogPostSerializer, self).create(validated_data)

        # Save image field
        if preview_image:
            instance.preview_image = create_photo(preview_image)

        instance.save()
        return instance

    def update(self, instance, validated_data):
          preview_image = validated_data.pop('preview_image', None)
        instance = super(BlogPostSerializer, self).update(instance, validated_data)

        # Update image field
        if preview_image:
            instance.preview_image = create_photo(preview_image)

        instance.save()
        return instance
Enter fullscreen mode Exit fullscreen mode

Notes for Production

You should integrate the photologue library step by step.

1) Install the package.

pip install django-photologue
Enter fullscreen mode Exit fullscreen mode

2) Add photologue, sortedm2m, django.contrib.sites in INSTALLED_APPS

INSTALLED_APPS = [
     ...

    'django.contrib.sites',

    # Django Image Library - Photologue
    'photologue',
    'sortedm2m',
]
Enter fullscreen mode Exit fullscreen mode
python manage.py migrate photologue
Enter fullscreen mode Exit fullscreen mode

3) Apply photologue migrations

python manage.py migrate photologue
Enter fullscreen mode Exit fullscreen mode

4) If you would like to replace an ImageField with a Photo in your models.py, you have to follow theses steps :

  • Remove ImageField in your models.py
  • Apply migrations
  • Add Photo in your models.py
  • Apply migrations

If you would like to keep your images, you should keep two fields:

  • Add Photo in your models.py
  • Apply migrations
  • Instanciates Photo
  • Remove ImageField in your models.py
  • Apply migrations

Links

Discussion (0)