Software | Web | Project Management

Using Images in Django

This is another tutorial style write-up of my exploration of django features. I build a basic gallery style app and look at django-imagekit

I have a long standing photography website hosting thousands of images I have collated from various holidays and treks through the countryside. It's based on the Gallery Project, which is a PHP based app with an impressive array of features. Sadly, after quite a bit of inactivity, Gallery has been discontinued. This seemed like a good time to look at beginning one in Django. There are plenty available already so, don't reinvent the wheel if you're looking for an out of the box solution.

Models

To store images, Django provides an ImageField and FileField for storage. The contents of uploaded files are stored in the filesystem, so they require setting up a MEDIA_ROOT and MEDIA_URL if you don't have them already. For a gallery I really need server side resizing though, as I don't want to load full size images, especially for thumbnails. This can be achieved through a library like PIL/Pillow, but django-imagekit already conveniently provides this feature, alongside other configurable processors

I will categorise my Images into Albums, to group them in a meaningful way. Images can be tagged, and each album has an optional 'highlight' image, which will be the one used on its thumbnail.


from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit

# Image resize defaults
thumbnail_size = 200
preview_size = 1000

# Create your models here.
class Tag(models.Model):
    name = models.CharField(max_length=250)

    def __str__(self):
        return self.name

class Image(models.Model):
    title = models.CharField(max_length=250)
    data = models.ImageField(upload_to='images')
    data_thumbnail = ImageSpecField(source='data',
                                      processors=[ResizeToFit(width=thumbnail_size,height=thumbnail_size)],
                                      format='JPEG',
                                      options={'quality': 60})
    data_preview = ImageSpecField(source='data',
                                      processors=[ResizeToFit(width=preview_size,height=preview_size)],
                                      format='JPEG',
                                      options={'quality': 60})
    date_uploaded = models.DateTimeField(auto_now_add=True)
    tag = models.ManyToManyField(Tag, blank=True)

    def __str__(self):
        return self.title

class Album(models.Model):
    title = models.CharField(max_length=250)
    images = models.ManyToManyField(Image, blank=True)
    highlight = models.OneToOneField(Image,
                                     related_name='album_highlight',
                                     null=True, blank=True,
    )
    def __str__(self):
        return self.title

The ImageSpecField is provided by django-imagekit, and enables us to configure the processor and the output of the image data. In this case, as simple resize with a lowish quality jpeg for quick loading. These are generated on the fly, and then cached in the media folder, so you may notice a short delay the first time they are loaded.

These fields can be accessed in the same way as an image field, for example:

<img src="{{ image.data_preview.url }}" title="{{ image.title }}">

There is also a an additional shortcut tag provided called generateimage, but I needed a bit more flexibility than this.

Views

For convenience when creating an album I want to display a preview image for each even if no highlight is created. I overrode the queryset in the Album ListView to replace the otherwise empty highlight with the first image in the album


from django.views.generic import ListView
from gallery.models import Album

class AlbumList(ListView):
    model = Album
    template_name = 'gallery/album_list.html'

    def get_queryset(self):
        # Return a list of albums containing a highlight even if none is selected
        album_list=[]
        for album in super(AlbumList, self).get_queryset():
            # if there is no highlight but there are images in the album, use the first
            if not album.highlight and album.images.count():
                first_image = album.images.earliest('id')
                album.highlight = first_image
            album_list.append(album)
        return album_list

This approach saves entering any logic into the template. The queryset is iterated through and if a highlight is missing, it will be added in. The first element in a queryset is available from earliest().

The other feature I wanted was a thumbnail list of other images in the same album. I changed my image DetailView to make these images available for display. An image can below to more than one album, so we need to know if an Image is being displayed in the context of another album. This involved putting two parameters in the url pattern


urlpatterns = patterns('',
...
    url(r'^image/(?P\d+)/$', ImageView.as_view(), name='image_detail'),
    url(r'^album/(?P\d+)/image/(?P\d+)/$', ImageView.as_view(), name='album_image_detail'),

)

This allows the ImageView to be called on its own, or in the context of an album, when the 'apk' will also be available in the kwargs.

I then added an additional sequence to the ImageView context data, for easy access in the template


from django.views.generic import DetailView
from gallery.models import Image, Album

class ImageView(DetailView):
    model = Image
    
    def get_context_data(self, **kwargs):
        context = super(ImageView, self).get_context_data(**kwargs)
        context['album_images'] = []
        context['apk'] = self.kwargs.get('apk')
        # If there is an album in the context, look up the images in it
        if context['apk']:
            for album in Album.objects.filter(pk=context['apk']):
                context['album_images'] = album.images.all
        return context

The album is looked up using filter(), and the hopefully single record returned can then be used to return all its images with the all() method

GitHub

Take a look at the full code on GitHub and the online demo on the Starcross site

Add a comment

captcha