Tag Archives: django

Better way to use Django’s SelectMultiple in Google App Engine for a ListProperty

So my last post was a quick hack attempt at this, but I have come up with a much cleaner method of getting a GAE ListProperty field to work with Django Forms (newforms). You simply have to create a new form field called ListPropertyChoice:

from django.newforms.fields import MultipleChoiceField
from appengine_django.models import BaseModel

class ListPropertyChoice(MultipleChoiceField):

    def clean(self, value):
        """ extending the clean method to work with GAE keys """
        new_value = super(ListPropertyChoice, self).clean(value)
        key_list = []
        for k in new_value:
        return key_list

You can then use this in your forms:

from fields import ListPropertyChoice

class Form(djangoforms.ModelForm):
    my_model = ListPropertyChoice(
                        choices=[(m.key(), m.name) for m in db.Query(MyModel)]
    class Meta:
        model = ParentModel

Obviously you can use both the SelectMultiple widget and the CheckboxSelectMultiple widget and this same process could be easily duplicated for ChoiceField:

class GAEChoiceField(ChoiceField):
    def clean(self, value):
        """ extending the clean method to work with GAE keys """
        value = super(GAEChoiceField, self).clean(value)
        return BaseModel.get(value).key()

SelectMultiple for a Google App Engine ListProperty using Django Forms

***EDIT: I recently posted a better way to do this.

I just started with Google App Engine the other day. Man, who could resist a free server environment?

Being a long time Django developer (one year), I was really excited about it because it “runs django”. Now this isn’t really the case because of BigTable, which isn’t a real relational database. However, if you forgo the Django ORM and use GAE models, you can still work with the other things that make Django so awesome: templates and forms. AND, the google models are so similar to Django that you’ll barely even notice the difference.

Ok, this isn’t a full on review GAE with Django, so I’ll cut right to the chase. I’m sure you found this page when you were trying to use django forms to handle the ListProperty model field in your HTML forms. The problem is that things like ModelChoiceField and ModelMultipleChoiceField won’t work because they rely on the Django ORM. For that matter, ChoiceField and MultipleChoiceField won’t work because they return a list of strings, that although they can be the string for a db.key(), they won’t be the Key objects that your new models expect for ListProperty.

So, to get around this I just adjusted the MultipleChoiceField a bit so that it returns the list chosen fields as Key objects:

class Toping(BaseModel):
    name = db.StringProperty(required=True)

class Pizza(BaseModel):
    name = db.StringProperty(required=True)
    toppings = db.ListProperty(db.Key)

    def get_topings(self):
        # This returns the models themselves, not just the keys that are stored in toppings
        return Topping.get(self.toppings)

class PizzaForm(djangoforms.ModelForm):
    toppings = ListPropertyChoice(
                        choices=[(rt.key(), rt.name) for rt in db.Query(Topping)]
    class Meta:
        model = Pizza

from django.newforms.util import ErrorList, ValidationError
from django.newforms.fields import ChoiceField
from django.newforms.widgets import MultipleHiddenInput, SelectMultiple
from appengine_django.models import BaseModel

from appengine_django.serializer.python import smart_unicode
from django.utils.translation import gettext

class ListPropertyChoice(ChoiceField):
    hidden_widget = MultipleHiddenInput

    def __init__(self, choices=(), required=True, widget=SelectMultiple, label=None, initial=None, help_text=None):
        super(ListPropertyChoice, self).__init__(choices, required, widget, label, initial, help_text)

    def clean(self, value):
        Validates that the input is a list or tuple.
        if self.required and not value:
            raise ValidationError(gettext(u'This field is required.'))
        elif not self.required and not value:
            return []
        if not isinstance(value, (list, tuple)):
            raise ValidationError(gettext(u'Enter a list of values.'))
        new_value = []
        for val in value:
            val = smart_unicode(val)
        # Validate that each value in the value list is in self.choices.
        valid_values = set([smart_unicode(k) for k, v in self.choices])
        for val in new_value:
            if val not in valid_values:
                raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
        # These are the only changes to the django MultipleChoice Field
        # we just convert the list of strings to a list of keys
        key_list = []
        for k in new_value:
        return key_list

Now for a quick expaination… The ListPropertyChoice class itself is really just the MultipleChoiceField from Django V0.96. I had to use this because I couldn’t get the latest svn version to work w/ App Engine due to some SafeUnicode issues I haven’t looked into yet. The only real changes to that class are the last four lines which return the list of keys instead of key-strings from the form.

The only other thing that might need explanation is the get_toppings() method that I added to the Pizza model. This is just a shortcut to access the models instead of the keys, as they’re stored in the ListProperty.

Quick image resizing with python

I typically build automatic resizing into any django models that use ImageField by overriding the save function like so:

def save(self):
super(GalleryImage, self).save() # Call the "real" save() method
if self.image:
# now change the size of the image
path = settings.MEDIA_ROOT + self.image
img1 = PIL.open(path)
img2 = PIL.open(path)

size = 600,600
img1.thumbnail(size, PIL.ANTIALIAS)

# create the thumbnail
size = 150, 150
img2.thumbnail(size, PIL.ANTIALIAS)

def get_thumbnail_path(self):
path = settings.MEDIA_ROOT + self.image
return self.convert_path_to_thumbnail(path)

def get_thumbnail_url(self):
path = self.get_image_url()
return self.convert_path_to_thumbnail(path)

def convert_path_to_thumbnail(self, path):

basedir = os.path.dirname(path) + '/'
base, ext = os.path.splitext(os.path.basename(path))

# make thumbnail filename
th_name = base + "_tn"
th_name += ext
return urlparse.urljoin(basedir, th_name)

This ensures that all my images conform to the sizes I need. However, even if the server is automatically resizing the images for me, I don’t want to spend hours uploading huge images only to have them resized. It’s just a waste of bandwidth and time. So, I’ve written a quick python script that resizes all the images in a given directory and places the copies in a new directory called resized:

#! /usr/bin/env python

Resize all the jpg images in a directory
All images will be placed inside a directory called "resized"
This directory must exist
Usage: ./resize.py

import PIL.Image as PIL
import re, os, sys, urlparse

SIZE = 600,600
JPG = re.compile(".*\.(jpg|jpeg)", re.IGNORECASE)

def get_new_path(path):
basedir = os.path.dirname(path) + '/resized/'
base, ext = os.path.splitext(os.path.basename(path))
file = base + ext

return urlparse.urljoin(basedir, file)

image_dir = sys.argv[1]
print "You must specify a directory"

files = os.listdir(image_dir)
for file in files:
if JPG.match(file):
f = image_dir.rstrip("/") + "/" + file
print f
img = PIL.open(f)
img.thumbnail(SIZE, PIL.ANTIALIAS)