Software | Web | Coding
Using Images in Django
Update: I have now expanded the gallery package to include many new features and released it on PyPI as django-starcross-gallery. The version on github is the full version instead of the basic one described below
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
Take a look at the full code on GitHub and the online demo on the Starcross site
Good job. thank you.
wohaaa, impressive !! good job and thx for sharing :))