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 §
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
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 ' % force_unicode(w) for w in self]))
)
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!
@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.