Google AppEngine, Django and File Uploads

Thursday, September 11th, 2008

So I’ve been preparing my Google AppEngine talk for PyConUK, and have struggled with one of my demos because of what appears to be a bug in the AppEngine to Django conversion code.

I want the facility to upload a file, storing it into a Blob field on the datastore.

My model looks like

class Kitten(db.Model):
  index = db.IntegerProperty(default=0)
  name = db.StringProperty(required=True)
  picture = db.BlobProperty()
  votes = db.IntegerProperty(default=0)

Then we create a form called that we can use to hold that data,

class KittenForm(djangoforms.ModelForm):
    class Meta:
      model = Kitten
      exclude = [ 'index' ]

In our add method, we attempt to load the form, and validate it if we have been posted back,

if request.method == "POST":
    # Handle form submission
    form = KittenForm(request.POST, request.FILES)
    if (form.is_valid()):
      # Do stuff
  else:
    form = KittenForm()
  return render_to_response('addkitten.html', {'form': form})

At first we just don’t get a file upload button in our form, this is because Django 0.97.1 doesn’t support FileFields properly, so AppEngine wont map a Blob to one unless you upgrade to django 1.0

Once that’s done, and we’re running AppEngine from subversion as well to get latest code fixes there. when we post to the method, we get an attribute error,

'NoneType' object has no attribute 'validate'

This appears to be caused by the clean_for_property_field calling property_clean.

When the model is created, it sets up a set of property cleaners, one for each field on the model.

The code to do so looks like:

in google/appengine/ext/db/djangoforms.py:
for name, field in model_fields.iteritems():
  prop = props.get(name)
  if prop:
    def clean_for_property_field(value, prop=prop, old_clean=field.clean):
      value = old_clean(value)
      property_clean(prop, value)
      return value
    field.clean = clean_for_property_field

According to my traceback, prop is equal to None when clean_for_property_field is called back by django.

However, it looks to me like what has happened is that the code that calls the above code has changed in how it calls it, passing in a second parameter called initial, but only when it’s a FileField.

if isinstance(field, FileField):
    initial = self.initial.get(name, field.initial)
    value = field.clean(value, initial)
else:
    value = field.clean(value)