Overriding a single field in the Django admin, using newforms-admin

Django has been gradually changing the way their automatically-created admin system works to use the newforms-admin code, which makes lots of cool new things possible. However, because newforms-admin is rather new (ha!), it’s not brilliantly documented. One of the things I wanted to do today was to make one field use a custom field-editing widget that I’d created, rather than Django’s default textbox, in the Django admin system. You do that like this. In newforms-admin, you specify admin options for a model by creating an extra ModelAdmin class for it:

class Vehicle(models.Model):
  colour = models.CharField()
  name = models.CharField()

class VehicleAdmin(admin.ModelAdmin):
  search_fields = ["name", "colour"]

admin.site.register(Vehicle, VehicleAdmin)

Imagine that we wanted to build a custom widget to allow people to choose a colour by clicking on a colour swatch. To do this, you need to actually create your custom widget. So, in a file custom_widgets.py, you create your widget. The easiest way to do this is to subclass one of the existing widgets (TextInput is a good one here, because that’s a normal textbox, which is what gets used by default for CharFields) and then change its render method:

import django.newforms as forms
from string import Template
from django.utils.safestring import mark_safe

class ColourChooserWidget(forms.TextInput):
  def render(self, name, value, attrs=None):
    tpl = Template(u"""<h1>There would be a colour widget here, for value $colour</h1>""")
    return mark_safe(tpl.substitute(colour=value))

There are a few interesting wrinkles in there. First, overriding render() changes the HTML that your widget prints when asked to display itself by the admin system. (I haven’t actually implemented the widget there; left as an exercise for the reader, that bit.) Second, you need to call mark_safe() on the HTML you return, otherwise the admin will escape it. Third, all input to mark_safe() must be Unicode, hence the u""" at the beginning of the string. The parameter value already is Unicode, but any strings you provide must also be explicitly Unicode strings; otherwise, mark_safe() fails silently — the string will be escaped. Fourth, you don’t have to use string.Template, but it’s pretty convenient. Once you’ve created your custom widget, you have to hook it up to your model. In our example, we need to change VehicleAdmin:

from custom_widgets import ColourChooserWidget


class VehicleAdmin(admin.ModelAdmin):
  search_fields = ["name", "colour"]
  def formfield_for_dbfield(self, db_field, **kwargs):
    if db_field.name == 'colour':
      kwargs['widget'] = ColourChooserWidget
    return super(ArticleOptions,self).formfield_for_dbfield(db_field,**kwargs)

The formfield_for_dbfield() function gets called for each of the fields in your model; for the one we care about (colour in this example), override widget in the kwargs and then carry on with the rest of the function, and that hooks it up. That should be it; now, in the Django admin system, your colour field will use your ColourChooserWidget for editing.

I'm currently available for hire, to help you plan, architect, and build new systems, and for technical writing and articles. You can take a look at some projects I've worked on and some of my writing. If you'd like to talk about your upcoming project, do get in touch.

More in the discussion (powered by webmentions)

  • (no mentions, yet.)