Creating a Photo Gallery with Django and jQuery

As part of the Mobile Theme Generator, I had to create a Photo Gallery in order to display the header images. As stated in previous post, I created the Mobile Theme Generator using Django and jQuery Mobile. Although there are probably plenty of Django-based Photo Gallery apps, I thought that it would be easier to integrate a custom Photo Gallery into a generator, so I decided to build it myself.

models.py

The first step in building the Photo Gallery was creating the model. I needed a table that stored the header images and a table to store the image categories:

?View Code PYTHON
from django.db import models
import os
 
def header_image_file_name(instance, filename):
    cat = Image_category.objects.get(id=instance.category.id)
    n = cat.name
    n = n.lower().replace (" ", "_")
    return os.path.join(
      "template_images", "headers", "%s" % n, "large", filename)
 
def header_image_thumbnail_file_name(instance, filename):
    cat = Image_category.objects.get(id=instance.category.id)
    n = cat.name
    n = n.lower().replace (" ", "_")
    return os.path.join(
      "template_images", "headers", "%s" % n, "thumbs", filename)
 
class Image_category(models.Model):
    name = models.CharField(max_length=35)
 
    def __unicode__(self):
        return self.name
 
class Header_image(models.Model):
    category = models.ForeignKey(Image_category)
    image = models.ImageField(upload_to=header_image_file_name)
    thumbnail = models.ImageField(upload_to=header_image_thumbnail_file_name, blank=True,null=True)
 
    def __unicode__(self):
        return str(self.image)
 
class Website(models.Model):
    user = models.ForeignKey(User)
    header_image = models.ForeignKey(Header_image)
    domain_name = models.CharField(max_length=75)
    site_type = models.ForeignKey(Site_type)
    ...

First I define the format of image file name. I want the images to be stored under a relevant category folder. If the category name has a space in it, I replace the space with an underscore (_). The large images will be uploaded to the “large” folder and the thumbnail images will be uploaded to the “thumbnail” folder.

The Image_category table only needs the name of the category. The Header_image table stores the location of the main (large) and thumbnail image as well as the category_id.

The header_image in the Website table stores the primary key (from the Header_image table) for the selected image.

admin.py

Next I created the backend processes to create the categories and upload the images. Fortunately, Django does all of the work for image category interface/save routines. I only have to create the save routine for the header images. I did this by overriding the save_model method.

?View Code PYTHON
import os
from PIL import Image
from cStringIO import StringIO
from django.core.files.base import ContentFile
from sites.models import Image_category
from sites.models import Header_image
from django.contrib import admin
 
class Header_imageAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        tf = self.create_thumbnail(obj)
        obj.save()
 
    def create_thumbnail(self, obj):
        # original code for this method came from
        # http://snipt.net/danfreak/generate-thumbnails-in-django-with-pil/
 
        # If there is no image associated with this.
        # do not create thumbnail
        if not obj.image:
            return
 
        # Set our max thumbnail size in a tuple (max width, max height)
        THUMBNAIL_SIZE = (256,75)
 
        DJANGO_TYPE = obj.image.file.content_type
 
        if DJANGO_TYPE == 'image/jpeg':
            PIL_TYPE = 'jpeg'
            FILE_EXTENSION = 'jpg'
        elif DJANGO_TYPE == 'image/png':
            PIL_TYPE = 'png'
            FILE_EXTENSION = 'png'
 
        # Open original photo which we want to thumbnail using PIL's Image
        image = Image.open(StringIO(obj.image.read()))
 
 
        # We use our PIL Image object to create the thumbnail, which already
        # has a thumbnail() convenience method that contrains proportions.
        # Additionally, we use Image.ANTIALIAS to make the image look better.
        # Without antialiasing the image pattern artifacts may result.
        image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
 
        # Save the thumbnail
        temp_handle = StringIO()
        image.save(temp_handle, PIL_TYPE)
        temp_handle.seek(0)
 
        name = os.path.basename(obj.image.name)
 
        obj.thumbnail.save(name, ContentFile(temp_handle.getvalue()), save=False)
 
        return True
 
