Using custom widgets with Django's newforms-admin

tags python,django

The following isn't magic but it was unclear to me and required reading both documentation and source code and some additional Googling to get right. Maybe that's because I'm a Django newbie, but hey, I'm probably not the only one. By the way, the following applies to Django SVN revision 8068. That's roughly Django 1.0 alpha.

Anyway, I have in my model a field that's basically a time-stamped boolean: a field called deleted of type DateTimeField. If it's NULL, the thing, let's call it Foo, is not deleted; if it has a value, it tells us the Foo in question was marked as deleted back then. It's not the only way I could have implemented it but it meets my requirements and I didn't want to change it.

How to display that in admin? I could have left it as just a split datetime field, but that doesn't really communicate the intent. A checkbox with additional text telling the date is a much better representation.

I found some help in in Stuart Langridge's Overriding a single field in the Django admin, using newforms-admin, but it seems it's slightly outdated and didn't contain all the details.

Turns out for this to work well, I needed three bits: a widget class, a field class and a formfield_for_dbfield method in my ModelAdmin class.

Here's my widget class:

class BooleanDateWidget(forms.CheckboxInput):
    def __init__(self, attrs=None, check_test=lambda v: v is not None):
        super(BooleanDateWidget, self).__init__(attrs, check_test)

    def render(self, name, value, attrs=None):
        final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
        try:
            result = self.check_test(value)
        except: # Silently catch exceptions
            result = False
        if result:
            final_attrs['checked'] = 'checked'
            dt = ' <label for="%s" class="vCheckboxLabel">(%s)</label>' % (final_attrs["id"], value)
        else:
            dt = ""
        if value not in ('', True, False, None):
            # Only add the 'value' attribute if a value is non-empty.
            final_attrs['value'] = force_unicode(value)
        return mark_safe(u'<input%s />%s' % (flatatt(final_attrs), dt))

The render method has mostly been copied and pasted from django.forms.CheckboxInput.render, with a few modifications to create an additional label. When database value is rendered to the admin form, that method gets called. If deleted is NULL, it creates an empty checkbox; if there's a date there, it creates a checked checkbox with an additional label that contains the timestamp (not very prettily formatted, though.)

Next, the field class:

class BooleanDateField(fields.BooleanField):
    widget = BooleanDateWidget

    def clean(self, value):
        v = super(BooleanDateField, self).clean(value)
        if v:
            return datetime.datetime.now()
        return None

That's pretty simple. I just specify the widget to use and make the clean method which is called when the value sent by the browser is validated for storage in the model return either None if the checkbox was checked or None otherwise.

And finally the last piece is formfield_for_dbfield method in the ModelAdmin class.

class FooAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name == 'deleted':
            field = db_field.formfield(form_class=booleandate.BooleanDateField)
        else:
            field = super(FooAdmin,self).formfield_for_dbfield(db_field,**kwargs)
        return field

That method is invoked when the newforms-admin interface needs the field object for displaying and handling the deleted value. It checks the field name to specialize the interface. For the deleted class, we call the db_field object's formfield method, otherwise we delegate to ModelAdmin. ModelAdmin's formfield_for_dbfield is pretty simple itself, it mostly just sets the widget type for a few field types and calls the formfield method of the db_field object. That's what we could have done here, too; instead of specifying the widget type in the Field class, we could have done it here.

That's all it takes! With those modifications, the split datetime field disappears and instead you get a datetime-labeled checkbox.

Article page

A moment of C++ hate

Apologies, I'm going to indulge myself for a moment. If you aren't interested in C++ ranting, skip this.

I'm in the process of converting some C++ code to heap allocate objects instead of putting them in the stack, because I need to use them in Objective-C++ and stack-allocated objects aren't the best idea there.

Who in their right mind wants to spend programming time worrying about where to allocate objects? Why do I have to care? There's an actual problem domain here with business objects, and I'm twiddling object allocation. I feel like bashing my head against the keyboard.

Article page

JUGC 0.2 with Python support

I just released version 0.2 (and 0.2.1, now with a NEWS file!) of JUGC, the unit conversion library generator. It allows you to specify a set of measuring units and translations in XML files and generate a conversion library from them, thus avoiding parsing a definition file at startup. The generator portion is still written in Java, but now in addition to Java, it can also generate Python code. There are small examples on the Examples wiki page. There isn't much documentation, but the example data files and tests should be clear enough.

Article page

pgrok

I put up pgrok ("Project Grokking for Emacs"), a simple Elisp package for project settings and tools, on Launchpad. It's a tidied up version of a couple of things I've been using for ages, basically for loading project (a source tree) settings, functions or whatever you need from files that are looked up in your directory hierarchy. There are also two (at the moment) functions for locating stuff in your project with find and grep.

There are no releases yet, but it's just one file in the bzr branch hosted on Launchpad. I use GNU Emacs 23.0.60, no guarantees about any other Emacsen.

Article page

Software Engineering Radio

They keep on asking their listeners to plug them, so here goes. I've been listening to podcasts quite a bit lately, as I got a phone that can do them and my commute is too short to really concentrate on reading articles, and the best of the ones I regularly follow is Software Engineering Radio. There's a lot of backlog to go through and at least the interviews they've done with Joe Armstrong, Galen Hunt and Dave "not PragDave" Thomas have been really nice.

Article page

AudioFormat 0.6

Waking the project up, at least momentarily, from a nine-month slumber, I released version 0.6 of AudioFormat, the friendly audio file format converter for the GNOME desktop.

I've again found more use for the software and discovered that it was in several ways still embarassingly broken, from refusing to install on Python versions with micro version numbers (like 2.5.1) to still continuing conversions after clicking cancel, even without the window, when using as a Nautilus extension.

It should be at least a bit better now.

Article page