739 lines
26 KiB
Plaintext
739 lines
26 KiB
Plaintext
=====================
|
|
The Django admin site
|
|
=====================
|
|
|
|
One of the most powerful parts of Django is the automatic admin interface. It
|
|
reads metadata in your model to provide a powerful and production-ready
|
|
interface that content producers can immediately use to start adding content to
|
|
the site. In this document, we discuss how to activate, use and customize
|
|
Django's admin interface.
|
|
|
|
.. admonition:: Note
|
|
|
|
The admin site has been refactored significantly since Django 0.96. This
|
|
document describes the newest version of the admin site, which allows for
|
|
much richer customization. If you follow the development of Django itself,
|
|
you may have heard this described as "newforms-admin."
|
|
|
|
Overview
|
|
========
|
|
|
|
There are five steps in activating the Django admin site:
|
|
|
|
1. Add ``django.contrib.admin`` to your ``INSTALLED_APPS`` setting.
|
|
|
|
2. Determine which of your application's models should be editable in the
|
|
admin interface.
|
|
|
|
3. For each of those models, optionally create a ``ModelAdmin`` class that
|
|
encapsulates the customized admin functionality and options for that
|
|
particular model.
|
|
|
|
4. Instantiate an ``AdminSite`` and tell it about each of your models and
|
|
``ModelAdmin`` classes.
|
|
|
|
5. Hook the ``AdminSite`` instance into your URLconf.
|
|
|
|
``ModelAdmin`` objects
|
|
======================
|
|
|
|
The ``ModelAdmin`` class is the representation of a model in the admin
|
|
interface. These are stored in a file named ``admin.py`` in your application.
|
|
Let's take a look at a very simple example the ``ModelAdmin``::
|
|
|
|
from django.contrib import admin
|
|
from myproject.myapp.models import Author
|
|
|
|
class AuthorAdmin(admin.ModelAdmin):
|
|
pass
|
|
admin.site.register(Author, AuthorAdmin)
|
|
|
|
``ModelAdmin`` Options
|
|
----------------------
|
|
|
|
The ``ModelAdmin`` is very flexible. It has several options for dealing with
|
|
customizing the interface. All options are defined on the ``ModelAdmin``
|
|
subclass::
|
|
|
|
class AuthorAdmin(admin.ModelAdmin):
|
|
date_hierarchy = 'pub_date'
|
|
|
|
``date_hierarchy``
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
Set ``date_hierarchy`` to the name of a ``DateField`` or ``DateTimeField`` in
|
|
your model, and the change list page will include a date-based drilldown
|
|
navigation by that field.
|
|
|
|
Example::
|
|
|
|
date_hierarchy = 'pub_date'
|
|
|
|
``fieldsets``
|
|
~~~~~~~~~~~~~
|
|
|
|
Set ``fieldsets`` to control the layout of admin "add" and "change" pages.
|
|
|
|
``fieldsets`` is a list of two-tuples, in which each two-tuple represents a
|
|
``<fieldset>`` on the admin form page. (A ``<fieldset>`` is a "section" of the
|
|
form.)
|
|
|
|
The two-tuples are in the format ``(name, field_options)``, where ``name`` is a
|
|
string representing the title of the fieldset and ``field_options`` is a
|
|
dictionary of information about the fieldset, including a list of fields to be
|
|
displayed in it.
|
|
|
|
A full example, taken from the ``django.contrib.flatpages.FlatPage`` model::
|
|
|
|
class FlatPageAdmin(admin.ModelAdmin):
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('url', 'title', 'content', 'sites')
|
|
}),
|
|
('Advanced options', {
|
|
'classes': ('collapse',),
|
|
'fields': ('enable_comments', 'registration_required', 'template_name')
|
|
}),
|
|
)
|
|
|
|
This results in an admin page that looks like:
|
|
|
|
.. image:: http://media.djangoproject.com/img/doc/flatfiles_admin.png
|
|
|
|
If ``fieldsets`` isn't given, Django will default to displaying each field
|
|
that isn't an ``AutoField`` and has ``editable=True``, in a single fieldset,
|
|
in the same order as the fields are defined in the model.
|
|
|
|
The ``field_options`` dictionary can have the following keys:
|
|
|
|
``fields``
|
|
A tuple of field names to display in this fieldset. This key is required.
|
|
|
|
Example::
|
|
|
|
{
|
|
'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
|
|
}
|
|
|
|
To display multiple fields on the same line, wrap those fields in their own
|
|
tuple. In this example, the ``first_name`` and ``last_name`` fields will
|
|
display on the same line::
|
|
|
|
{
|
|
'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
|
|
}
|
|
|
|
``classes``
|
|
A list containing extra CSS classes to apply to the fieldset.
|
|
|
|
Example::
|
|
|
|
{
|
|
'classes': ['wide', 'extrapretty'],
|
|
}
|
|
|
|
Two useful classes defined by the default admin-site stylesheet are
|
|
``collapse`` and ``wide``. Fieldsets with the ``collapse`` style will be
|
|
initially collapsed in the admin and replaced with a small "click to expand"
|
|
link. Fieldsets with the ``wide`` style will be given extra horizontal space.
|
|
|
|
``description``
|
|
A string of optional extra text to be displayed at the top of each fieldset,
|
|
under the heading of the fieldset.
|
|
|
|
Note that this value is *not* HTML-escaped when it's displayed in
|
|
the admin interface. This lets you include HTML if you so desire.
|
|
Alternatively you can use plain text and
|
|
``django.utils.html.escape()`` to escape any HTML special
|
|
characters.
|
|
|
|
``filter_horizontal``
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Use a nifty unobtrusive JavaScript "filter" interface instead of the
|
|
usability-challenged ``<select multiple>`` in the admin form. The value is a
|
|
list of fields that should be displayed as a horizontal filter interface. See
|
|
``filter_vertical`` to use a vertical interface.
|
|
|
|
``filter_vertical``
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
Same as ``filter_horizontal``, but is a vertical display of the filter
|
|
interface.
|
|
|
|
``list_display``
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
Set ``list_display`` to control which fields are displayed on the change list
|
|
page of the admin.
|
|
|
|
Example::
|
|
|
|
list_display = ('first_name', 'last_name')
|
|
|
|
If you don't set ``list_display``, the admin site will display a single column
|
|
that displays the ``__unicode__()`` representation of each object.
|
|
|
|
A few special cases to note about ``list_display``:
|
|
|
|
* If the field is a ``ForeignKey``, Django will display the
|
|
``__unicode__()`` of the related object.
|
|
|
|
* ``ManyToManyField`` fields aren't supported, because that would entail
|
|
executing a separate SQL statement for each row in the table. If you
|
|
want to do this nonetheless, give your model a custom method, and add
|
|
that method's name to ``list_display``. (See below for more on custom
|
|
methods in ``list_display``.)
|
|
|
|
* If the field is a ``BooleanField`` or ``NullBooleanField``, Django will
|
|
display a pretty "on" or "off" icon instead of ``True`` or ``False``.
|
|
|
|
* If the string given is a method of the model, Django will call it and
|
|
display the output. This method should have a ``short_description``
|
|
function attribute, for use as the header for the field.
|
|
|
|
Here's a full example model::
|
|
|
|
class Person(models.Model):
|
|
name = models.CharField(max_length=50)
|
|
birthday = models.DateField()
|
|
|
|
def decade_born_in(self):
|
|
return self.birthday.strftime('%Y')[:3] + "0's"
|
|
decade_born_in.short_description = 'Birth decade'
|
|
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
list_display = ('name', 'decade_born_in')
|
|
|
|
* If the string given is a method of the model, Django will HTML-escape the
|
|
output by default. If you'd rather not escape the output of the method,
|
|
give the method an ``allow_tags`` attribute whose value is ``True``.
|
|
|
|
Here's a full example model::
|
|
|
|
class Person(models.Model):
|
|
first_name = models.CharField(max_length=50)
|
|
last_name = models.CharField(max_length=50)
|
|
color_code = models.CharField(max_length=6)
|
|
|
|
def colored_name(self):
|
|
return '<span style="color: #%s;">%s %s</span>' % (self.color_code, self.first_name, self.last_name)
|
|
colored_name.allow_tags = True
|
|
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
list_display = ('first_name', 'last_name', 'colored_name')
|
|
|
|
* If the string given is a method of the model that returns True or False
|
|
Django will display a pretty "on" or "off" icon if you give the method a
|
|
``boolean`` attribute whose value is ``True``.
|
|
|
|
Here's a full example model::
|
|
|
|
class Person(models.Model):
|
|
first_name = models.CharField(max_length=50)
|
|
birthday = models.DateField()
|
|
|
|
def born_in_fifties(self):
|
|
return self.birthday.strftime('%Y')[:3] == 5
|
|
born_in_fifties.boolean = True
|
|
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
list_display = ('name', 'born_in_fifties')
|
|
|
|
|
|
* The ``__str__()`` and ``__unicode__()`` methods are just as valid in
|
|
``list_display`` as any other model method, so it's perfectly OK to do
|
|
this::
|
|
|
|
list_display = ('__unicode__', 'some_other_field')
|
|
|
|
* Usually, elements of ``list_display`` that aren't actual database fields
|
|
can't be used in sorting (because Django does all the sorting at the
|
|
database level).
|
|
|
|
However, if an element of ``list_display`` represents a certain database
|
|
field, you can indicate this fact by setting the ``admin_order_field``
|
|
attribute of the item.
|
|
|
|
For example::
|
|
|
|
class Person(models.Model):
|
|
first_name = models.CharField(max_length=50)
|
|
color_code = models.CharField(max_length=6)
|
|
|
|
def colored_first_name(self):
|
|
return '<span style="color: #%s;">%s</span>' % (self.color_code, self.first_name)
|
|
colored_first_name.allow_tags = True
|
|
colored_first_name.admin_order_field = 'first_name'
|
|
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
list_display = ('first_name', 'colored_first_name')
|
|
|
|
The above will tell Django to order by the ``first_name`` field when
|
|
trying to sort by ``colored_first_name`` in the admin.
|
|
|
|
``list_display_links``
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Set ``list_display_links`` to control which fields in ``list_display`` should
|
|
be linked to the "change" page for an object.
|
|
|
|
By default, the change list page will link the first column -- the first field
|
|
specified in ``list_display`` -- to the change page for each item. But
|
|
``list_display_links`` lets you change which columns are linked. Set
|
|
``list_display_links`` to a list or tuple of field names (in the same format as
|
|
``list_display``) to link.
|
|
|
|
``list_display_links`` can specify one or many field names. As long as the
|
|
field names appear in ``list_display``, Django doesn't care how many (or how
|
|
few) fields are linked. The only requirement is: If you want to use
|
|
``list_display_links``, you must define ``list_display``.
|
|
|
|
In this example, the ``first_name`` and ``last_name`` fields will be linked on
|
|
the change list page::
|
|
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
list_display = ('first_name', 'last_name', 'birthday')
|
|
list_display_links = ('first_name', 'last_name')
|
|
|
|
Finally, note that in order to use ``list_display_links``, you must define
|
|
``list_display``, too.
|
|
|
|
``list_filter``
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Set ``list_filter`` to activate filters in the right sidebar of the change list
|
|
page of the admin. This should be a list of field names, and each specified
|
|
field should be either a ``BooleanField``, ``CharField``, ``DateField``,
|
|
``DateTimeField``, ``IntegerField`` or ``ForeignKey``.
|
|
|
|
This example, taken from the ``django.contrib.auth.models.User`` model, shows
|
|
how both ``list_display`` and ``list_filter`` work::
|
|
|
|
class UserAdmin(admin.ModelAdmin):
|
|
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
|
|
list_filter = ('is_staff', 'is_superuser')
|
|
|
|
The above code results in an admin change list page that looks like this:
|
|
|
|
.. image:: http://media.djangoproject.com/img/doc/users_changelist.png
|
|
|
|
(This example also has ``search_fields`` defined. See below.)
|
|
|
|
``list_per_page``
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
Set ``list_per_page`` to control how many items appear on each paginated admin
|
|
change list page. By default, this is set to ``100``.
|
|
|
|
``list_select_related``
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Set ``list_select_related`` to tell Django to use ``select_related()`` in
|
|
retrieving the list of objects on the admin change list page. This can save you
|
|
a bunch of database queries.
|
|
|
|
The value should be either ``True`` or ``False``. Default is ``False``.
|
|
|
|
Note that Django will use ``select_related()``, regardless of this setting,
|
|
if one of the ``list_display`` fields is a ``ForeignKey``.
|
|
|
|
For more on ``select_related()``, see `the select_related() docs`_.
|
|
|
|
.. _the select_related() docs: ../db-api/#select-related
|
|
|
|
``inlines``
|
|
~~~~~~~~~~~
|
|
|
|
See ``InlineModelAdmin`` objects below.
|
|
|
|
``ordering``
|
|
~~~~~~~~~~~~
|
|
|
|
Set ``ordering`` to specify how objects on the admin change list page should be
|
|
ordered. This should be a list or tuple in the same format as a model's
|
|
``ordering`` parameter.
|
|
|
|
If this isn't provided, the Django admin will use the model's default ordering.
|
|
|
|
``prepopulated_fields``
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Set ``prepopulated_fields`` to a dictionary mapping field names to the fields
|
|
it should prepopulate from::
|
|
|
|
class ArticleAdmin(admin.ModelAdmin):
|
|
prepopulated_fields = {"slug": ("title",)}
|
|
|
|
When set, the given fields will use a bit of JavaScript to populate from the
|
|
fields assigned. The main use for this functionality is to automatically
|
|
generate the value for ``SlugField`` fields from one or more other fields. The
|
|
generated value is produced by concatenating the values of the source fields,
|
|
and then by transforming that result into a valid slug (e.g. substituting
|
|
dashes for spaces).
|
|
|
|
``prepopulated_fields`` doesn't accept ``DateTimeField``, ``ForeignKey``, nor
|
|
``ManyToManyField`` fields.
|
|
|
|
``radio_fields``
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
By default, Django's admin uses a select-box interface (<select>) for
|
|
fields that are ``ForeignKey`` or have ``choices`` set. If a field is present
|
|
in ``radio_fields``, Django will use a radio-button interface instead.
|
|
Assuming ``group`` is a ``ForeignKey`` on the ``Person`` model::
|
|
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
radio_fields = {"group": admin.VERTICAL}
|
|
|
|
You have the choice of using ``HORIZONTAL`` or ``VERTICAL`` from the
|
|
``django.contrib.admin`` module.
|
|
|
|
Don't include a field in ``radio_fields`` unless it's a ``ForeignKey`` or has
|
|
``choices`` set.
|
|
|
|
``raw_id_fields``
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
By default, Django's admin uses a select-box interface (<select>) for
|
|
fields that are ``ForeignKey``. Sometimes you don't want to incur the
|
|
overhead of having to select all the related instances to display in the
|
|
drop-down.
|
|
|
|
``raw_id_fields`` is a list of fields you would like to change
|
|
into a ``Input`` widget for the primary key.
|
|
|
|
``save_as``
|
|
~~~~~~~~~~~
|
|
|
|
Set ``save_as`` to enable a "save as" feature on admin change forms.
|
|
|
|
Normally, objects have three save options: "Save", "Save and continue editing"
|
|
and "Save and add another". If ``save_as`` is ``True``, "Save and add another"
|
|
will be replaced by a "Save as" button.
|
|
|
|
"Save as" means the object will be saved as a new object (with a new ID),
|
|
rather than the old object.
|
|
|
|
By default, ``save_as`` is set to ``False``.
|
|
|
|
``save_on_top``
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Set ``save_on_top`` to add save buttons across the top of your admin change
|
|
forms.
|
|
|
|
Normally, the save buttons appear only at the bottom of the forms. If you set
|
|
``save_on_top``, the buttons will appear both on the top and the bottom.
|
|
|
|
By default, ``save_on_top`` is set to ``False``.
|
|
|
|
``search_fields``
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
Set ``search_fields`` to enable a search box on the admin change list page.
|
|
This should be set to a list of field names that will be searched whenever
|
|
somebody submits a search query in that text box.
|
|
|
|
These fields should be some kind of text field, such as ``CharField`` or
|
|
``TextField``. You can also perform a related lookup on a ``ForeignKey`` with
|
|
the lookup API "follow" notation::
|
|
|
|
search_fields = ['foreign_key__related_fieldname']
|
|
|
|
When somebody does a search in the admin search box, Django splits the search
|
|
query into words and returns all objects that contain each of the words, case
|
|
insensitive, where each word must be in at least one of ``search_fields``. For
|
|
example, if ``search_fields`` is set to ``['first_name', 'last_name']`` and a
|
|
user searches for ``john lennon``, Django will do the equivalent of this SQL
|
|
``WHERE`` clause::
|
|
|
|
WHERE (first_name ILIKE '%john%' OR last_name ILIKE '%john%')
|
|
AND (first_name ILIKE '%lennon%' OR last_name ILIKE '%lennon%')
|
|
|
|
For faster and/or more restrictive searches, prefix the field name
|
|
with an operator:
|
|
|
|
``^``
|
|
Matches the beginning of the field. For example, if ``search_fields`` is
|
|
set to ``['^first_name', '^last_name']`` and a user searches for
|
|
``john lennon``, Django will do the equivalent of this SQL ``WHERE``
|
|
clause::
|
|
|
|
WHERE (first_name ILIKE 'john%' OR last_name ILIKE 'john%')
|
|
AND (first_name ILIKE 'lennon%' OR last_name ILIKE 'lennon%')
|
|
|
|
This query is more efficient than the normal ``'%john%'`` query, because
|
|
the database only needs to check the beginning of a column's data, rather
|
|
than seeking through the entire column's data. Plus, if the column has an
|
|
index on it, some databases may be able to use the index for this query,
|
|
even though it's a ``LIKE`` query.
|
|
|
|
``=``
|
|
Matches exactly, case-insensitive. For example, if
|
|
``search_fields`` is set to ``['=first_name', '=last_name']`` and
|
|
a user searches for ``john lennon``, Django will do the equivalent
|
|
of this SQL ``WHERE`` clause::
|
|
|
|
WHERE (first_name ILIKE 'john' OR last_name ILIKE 'john')
|
|
AND (first_name ILIKE 'lennon' OR last_name ILIKE 'lennon')
|
|
|
|
Note that the query input is split by spaces, so, following this example,
|
|
it's currently not possible to search for all records in which
|
|
``first_name`` is exactly ``'john winston'`` (containing a space).
|
|
|
|
``@``
|
|
Performs a full-text match. This is like the default search method but uses
|
|
an index. Currently this is only available for MySQL.
|
|
|
|
``ModelAdmin`` media definitions
|
|
--------------------------------
|
|
|
|
There are times where you would like add a bit of CSS and/or JavaScript to
|
|
the add/change views. This can be accomplished by using a Media inner class
|
|
on your ``ModelAdmin``::
|
|
|
|
class ArticleAdmin(admin.ModelAdmin):
|
|
class Media:
|
|
css = {
|
|
"all": ("my_styles.css",)
|
|
}
|
|
js = ("my_code.js",)
|
|
|
|
Keep in mind that this will be prepended with ``MEDIA_URL``. The same rules
|
|
apply as `regular media definitions on forms`_.
|
|
|
|
.. _regular media definitions on forms: ../forms/#media
|
|
|
|
``InlineModelAdmin`` objects
|
|
============================
|
|
|
|
The admin interface has the ability to edit models on the same page as a
|
|
parent model. These are called inlines. You can add them to a model by
|
|
specifying them in a ``ModelAdmin.inlines`` attribute::
|
|
|
|
class BookInline(admin.TabularInline):
|
|
model = Book
|
|
|
|
class AuthorAdmin(admin.ModelAdmin):
|
|
inlines = [
|
|
BookInline,
|
|
]
|
|
|
|
Django provides two subclasses of ``InlineModelAdmin`` and they are:
|
|
|
|
* ``TabularInline``
|
|
* ``StackedInline``
|
|
|
|
The difference between these two is merely the template used to render them.
|
|
|
|
``InlineModelAdmin`` options
|
|
-----------------------------
|
|
|
|
The ``InlineModelAdmin`` class is a subclass of ``ModelAdmin`` so it inherits
|
|
all the same functionality as well as some of its own:
|
|
|
|
``model``
|
|
~~~~~~~~~
|
|
|
|
The model in which the inline is using. This is required.
|
|
|
|
``fk_name``
|
|
~~~~~~~~~~~
|
|
|
|
The name of the foreign key on the model. In most cases this will be dealt
|
|
with automatically, but ``fk_name`` must be specified explicitly if there are
|
|
more than one foreign key to the same parent model.
|
|
|
|
``formset``
|
|
~~~~~~~~~~~
|
|
|
|
This defaults to ``BaseInlineFormset``. Using your own formset can give you
|
|
many possibilities of customization. Inlines are built around
|
|
`model formsets`_.
|
|
|
|
.. _model formsets: ../modelforms/#model-formsets
|
|
|
|
``form``
|
|
~~~~~~~~
|
|
|
|
The value for ``form`` is inherited from ``ModelAdmin``. This is what is
|
|
passed through to ``formset_factory`` when creating the formset for this
|
|
inline.
|
|
|
|
``extra``
|
|
~~~~~~~~~
|
|
|
|
This controls the number of extra forms the formset will display in addition
|
|
to the initial forms. See the `formsets documentation`_ for more information.
|
|
|
|
.. _formsets documentation: ../forms/#formsets
|
|
|
|
``max_num``
|
|
~~~~~~~~~~~
|
|
|
|
This controls the maximum number of forms to show in the inline. This doesn't
|
|
directly corrolate to the number of objects, but can if the value is small
|
|
enough. See `max_num in formsets`_ for more information.
|
|
|
|
.. _max_num in formsets: ../modelforms/#limiting-the-number-of-objects-editable
|
|
|
|
``template``
|
|
~~~~~~~~~~~~
|
|
|
|
The template used to render the inline on the page.
|
|
|
|
``verbose_name``
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
An override to the ``verbose_name`` found in the model's inner ``Meta`` class.
|
|
|
|
``verbose_name_plural``
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
An override to the ``verbose_name_plural`` found in the model's inner ``Meta``
|
|
class.
|
|
|
|
Working with a model with two or more foreign keys to the same parent model
|
|
---------------------------------------------------------------------------
|
|
|
|
It is sometimes possible to have more than one foreign key to the same model.
|
|
Take this model for instance::
|
|
|
|
class Friendship(models.Model):
|
|
to_person = models.ForeignKey(Person, related_name="friends")
|
|
from_person = models.ForeignKey(Person, related_name="from_friends")
|
|
|
|
If you wanted to display an inline on the ``Person`` admin add/change pages
|
|
you need to explicitly define the foreign key since it is unable to do so
|
|
automatically::
|
|
|
|
class FriendshipInline(admin.TabularInline):
|
|
model = Friendship
|
|
fk_name = "to_person"
|
|
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
inlines = [
|
|
FriendshipInline,
|
|
]
|
|
|
|
Working with Many-to-Many Intermediary Models
|
|
----------------------------------------------
|
|
|
|
By default, admin widgets for many-to-many relations will be displayed inline
|
|
on whichever model contains the actual reference to the `ManyToManyField`.
|
|
However, when you specify an intermediary model using the ``through``
|
|
argument to a ``ManyToManyField``, the admin will not display a widget by
|
|
default. This is because each instance of that intermediary model requires
|
|
more information than could be displayed in a single widget, and the layout
|
|
required for multiple widgets will vary depending on the intermediate model.
|
|
|
|
However, we still want to be able to edit that information inline. Fortunately,
|
|
this is easy to do with inline admin models. Suppose we have the following
|
|
models::
|
|
|
|
class Person(models.Model):
|
|
name = models.CharField(max_length=128)
|
|
|
|
class Group(models.Model):
|
|
name = models.CharField(max_length=128)
|
|
members = models.ManyToManyField(Person, through='Membership')
|
|
|
|
class Membership(models.Model):
|
|
person = models.ForeignKey(Person)
|
|
group = models.ForeignKey(Group)
|
|
date_joined = models.DateField()
|
|
invite_reason = models.CharField(max_length=64)
|
|
|
|
The first step in displaying this intermediate model in the admin is to
|
|
define an inline model for the Membership table::
|
|
|
|
class MembershipInline(admin.TabularInline):
|
|
model = Membership
|
|
extra = 1
|
|
|
|
This simple example uses the defaults inline form for the Membership model,
|
|
and shows 1 extra line. This could be customized using any of the options
|
|
available to inline models.
|
|
|
|
Now create admin views for the ``Person`` and ``Group`` models::
|
|
|
|
class PersonAdmin(admin.ModelAdmin):
|
|
inlines = (MembershipInline,)
|
|
|
|
class GroupAdmin(admin.ModelAdmin):
|
|
inlines = (MembershipInline,)
|
|
|
|
Finally, register your ``Person`` and ``Group`` models with the admin site::
|
|
|
|
admin.site.register(Person, PersonAdmin)
|
|
admin.site.register(Group, GroupAdmin)
|
|
|
|
Now your admin site is set up to edit ``Membership`` objects inline from either
|
|
the ``Person`` or the ``Group`` detail pages.
|
|
|
|
``AdminSite`` objects
|
|
=====================
|
|
|
|
Hooking ``AdminSite`` instances into your URLconf
|
|
-------------------------------------------------
|
|
|
|
The last step in setting up the Django admin is to hook your ``AdminSite``
|
|
instance into your URLconf. Do this by pointing a given URL at the
|
|
``AdminSite.root`` method.
|
|
|
|
In this example, we register the default ``AdminSite`` instance
|
|
``django.contrib.admin.site`` at the URL ``/admin/`` ::
|
|
|
|
# urls.py
|
|
from django.conf.urls.defaults import *
|
|
from django.contrib import admin
|
|
|
|
admin.autodiscover()
|
|
|
|
urlpatterns = patterns('',
|
|
('^admin/(.*)', admin.site.root),
|
|
)
|
|
|
|
Above we used ``admin.autodiscover()`` to automatically load the
|
|
``INSTALLED_APPS`` admin.py modules.
|
|
|
|
In this example, we register the ``AdminSite`` instance
|
|
``myproject.admin.admin_site`` at the URL ``/myadmin/`` ::
|
|
|
|
# urls.py
|
|
from django.conf.urls.defaults import *
|
|
from myproject.admin import admin_site
|
|
|
|
urlpatterns = patterns('',
|
|
('^myadmin/(.*)', admin_site.root),
|
|
)
|
|
|
|
There is really no need to use autodiscover when using your own ``AdminSite``
|
|
instance since you will likely be importing all the per-app admin.py modules
|
|
in your ``myproject.admin`` module.
|
|
|
|
Note that the regular expression in the URLpattern *must* group everything in
|
|
the URL that comes after the URL root -- hence the ``(.*)`` in these examples.
|
|
|
|
Multiple admin sites in the same URLconf
|
|
----------------------------------------
|
|
|
|
It's easy to create multiple instances of the admin site on the same
|
|
Django-powered Web site. Just create multiple instances of ``AdminSite`` and
|
|
root each one at a different URL.
|
|
|
|
In this example, the URLs ``/basic-admin/`` and ``/advanced-admin/`` feature
|
|
separate versions of the admin site -- using the ``AdminSite`` instances
|
|
``myproject.admin.basic_site`` and ``myproject.admin.advanced_site``,
|
|
respectively::
|
|
|
|
# urls.py
|
|
from django.conf.urls.defaults import *
|
|
from myproject.admin import basic_site, advanced_site
|
|
|
|
urlpatterns = patterns('',
|
|
('^basic-admin/(.*)', basic_site.root),
|
|
('^advanced-admin/(.*)', advanced_site.root),
|
|
)
|