admin.site.register(Image_category)
admin.site.register(Header_image, Header_imageAdmin)

The thumbnail image is created/uploaded before the main image is uploaded/saved. The thumbnail is created using Python’s Image Library (PIL).

After the code is in place, I logged into the admin of MobilePebbles and created the categories. I then created the image records.

forms.py

Next, I created the model form. One of the purposes of the form is to store the id of the selected image in a hidden field.

?View Code JAVASCRIPT
from django import forms
from django.forms import widgets
from sites.models import Website
 
class WebSiteForm(forms.ModelForm):
    class Meta:
        model = Website
        exclude = ('user', 'updated', 'site_type', 'mobile_type', 'generated')
        widgets = {
           ...
           'header_image' : forms.HiddenInput()
        }

views.py

I then created the view for the page. Both the add and edit methods are similar so I will just display the add method.

?View Code PYTHON
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.template.context import RequestContext
from django.contrib import messages
 
def add(request, user, site_type):
    if request.method == 'POST': # If the form has been submitted...
        data = {
                ...
                'header_image': request.POST.get('header_image'), 
                ...
        }
        websiteForm = WebSiteForm(data)
        if websiteForm.is_valid(): # All validation rules pass
            wbf = websiteForm.save(commit=False)
            wbf.user = request.user
            wbf.site_type_id = site_type
            wbf.mobile_type_id = 1
            wbf.save()  
        else:
            messages.error(request, websiteForm.errors)
 
        return HttpResponseRedirect('/sites/'+str(request.user.id))
    else:
        imageCat = Image_category.objects.exclude(id=0).order_by('name')
        websiteForm = WebSiteForm(initial={'domain_name': 'GrasshopperPebbles.com', 'footer_copyright': 'Copyright © 2013 MobilePebbles.com. All rights reserved.'})
    return render_to_response('wordpress/jquery/add_update.html', { 'websiteForm': websiteForm, 'imageCat': imageCat, 'action': 'add', 'id': '0', 'site_type': '1'}, context_instance=RequestContext(request))

If the request method is “POST”, I save the form data. Else, I displaying the page and pass the form and the image categories.

The Template

Next, I created the template for the page. For the Mobile Theme Generator, the Photo Gallery is displayed in a jQuery Mobile Dialog, but the end result would be the same if I displayed the Photo Gallery on the main page. When the user clicks on the btn-header-image-options button the photo gallery dialog will display.

<div class="ui-block-b"><a id="btn-header-image-options" href="#header-image-gallery-dialog" data-rel="dialog" data-role="button" data-position-to="window" data-transition="pop" data-inline="true" data-mini="true">Choose Image</a></div>
 
<div data-role="dialog" id="header-image-gallery-dialog">
    <div data-role="header" data-theme="b">
        <h1>Header Images</h1>
    </div>
    <div data-role="content">
    	<select id="image_categories">
    		<option value="0">Select Category...</option>	
		{% for cat in imageCat %}
			<option value="{{ cat.id }}">{{ cat.name }}</option>
		{% endfor %}
		</select>
		<div id="thumb-container"></div>
		<p><strong>Note:</strong> Click Remove if you no longer want to display a header image.</p>
	</div>	
    <div data-role="footer">
        <fieldset class="ui-grid-a">
			<div class="ui-block-b"><a id="btn-header-image-dlg-remove" data-role="button" data-inline="true" data-mini="true" data-theme="a">Remove</a></div>
			<div class="ui-block-c"><a id="btn-header-image-dlg-cancel" data-role="button" data-inline="true" data-mini="true" data-theme="a">Cancel</a></div>	   
		</fieldset>        
    </div>
</div>
<input id="media_url" type="hidden" value="{{ MEDIA_URL }}" />
{{ websiteForm.header_image }}

I created a category drop-down. When the user selects a category, the thumbnail images will be retrieved ajaxally. Also, notice the hidden field header_image. This will store the id of the selected image. I also created another hidden field media_url. jQuery will use the MEDIA_URL to display the images.

Be Sociable, Share!

Checkout My New Site - T-shirts For Geeks