All Unkept
Posted in: Django, Python, Web development  —  24 June 2008

Django newforms-admin upgrade

I haven't been doing much Django recently, but having finished college I'm enjoying getting back into it, and into actually committing code and fixing bugs again.

The next step was to finally bit the bullet and start the switch to Django newforms-admin branch for the main project I maintain that uses Django. I have to say I'm very impressed.

One of my models used a heavily customised admin page for editing it — so heavily customised, in fact, that I had eventually been forced to copy and modify the change_form.html template, and then copy and paste the entire admin change_stage view function and make tweaks to it. In total, I had these custom needs for one model:

  • Which fields are visible should depend on the user using the form (previously solved with a neat hack that I enjoyed at the time, but am very glad to be rid of!)
  • For the field that is hidden for some users (which is actually the 'ownership' field, a foreign key to User) it should be chosen for them (with no chance of them fixing the field themselves).
  • Normal users need to be able to access the 'Add' form, yet preferably without normally having 'add permission' for that model.
  • Normal users need to be able to change instances they created, but no-one else's (so they don't have 'change permission' either).
  • After editing, users should be redirected back to a custom page, not the 'change list'.
  • Some fields are 'required fields', but the until the user has checked the 'finished' field, these fields are allowed to have null/blank values. (This was previously solved using a decorator like function that wrapped model fields and added 'validators', but validation doesn't work that way in newforms).
  • There is one custom field type, like a CharField, but with input validated to be in the form 'YYYY/MM'.
  • As the widget for 'NullBooleanField's, a 'Yes/No' radio button widget should be used, but one that is nullable without obviously having a 'null' option. (i.e. it only shows 'Yes' and 'No' radio buttons, but you can leave it with neither selected when saving it). These fields also needed to work with the 'required field' functionality above.
  • A timestamp field should be updated whenever the model is added or changed.
  • A special notice needs to be displayed at the bottom of the change form.

Using newforms admin, I was able to implement all of these without duplicating any of admin view code or templates. I did end up with a custom template, but only to support the last requirement, and it simply inherits the normal template and fills in the after_related_objects block. The newforms method provides lots of hooks of different types — custom DB fields, custom formfields, custom widgets, custom form classes, and then the ability to override various parts of the of the view functions. In almost every case, my code mainly just delegates to super(), with some added pre- and post- logic.

Implementing all of this took some time (about a day), but partly that was due to not being very familiar with newforms, and the documentation for newforms admin isn't yet what it could be (I added some). I often found myself getting confused between model fields and form fields — holding on to the difference between these, and then how they relate to forms and widgets, is really important to get your head around these kind of customisations.

Anyway, congrats to Brian, Joseph, Karen and everyone else who's worked on this for the really great changes in this branch, this is excellent stuff, and very exciting.

Comments §

§ On 24 June 2008, Simon Willison wrote:
323 Any chance you might share the code you're describing? Sounds like it would make a great demo app - might even be worth adding to the newforms-admin test suite to guard against regressions that would break the customisation hooks.

§ On 24 June 2008, luke wrote:
324 Simon: there is no problem sharing the code, the problem is writing the tests!

I'm not sure it would be useful to use my code 'as is' as a demo -- some of it is quite involved, and this particular model has loads of fields, far more than would be good for a demo.

But anyway, an up to date snapshot of my code is here:
http://files.lukeplant.fastmail.fm/public/cciw_django_public_2008-06-24.zip

The relevant stuff is in cciw/officers/admin.py,models.py,fields.py,formfields.py,widget.py

§ On 26 August 2008, Mike wrote:
352 I modified your widgets.py to handle the 'Yes'/'No' Radio buttons instead of the checkbox for boolean fields, so that you can modify the formatting... Here's only the added code...


from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode
from django import forms
...
...
    def __getitem__(self, idx):
        choice = self.choices[idx] # Let the IndexError propogate
        return forms.widgets.RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)

    def render(self, name, value, attrs=None, choices=()): # replaces your render
        try:
            value = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[value]
        except KeyError:
            value = u'1'

        self.name, self.value = name, value
        return mark_safe(u'<div %s>\n%s\n</div>' % (
            forms.util.flatatt(self.attrs),
            u'\n'.join([u'%s &nbsp;&nbsp;' % force_unicode(w) for w in self]))
        )

§ On 4 June 2010, Derek wrote:
882 Luke

Can you point where, in the sample code that you have made available, the code for deciding "which fields are visible should depend on the user using the form" is located?

Thanks!

§ On 7 June 2010, luke wrote:
883

@Derek: it's currently here:

https://bitbucket.org/spookylukey/cciw-website/src/tip/cciw/officers/admin.py#cl-164

and also here:

https://bitbucket.org/spookylukey/cciw-website/src/tip/cciw/officers/admin.py#cl-192

It will appear to work with just the first, but won't be secure without the second.


Add comment

Format:

  • Javascript has to be on to get past my spam protection, and cookies, and there is a delay, sorry for any inconvenience!
  • I reserve the right to moderate comments.