I'm really excited about the changes made so far in this branch. In a
nutshell, the way admin options are specified has gotten *much* more
flexible, resulting in easier and more powerful customization of the
Django admin site. A model's "class Admin" now has new hooks, such as
has_add_permission(), that let you define arbitrary behavior. I've
also taken the opportunity to refactor some admin-specific options --
raw_id_admin and prepopulate_from -- so that they're defined in the
"class Admin" rather than in the model fields themselves. To see which
hooks have been implemented, see the class ModelAdmin in
django/contrib/admin/options.py in the newforms-admin branch.
The admin site, using this branch, should be completely functional
according to my tests -- except for "edit inline" fields. If you're
not using "edit_inline" in your models, I encourage you to check out
the branch and take it for a spin, reporting any bugs. Information on
getting the branch is here:
http://code.djangoproject.com/wiki/NewformsAdminBranch
Regarding "edit inline," a couple of decisions need to be made. Ticket
#2248 (http://code.djangoproject.com/ticket/2248) has an interesting
suggestion: you define the inline stuff within the admin class. I'll
copy and paste from that ticket:
class Admin:
inline_models = (
{'model':'Child',
'type':models.TABULAR,
'min_num_in_admin':3,
'max_num_in_admin':20,
'num_extra_on_change':3,
'fields':('name','age',)
},
{'model':'Job',
'type':models.STACKED,
'min_num_in_admin':1,
'max_num_in_admin':3,
'fields':('company','salary',)
}
)
I agree this approach is a huge improvement over the current syntax,
but I wonder whether it can be expanded even more. Instead of
dictionaries, let's use objects:
from django.contrib.admin import TabularInline, StackedInline
# ...
class Admin:
inlines = (
TabularInline('Child', min=3, max=20, extra=3,
fields=('name', 'age')),
StackedInline('Job', min=1, max=3,
fields=('company', 'salary')),
)
This infastructure would let you create your own inline types by
implementing an Inline subclass.
What sort of hooks would an Inline class need? And are there any
thoughts or other ideas/proposals while we're focusing on this?
Adrian
--
Adrian Holovaty
holovaty.com | djangoproject.com
Kudos on these changes (& newforms, of course). I really like that
admin is now as "legit" as regular forms for public views - very
efficient & flexible. Very DRY!
> from django.contrib.admin import TabularInline, StackedInline
>
> # ...
>
> class Admin:
> inlines = (
> TabularInline('Child', min=3, max=20, extra=3,
> fields=('name', 'age')),
> StackedInline('Job', min=1, max=3,
> fields=('company', 'salary')),
> )
>
Adrian, that is a phenomenal idea. Succinct and powerful.
Just one q: How would you specify the ordering of these displayed
fields vs. others? e.g. can you say 'field1', 'field2', inline[0],
'field3', inline[1] etc.
-rob
Woah... inline that is specified by objects that can be subclasses?
I'll have to wrap my head around that one.
Would it be possible to nest inlines? This is one limitation we
sometimes bump up against since our data model spans more than a few
relationships sometimes, and it would be nice to have greater inline
depth.
For example, if you have:
class Kingdom(models.Model):
#
class Admin:
inlines = (
StackedInline('Phylum')
)
class Phylum(models.Model):
kingdom = models.ForeignKey(Kingdom)
class Admin:
inlines = (
StackedInline('Class')
)
class Class(models.Model): # ignore the reserved word :)
phylum = models.ForeignKey(Phylum)
class Admin:
inlines = (
StackedInline('Order')
)
etc...
Could that work and span all those relationships, following the
relationships as it goes.
-Rob
with this change to the raw_id_admin, what is the recommended approach,
when i want to use a ChangeManipulator (the automatic one generated from
the model), but i want to do it the raw_id_admin way, otherwise the
Manipulator loads in every possibly-related object, when doing the
validation.
up to now i simply added raw_id_admin = True to the model's field, but
now i do not know how it should be handled...i realize that the
new-admin is probably using the newforms... and i do not know how
raw_id_admin is handled there
gabor
I was thinking of it a bit... My first solution was to create a custom
manipulator that was switching off those huge <select>s with 'follow'.
In newforms this can be achieved by specifying for ForeignKeys a simpler
widget like HiddenInput. But the main problem with this solution is that
you need to create a new form subclass just to say "I won't display this
field" which is 1) an overkill and 2) doesn't work in cases where form
is created automatically (generic views).
I have a better proposal. We can just defer getting 'choices' for
<select> until 'render' is called. Thus if the field is never displayed
in template it won't hit database. I'm not sure how to do it properly
though... I see two ways now:
- Turn Field.get_choices into a generator. It now actually fetches rows
and wraps them into a list and adds a 'blank' choice.
- Allow a callable in 'choices' param of a Select widget that will be
called on 'render'. It then can be passed as lambda calling form's choices.
I better like first option. What do you think?
One suggestion that I kind of like is the ability to pass a QuerySet
to 'choices' and, in that case, have it generate the choices from,
say, the id and __str__ of each object in the QuerySet.
Since it doesn't actually need those values until render time, it
could take advantage of the laziness of QuerySet evaluation to avoid
hitting the DB at all when the field is never shown.
--
"Bureaucrat Conrad, you are technically correct -- the best kind of correct."
+1 to the general idea. I've always been concerned about the idea of
having form definitions in the field.
> from django.contrib.admin import TabularInline, StackedInline
>
> # ...
>
> class Admin:
> inlines = (
> TabularInline('Child', min=3, max=20, extra=3,
> fields=('name', 'age')),
> StackedInline('Job', min=1, max=3,
> fields=('company', 'salary')),
> )
>
> This infastructure would let you create your own inline types by
> implementing an Inline subclass.
This is possibly nitpicking on a quick-and-dirty example - but
shouldn't the Inline definitions be tied to a m2m/m2o field name,
rather than the model name?
class Admin:
inlines = {
'children': TabularInline(min=3, max=20, extra=3,
fields=('name', 'age')),
'jobs': StackedInline(min=1, max=3,
fields=('company', 'salary')),
}
That way there are no difficulties if there are multiple m2m realtions
on the same model.
> What sort of hooks would an Inline class need? And are there any
> thoughts or other ideas/proposals while we're focusing on this?
This is possibly what you already have in mind - but Is there an
intersection here between inline fields and recursive form
definitions?
form = form_for_model(Person, fields=('name','age'),
inlines={ 'children': TabularInline(... })
The *Inline classes need not just be data containers - they could
contain all the rendering information for handling their own display,
and the display of the subforms for each related object - and if this
is the case, they can be used by end users on their own forms.
Yours,
Russ Magee %-)
Then you'll loose a 'blank' choice. Current Field.get_choices does a bit
more than just iterating a queryset but as far as I can tell it can
easily be made lazy by turning it into a generator: instead of
concatenating two actual lists just yield them by items.
The first option sounds better to me, too. Are you willing to code up a patch?
OK. I'll post a follow-up here with the ticket number in a day or two.
I totally agree with this interesting proposition. Forms are not that
hard to htmlize but always take time, with those classes it could be
incredibly fast.
Cheers,
David
Here it is: http://code.djangoproject.com/ticket/3436
Excellent. Thanks, Ivan!