This tutorial will show how you can build a working photo gallery for your django-powered website.
Requirements
I assume that you have a Python, PIL, Django 0.96 and django-tagging running and working properly.
First of it all create “gallery” and “utils” applications:
$ python manage.py startapp gallery
$ python manage.py startapp utils
You need to have the current user’s info outside requests, have a look at CookBookThreadlocalsAndUser and add the middleware in your “MIDDLEWARE_CLASSES” settings.
Now you must have custom upload fields and template filters. Download “fs.py” and “imaging.py” from CustomUploadAndFilters and place it into your “utils” application directory. Create the templatetags module directory and put inside it the “image_filters.py” file containing the code shown in the wiki page and adjust the “import” statements:
# Adjust your paths to ‘imaging’ and ‘fs’
from myproject.utils.imaging import fit, fit_crop
from myproject.utils.fs import add_to_basename
And add both applications into your “settings.py”:
INSTALLED_APPS = (
… CUT …
‘myproject.utils’,
‘myproject.gallery’,
… CUT …
)
Models
Let’s have a look at the gallery’s models:
from django.db import models
from django.db.models import permalink
from django.conf import settings
from django.contrib.auth.models import User
from myproject.middleware import threadlocals
from myproject.utils.fields import AutoImageField
from myproject.tagging.fields import TagField
from myproject.utils.templatetags.image_filters import thumb
class GalleryManager(models.Manager):
def get_query_set(self):
return super(GalleryManager, self).get_query_set().filter(published=True)
class Gallery(models.Model):
title = models.CharField(maxlength=75)
slug = models.SlugField(maxlength=75, prepopulate_from=(‘title’,), unique=True)
description = models.TextField(blank=True)
published = models.BooleanField()
author = models.ForeignKey(User, blank=True,
help_text=’Keep blank, it will be set to current user’)
admin_objects = models.Manager()
objects = GalleryManager()
def thumb(self):
try:
photo = Photo.objects.filter(gallery=self.id)[0]
except IndexError:
pass
else:
return photo.image
def get_absolute_url(self):
return (‘myproject.gallery.views.photo_list’, None, {‘slug’: self.slug})
get_absolute_url = permalink(get_absolute_url)
def __str__(self):
return self.title
def save(self):
if not self.id:
self.author = threadlocals.get_current_user()
super(Gallery, self).save()
class Meta:
verbose_name_plural = ‘Gallery’
class Admin:
list_display = (‘__str__’, ‘author’, ‘published’,)
class PhotoManager(models.Manager):
def get_query_set(self):
return super(PhotoManager, self).get_query_set().filter(gallery__published=True)
class Photo(models.Model):
gallery = models.ForeignKey(Gallery)
image = AutoImageField(max_width=760, help_text=’Automatically resized’)
title = models.CharField(maxlength=75)
slug = models.SlugField(maxlength=75, prepopulate_from=(‘title’,), unique=True)
description = models.TextField(blank=True)
posted = models.DateTimeField()
tags = TagField(blank=True)
admin_objects = models.Manager()
objects = PhotoManager()
def __str__(self):
return self.title
def _image_repr(self):
return ‘<img src=”%s” />’ % thumb(self.image, ‘width=220’)
_image_repr.short_description = “Image”
_image_repr.allow_tags = True
class Admin:
list_display = (‘_image_repr’, ‘__str__’, ‘gallery’, ‘posted’)
list_per_page = 10
class Meta:
ordering = (‘-posted’,)
get_latest_by = ‘posted’
“Gallery” and “Photo” models are related by a “ForeignKey” field, I didn’t use a many-to-many relationship because I prefer to use tags to categorize gallery photos between different galleries.
Gallery Model
I use a custom manager for the Gallery model that returns only published galleries:
class GalleryManager(models.Manager):
def get_query_set(self):
return super(GalleryManager, self).get_query_set().filter(published=True)
I also created a “thumb” method to get the first related photo image:
def thumb(self):
try:
photo = Photo.objects.filter(gallery=self.id)[0]
except IndexError:
pass
else:
return photo.image
The “get_absolute_url” method uses the permalink decorator, that allows to generate URLs for model instances reading the URL conf of your project. Change the first argument of the returned tuple according to your project name:
def get_absolute_url(self):
return (‘myproject.gallery.views.photo_list’, None, {‘slug’: self.slug})
get_absolute_url = permalink(get_absolute_url)
Photo model
“Photo” model uses a custom manager too and it returns only published gallery’s photos:
class PhotoManager(models.Manager):
def get_query_set(self):
return super(PhotoManager, self).get_query_set().filter(gallery__published=True)
“Photo” image is an “AutoImageField” field, you may change the “max_width” or add the “max_height” keyword argument:
image = AutoImageField(max_width=760, help_text=’Automatically resized’)
Photos are uploaded into a “photo” directory inside your media directory, it must be writeable by the process that is running Django. You should change the upload directory setting the “upload_to” keyword argument.
Photos are categorized using the django-tagging generic application, you may change your import statement if you have installed it instead of having a checkout in your project directory.
The following code allows to have thumbnails in your admin list page using the “thumb” template filter:
def _image_repr(self):
return ‘<img src=”%s” />’ % thumb(self.image, ‘width=220’)
_image_repr.short_description = “Image”
_image_repr.allow_tags = True
class Admin:
list_display = (‘_image_repr’, ‘__str__’, ‘gallery’, ‘posted’)
list_per_page = 10
Passing a string to the “thumb” method is dirt, but both “crop” and “thumb” are both template filters, it could be useful to rewrite some lines of code to have a much cleaner code.
Views
I am using a custom view to get photos related to the selected gallery:
from django.http import Http404
from django.views.generic.list_detail import object_list
from models import Gallery
def photo_list(request, gallery_slug, *args, **kwargs):
try:
gallery = Gallery.objects.get(slug=gallery_slug)
except Gallery.DoesNotExist:
raise Http404
kwargs[‘queryset’] = gallery.photo_set.all()
if not kwargs.has_key(‘extra_context’):
kwargs[‘extra_context’] = {}
kwargs[‘extra_context’].update({‘gallery’: gallery})
return object_list(request, *args, **kwargs)
The django database api allows to get the “QuerySet” of the related model using the “photo_set” attribute, which is passed to the generic “object_list” view as extra argument. I didn’t add the “object_detail” generic views because I am using the ThickBox javascript widget to show photos directly from the photo listing pages.
Urls
This is the “urls.py” file for the gallery app:
from django.conf.urls.defaults import *
from django.views.generic.list_detail import object_list
from tagging.views import tagged_object_list
from views import photo_list
from models import Gallery, Photo
gallery_list_dict = {
‘queryset’: Gallery.objects.all(),
‘template_object_name’: ‘gallery’,
}
photo_list_dict = {
‘template_object_name’: ‘photo’,
}
tags_dict = {
‘model’: Photo,
‘template_name’: ‘gallery/tagged_photo_list.html’,
}
urlpatterns = patterns(”,
(r’^$’, object_list, gallery_list_dict),
(r’^gallery/(?P<gallery_slug>[-\w]+)/$’, photo_list, photo_list_dict),
(r’^(?P<tag>\w+)/$’, tagged_object_list, tags_dict),
)
Add the following url configuration to your project “urls.py”:
urlpatterns = patterns(”,
… CUT …
(r’^photos/’, include(‘myproject.gallery.urls’)),
… CUT …
)
You may need to change the prefix of your “urlpatterns” to fit your needs.
Templates
“base_gallery.html”
{% extends “base.html” %}
{% block htmlhead %}
<link rel=”stylesheet” href=”{{ media_url }}css/thickbox.css” type=”text/css” media=”screen” />
<script type=”text/javascript” src=”{{ media_url }}js/jquery.js”></script>
<script type=”text/javascript” src=”{{ media_url }}js/jquery.thickbox.js”></script>
{% endblock %}
{% block title %}Photo Gallery{% endblock %}
{% block headtitle %}Photo Gallery{% endblock %}
{% block content %}
{% endblock %}
“gallery/gallery_thumbnail.html”
{% load image_filters %}
<div class=”thumbnail”>
{% if gallery.thumb %}
<p>
<a href=”{{ gallery.get_absolute_url }}” title=”{{ gallery }}”><img src=”{{ gallery.thumb|thumb:”width=120” }}” alt=”{{ gallery }}” /></a>
</p>
{% endif %}
<p>
<a href=”{{ gallery.get_absolute_url }}” title=”{{ gallery }}”>{{ gallery }}
</a>
</p>
</div>
“gallery/photo_thumbnail.html”
{% load image_filters %}
<div class=”thumbnail”>
<p>
<a href=”{{ media_url }}{{ photo.image }}” title=”{{ photo.title }}” class=”thickbox” rel=”photo-gallery”><img src=”{{ photo.image|thumb:”width=120” }}” alt=”{{ photo.title }}” /></a>
</p>
<p>
Gallery: <a href=”{{ photo.gallery.get_absolute_url }}” title=”{{ photo.gallery }}”>{{ photo.gallery }}</a><br />
{% if photo.tags %}Tags: {{ photo.tags }}<br />{% endif %}
</p>
</div>
“gallery/gallery_list.html”
{% extends “base_gallery.html” %}
{% block content %}
{% for gallery in gallery_list %}
{% include “gallery/gallery_thumbnail.html” %}
{% endfor %}
{% endblock %}
“gallery/photo_list.html”
{% extends “base_gallery.html” %}
{% block title %}Photo Gallery: {{ gallery }}{% endblock %}
{% block headtitle %}Photo Gallery: {{ gallery }}{% endblock %}
{% block content %}
{% for photo in photo_list %}
{% include “gallery/photo_thumbnail.html” %}
{% endfor %}
{% endblock %}
“gallery/tagged_photo_list”
{% extends “base_gallery.html” %}
{% block title %}Gallery photos tagged with "{{ tag }}"{% endblock %}
{% block headtitle %}Gallery photos tagged with "{{ tag }}"{% endblock %}
{% block content %}
{% for photo in object_list %}
{% include “gallery/photo_thumbnail.html” %}
{% endfor %}
{% endblock %}
That’s all. Please let me know if you have any question and if you find any mistake, I really appreciate your feedback.