mirror of
https://github.com/django/django.git
synced 2025-08-02 10:02:41 +00:00
Merged the newforms-admin branch into trunk.
This is a backward incompatible change. The admin contrib app has been refactored. The newforms module has several improvements including FormSets and Media definitions. git-svn-id: http://code.djangoproject.com/svn/django/trunk@7967 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
dc375fb0f3
commit
a19ed8aea3
121 changed files with 8050 additions and 2680 deletions
678
docs/admin.txt
Normal file
678
docs/admin.txt
Normal file
|
@ -0,0 +1,678 @@
|
|||
=====================
|
||||
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 four steps in activating the Django admin site:
|
||||
|
||||
1. Determine which of your application's models should be editable in the
|
||||
admin interface.
|
||||
|
||||
2. For each of those models, optionally create a ``ModelAdmin`` class that
|
||||
encapsulates the customized admin functionality and options for that
|
||||
particular model.
|
||||
|
||||
3. Instantiate an ``AdminSite`` and tell it about each of your models and
|
||||
``ModelAdmin`` classes.
|
||||
|
||||
4. 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 string containing extra CSS classes to apply to the fieldset.
|
||||
|
||||
Example::
|
||||
|
||||
{
|
||||
'classes': 'wide',
|
||||
}
|
||||
|
||||
Apply multiple classes by separating them with spaces. 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. It's used verbatim, so you can use any HTML
|
||||
and you must escape any special HTML characters (such as ampersands) yourself.
|
||||
|
||||
``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.
|
||||
|
||||
``prepopulated_fields`` doesn't accept DateTimeFields, ForeignKeys nor
|
||||
ManyToManyFields.
|
||||
|
||||
``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: ../newforms/#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 a model being
|
||||
specifing 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: ../newforms/#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,
|
||||
]
|
||||
|
||||
``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),
|
||||
)
|
|
@ -516,8 +516,8 @@ It's your responsibility to provide the login form in a template called
|
|||
``registration/login.html`` by default. This template gets passed three
|
||||
template context variables:
|
||||
|
||||
* ``form``: A ``FormWrapper`` object representing the login form. See the
|
||||
`forms documentation`_ for more on ``FormWrapper`` objects.
|
||||
* ``form``: A ``Form`` object representing the login form. See the
|
||||
`newforms documentation`_ for more on ``Form`` objects.
|
||||
* ``next``: The URL to redirect to after successful login. This may contain
|
||||
a query string, too.
|
||||
* ``site_name``: The name of the current ``Site``, according to the
|
||||
|
@ -541,14 +541,14 @@ block::
|
|||
|
||||
{% block content %}
|
||||
|
||||
{% if form.has_errors %}
|
||||
{% if form.errors %}
|
||||
<p>Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action=".">
|
||||
<table>
|
||||
<tr><td><label for="id_username">Username:</label></td><td>{{ form.username }}</td></tr>
|
||||
<tr><td><label for="id_password">Password:</label></td><td>{{ form.password }}</td></tr>
|
||||
<tr><td>{{ form.username.label_tag }}</td><td>{{ form.username }}</td></tr>
|
||||
<tr><td>{{ form.password.label_tag }}</td><td>{{ form.password }}</td></tr>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="login" />
|
||||
|
@ -557,7 +557,7 @@ block::
|
|||
|
||||
{% endblock %}
|
||||
|
||||
.. _forms documentation: ../forms/
|
||||
.. _newforms documentation: ../newforms/
|
||||
.. _site framework docs: ../sites/
|
||||
|
||||
Other built-in views
|
||||
|
@ -677,29 +677,29 @@ successful login.
|
|||
* ``login_url``: The URL of the login page to redirect to. This
|
||||
will default to ``settings.LOGIN_URL`` if not supplied.
|
||||
|
||||
Built-in manipulators
|
||||
---------------------
|
||||
Built-in forms
|
||||
--------------
|
||||
|
||||
**New in Django development version.**
|
||||
|
||||
If you don't want to use the built-in views, but want the convenience
|
||||
of not having to write manipulators for this functionality, the
|
||||
authentication system provides several built-in manipulators:
|
||||
of not having to write forms for this functionality, the authentication
|
||||
system provides several built-in forms:
|
||||
|
||||
* ``django.contrib.auth.forms.AdminPasswordChangeForm``: A
|
||||
manipulator used in the admin interface to change a user's
|
||||
password.
|
||||
* ``django.contrib.auth.forms.AdminPasswordChangeForm``: A form used in
|
||||
the admin interface to change a user's password.
|
||||
|
||||
* ``django.contrib.auth.forms.AuthenticationForm``: A manipulator
|
||||
for logging a user in.
|
||||
* ``django.contrib.auth.forms.AuthenticationForm``: A form for logging a
|
||||
user in.
|
||||
|
||||
* ``django.contrib.auth.forms.PasswordChangeForm``: A manipulator
|
||||
for allowing a user to change their password.
|
||||
* ``django.contrib.auth.forms.PasswordChangeForm``: A form for allowing a
|
||||
user to change their password.
|
||||
|
||||
* ``django.contrib.auth.forms.PasswordResetForm``: A manipulator
|
||||
for resetting a user's password and emailing the new password to
|
||||
them.
|
||||
* ``django.contrib.auth.forms.PasswordResetForm``: A form for resetting a
|
||||
user's password and emailing the new password to them.
|
||||
|
||||
* ``django.contrib.auth.forms.UserCreationForm``: A manipulator
|
||||
for creating a new user.
|
||||
* ``django.contrib.auth.forms.UserCreationForm``: A form for creating a
|
||||
new user.
|
||||
|
||||
Limiting access to logged-in users that pass a test
|
||||
---------------------------------------------------
|
||||
|
|
|
@ -204,7 +204,6 @@ order:
|
|||
* ``unique_for_year``
|
||||
* ``validator_list``
|
||||
* ``choices``
|
||||
* ``radio_admin``
|
||||
* ``help_text``
|
||||
* ``db_column``
|
||||
* ``db_tablespace``: Currently only used with the Oracle backend and only
|
||||
|
|
|
@ -20,10 +20,10 @@ For example, here's how you can create a form with a field representing a
|
|||
French telephone number::
|
||||
|
||||
from django import newforms as forms
|
||||
from django.contrib.localflavor.fr.forms import FRPhoneNumberField
|
||||
from django.contrib.localflavor import fr
|
||||
|
||||
class MyForm(forms.Form):
|
||||
my_french_phone_no = FRPhoneNumberField()
|
||||
my_french_phone_no = fr.forms.FRPhoneNumberField()
|
||||
|
||||
Supported countries
|
||||
===================
|
||||
|
|
|
@ -12,8 +12,6 @@ The basics:
|
|||
* Each attribute of the model represents a database field.
|
||||
* Model metadata (non-field information) goes in an inner class named
|
||||
``Meta``.
|
||||
* Metadata used for Django's admin site goes into an inner class named
|
||||
``Admin``.
|
||||
* With all of this, Django gives you an automatically-generated
|
||||
database-access API, which is explained in the `Database API reference`_.
|
||||
|
||||
|
@ -425,18 +423,6 @@ not specified, Django will use a default length of 50.
|
|||
|
||||
Implies ``db_index=True``.
|
||||
|
||||
Accepts an extra option, ``prepopulate_from``, which is a list of fields
|
||||
from which to auto-populate the slug, via JavaScript, in the object's admin
|
||||
form::
|
||||
|
||||
models.SlugField(prepopulate_from=("pre_name", "name"))
|
||||
|
||||
``prepopulate_from`` doesn't accept DateTimeFields, ForeignKeys nor
|
||||
ManyToManyFields.
|
||||
|
||||
The admin represents ``SlugField`` as an ``<input type="text">`` (a
|
||||
single-line input).
|
||||
|
||||
``SmallIntegerField``
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -665,16 +651,6 @@ unless you want to override the default primary-key behavior.
|
|||
``primary_key=True`` implies ``null=False`` and ``unique=True``. Only
|
||||
one primary key is allowed on an object.
|
||||
|
||||
``radio_admin``
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
By default, Django's admin uses a select-box interface (<select>) for
|
||||
fields that are ``ForeignKey`` or have ``choices`` set. If ``radio_admin``
|
||||
is set to ``True``, Django will use a radio-button interface instead.
|
||||
|
||||
Don't use this for a field unless it's a ``ForeignKey`` or has ``choices``
|
||||
set.
|
||||
|
||||
``unique``
|
||||
~~~~~~~~~~
|
||||
|
||||
|
@ -822,14 +798,6 @@ relationship should work. All are optional:
|
|||
======================= ============================================================
|
||||
Argument Description
|
||||
======================= ============================================================
|
||||
``edit_inline`` If ``True``, this related object is edited
|
||||
"inline" on the related object's page. This means
|
||||
that the object will not have its own admin
|
||||
interface. Use either ``models.TABULAR`` or ``models.STACKED``,
|
||||
which, respectively, designate whether the inline-editable
|
||||
objects are displayed as a table or as a "stack" of
|
||||
fieldsets.
|
||||
|
||||
``limit_choices_to`` A dictionary of lookup arguments and values (see
|
||||
the `Database API reference`_) that limit the
|
||||
available admin choices for this object. Use this
|
||||
|
@ -848,39 +816,6 @@ relationship should work. All are optional:
|
|||
|
||||
Not compatible with ``edit_inline``.
|
||||
|
||||
``max_num_in_admin`` For inline-edited objects, this is the maximum
|
||||
number of related objects to display in the admin.
|
||||
Thus, if a pizza could only have up to 10
|
||||
toppings, ``max_num_in_admin=10`` would ensure
|
||||
that a user never enters more than 10 toppings.
|
||||
|
||||
Note that this doesn't ensure more than 10 related
|
||||
toppings ever get created. It simply controls the
|
||||
admin interface; it doesn't enforce things at the
|
||||
Python API level or database level.
|
||||
|
||||
``min_num_in_admin`` The minimum number of related objects displayed in
|
||||
the admin. Normally, at the creation stage,
|
||||
``num_in_admin`` inline objects are shown, and at
|
||||
the edit stage ``num_extra_on_change`` blank
|
||||
objects are shown in addition to all pre-existing
|
||||
related objects. However, no fewer than
|
||||
``min_num_in_admin`` related objects will ever be
|
||||
displayed.
|
||||
|
||||
``num_extra_on_change`` The number of extra blank related-object fields to
|
||||
show at the change stage.
|
||||
|
||||
``num_in_admin`` The default number of inline objects to display
|
||||
on the object page at the add stage.
|
||||
|
||||
``raw_id_admin`` Only display a field for the integer to be entered
|
||||
instead of a drop-down menu. This is useful when
|
||||
related to an object type that will have too many
|
||||
rows to make a select box practical.
|
||||
|
||||
Not used with ``edit_inline``.
|
||||
|
||||
``related_name`` The name to use for the relation from the related
|
||||
object back to this one. See the
|
||||
`related objects documentation`_ for a full
|
||||
|
@ -957,13 +892,6 @@ the relationship should work. All are optional:
|
|||
======================= ============================================================
|
||||
``related_name`` See the description under ``ForeignKey`` above.
|
||||
|
||||
``filter_interface`` Use a nifty unobtrusive Javascript "filter" interface
|
||||
instead of the usability-challenged ``<select multiple>``
|
||||
in the admin form for this object. The value should be
|
||||
``models.HORIZONTAL`` or ``models.VERTICAL`` (i.e.
|
||||
should the interface be stacked horizontally or
|
||||
vertically).
|
||||
|
||||
``limit_choices_to`` See the description under ``ForeignKey`` above.
|
||||
|
||||
``symmetrical`` Only used in the definition of ManyToManyFields on self.
|
||||
|
@ -1255,412 +1183,6 @@ attribute is the primary key field for the model. You can read and set this
|
|||
value, just as you would for any other attribute, and it will update the
|
||||
correct field in the model.
|
||||
|
||||
Admin options
|
||||
=============
|
||||
|
||||
If you want your model to be visible to Django's admin site, give your model an
|
||||
inner ``"class Admin"``, like so::
|
||||
|
||||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=30)
|
||||
last_name = models.CharField(max_length=30)
|
||||
|
||||
class Admin:
|
||||
# Admin options go here
|
||||
pass
|
||||
|
||||
The ``Admin`` class tells Django how to display the model in the admin site.
|
||||
|
||||
Here's a list of all possible ``Admin`` options. None of these options are
|
||||
required. To use an admin interface without specifying any options, use
|
||||
``pass``, like so::
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
Adding ``class Admin`` to a model is completely optional.
|
||||
|
||||
``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'
|
||||
|
||||
``fields``
|
||||
----------
|
||||
|
||||
Set ``fields`` to control the layout of admin "add" and "change" pages.
|
||||
|
||||
``fields`` 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 Admin:
|
||||
fields = (
|
||||
(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 ``fields`` 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 string containing extra CSS classes to apply to the fieldset.
|
||||
|
||||
Example::
|
||||
|
||||
{
|
||||
'classes': 'wide',
|
||||
}
|
||||
|
||||
Apply multiple classes by separating them with spaces. 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. It's used verbatim, so you can use any HTML
|
||||
and you must escape any special HTML characters (such as ampersands) yourself.
|
||||
|
||||
``js``
|
||||
------
|
||||
|
||||
A list of strings representing URLs of JavaScript files to link into the admin
|
||||
screen via ``<script src="">`` tags. This can be used to tweak a given type of
|
||||
admin page in JavaScript or to provide "quick links" to fill in default values
|
||||
for certain fields.
|
||||
|
||||
If you use relative URLs -- URLs that don't start with ``http://`` or ``/`` --
|
||||
then the admin site will automatically prefix these links with
|
||||
``settings.ADMIN_MEDIA_PREFIX``.
|
||||
|
||||
``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 ``__str__()`` 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()
|
||||
|
||||
class Admin:
|
||||
list_display = ('name', 'decade_born_in')
|
||||
|
||||
def decade_born_in(self):
|
||||
return self.birthday.strftime('%Y')[:3] + "0's"
|
||||
decade_born_in.short_description = 'Birth decade'
|
||||
|
||||
* 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)
|
||||
|
||||
class Admin:
|
||||
list_display = ('first_name', 'last_name', 'colored_name')
|
||||
|
||||
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
|
||||
|
||||
* 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()
|
||||
|
||||
class Admin:
|
||||
list_display = ('name', 'born_in_fifties')
|
||||
|
||||
def born_in_fifties(self):
|
||||
return self.birthday.strftime('%Y')[:3] == 5
|
||||
born_in_fifties.boolean = True
|
||||
|
||||
|
||||
* 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)
|
||||
|
||||
class Admin:
|
||||
list_display = ('first_name', 'colored_first_name')
|
||||
|
||||
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'
|
||||
|
||||
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 Admin:
|
||||
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 Admin:
|
||||
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
|
||||
|
||||
``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.
|
||||
|
||||
``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.
|
||||
|
||||
Managers
|
||||
========
|
||||
|
||||
|
|
|
@ -376,3 +376,125 @@ There are a couple of things to note, however.
|
|||
|
||||
Chances are these notes won't affect you unless you're trying to do something
|
||||
tricky with subclassing.
|
||||
|
||||
Model Formsets
|
||||
==============
|
||||
|
||||
Similar to regular formsets there are a couple enhanced formset classes that
|
||||
provide all the right things to work with your models. Lets reuse the
|
||||
``Author`` model from above::
|
||||
|
||||
>>> from django.newforms.models import modelformset_factory
|
||||
>>> AuthorFormSet = modelformset_factory(Author)
|
||||
|
||||
This will create a formset that is capable of working with the data associated
|
||||
to the ``Author`` model. It works just like a regular formset::
|
||||
|
||||
>>> formset = AuthorFormSet()
|
||||
>>> print formset
|
||||
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" />
|
||||
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></td></tr>
|
||||
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
|
||||
<option value="" selected="selected">---------</option>
|
||||
<option value="MR">Mr.</option>
|
||||
<option value="MRS">Mrs.</option>
|
||||
<option value="MS">Ms.</option>
|
||||
</select></td></tr>
|
||||
<tr><th><label for="id_form-0-birth_date">Birth date:</label></th><td><input type="text" name="form-0-birth_date" id="id_form-0-birth_date" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>
|
||||
|
||||
.. note::
|
||||
One thing to note is that ``modelformset_factory`` uses ``formset_factory``
|
||||
and by default uses ``can_delete=True``.
|
||||
|
||||
Changing the queryset
|
||||
---------------------
|
||||
|
||||
By default when you create a formset from a model the queryset will be all
|
||||
objects in the model. This is best shown as ``Author.objects.all()``. This is
|
||||
configurable::
|
||||
|
||||
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
|
||||
|
||||
Alternatively, you can use a subclassing based approach::
|
||||
|
||||
from django.newforms.models import BaseModelFormSet
|
||||
|
||||
class BaseAuthorFormSet(BaseModelFormSet):
|
||||
def get_queryset(self):
|
||||
return super(BaseAuthorFormSet, self).get_queryset().filter(name__startswith='O')
|
||||
|
||||
Then your ``BaseAuthorFormSet`` would be passed into the factory function to
|
||||
be used as a base::
|
||||
|
||||
>>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet)
|
||||
|
||||
Saving objects in the formset
|
||||
-----------------------------
|
||||
|
||||
Similar to a ``ModelForm`` you can save the data into the model. This is done
|
||||
with the ``save()`` method on the formset::
|
||||
|
||||
# create a formset instance with POST data.
|
||||
>>> formset = AuthorFormSet(request.POST)
|
||||
|
||||
# assuming all is valid, save the data
|
||||
>>> instances = formset.save()
|
||||
|
||||
The ``save()`` method will return the instances that have been saved to the
|
||||
database. If an instance did not change in the bound data it will not be
|
||||
saved to the database and not found in ``instances`` in the above example.
|
||||
|
||||
You can optionally pass in ``commit=False`` to ``save()`` to only return the
|
||||
model instances without any database interaction::
|
||||
|
||||
# don't save to the database
|
||||
>>> instances = formset.save(commit=False)
|
||||
>>> for instance in instances:
|
||||
... # do something with instance
|
||||
... instance.save()
|
||||
|
||||
This gives you the ability to attach data to the instances before saving them
|
||||
to the database. If your formset contains a ``ManyToManyField`` you will also
|
||||
need to make a call to ``formset.save_m2m()`` to ensure the many-to-many
|
||||
relationships are saved properly.
|
||||
|
||||
Limiting the number of objects editable
|
||||
---------------------------------------
|
||||
|
||||
Similar to regular formsets you can use the ``max_num`` parameter to
|
||||
``modelformset_factory`` to limit the number of forms displayed. With
|
||||
model formsets this will properly limit the query to only select the maximum
|
||||
number of objects needed::
|
||||
|
||||
>>> Author.objects.order_by('name')
|
||||
[<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]
|
||||
|
||||
>>> AuthorFormSet = modelformset_factory(Author, max_num=2, extra=1)
|
||||
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
|
||||
>>> formset.initial
|
||||
[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}]
|
||||
|
||||
If the value of ``max_num`` is less than the total objects returned it will
|
||||
fill the rest with extra forms::
|
||||
|
||||
>>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=1)
|
||||
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_table()
|
||||
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr>
|
||||
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr>
|
||||
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr>
|
||||
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr>
|
||||
|
||||
Using ``inlineformset_factory``
|
||||
-------------------------------
|
||||
|
||||
The ``inlineformset_factory`` is a helper to a common usage pattern of working
|
||||
with related objects through a foreign key. Suppose you have two models
|
||||
``Author`` and ``Book``. You want to create a formset that works with the
|
||||
books of a specific author. Here is how you could accomplish this::
|
||||
|
||||
>>> from django.newforms.models import inlineformset_factory
|
||||
>>> BookFormSet = inlineformset_factory(Author, Book)
|
||||
>>> author = Author.objects.get(name=u'Orson Scott Card')
|
||||
>>> formset = BookFormSet(instance=author)
|
||||
|
|
|
@ -76,6 +76,9 @@ The library deals with these concepts:
|
|||
* **Form** -- A collection of fields that knows how to validate itself and
|
||||
display itself as HTML.
|
||||
|
||||
* **Media** -- A definition of the CSS and JavaScript resources that are
|
||||
required to render a form.
|
||||
|
||||
The library is decoupled from the other Django components, such as the database
|
||||
layer, views and templates. It relies only on Django settings, a couple of
|
||||
``django.utils`` helper functions and Django's internationalization hooks (but
|
||||
|
@ -1864,6 +1867,643 @@ They've been deprecated, but you can still `view the documentation`_.
|
|||
.. _ModelForms documentation: ../modelforms/
|
||||
.. _view the documentation: ../form_for_model/
|
||||
|
||||
Media
|
||||
=====
|
||||
|
||||
Rendering an attractive and easy-to-use web form requires more than just
|
||||
HTML - it also requires CSS stylesheets, and if you want to use fancy
|
||||
"Web2.0" widgets, you may also need to include some JavaScript on each
|
||||
page. The exact combination of CSS and JavaScript that is required for
|
||||
any given page will depend upon the widgets that are in use on that page.
|
||||
|
||||
This is where Django media definitions come in. Django allows you to
|
||||
associate different media files with the forms and widgets that require
|
||||
that media. For example, if you want to use a calendar to render DateFields,
|
||||
you can define a custom Calendar widget. This widget can then be associated
|
||||
with the CSS and Javascript that is required to render the calendar. When
|
||||
the Calendar widget is used on a form, Django is able to identify the CSS and
|
||||
JavaScript files that are required, and provide the list of file names
|
||||
in a form suitable for easy inclusion on your web page.
|
||||
|
||||
.. admonition:: Media and Django Admin
|
||||
|
||||
The Django Admin application defines a number of customized widgets
|
||||
for calendars, filtered selections, and so on. These widgets define
|
||||
media requirements, and the Django Admin uses the custom widgets
|
||||
in place of the Django defaults. The Admin templates will only include
|
||||
those media files that are required to render the widgets on any
|
||||
given page.
|
||||
|
||||
If you like the widgets that the Django Admin application uses,
|
||||
feel free to use them in your own application! They're all stored
|
||||
in ``django.contrib.admin.widgets``.
|
||||
|
||||
.. admonition:: Which JavaScript toolkit?
|
||||
|
||||
Many JavaScript toolkits exist, and many of them include widgets (such
|
||||
as calendar widgets) that can be used to enhance your application.
|
||||
Django has deliberately avoided blessing any one JavaScript toolkit.
|
||||
Each toolkit has its own relative strengths and weaknesses - use
|
||||
whichever toolkit suits your requirements. Django is able to integrate
|
||||
with any JavaScript toolkit.
|
||||
|
||||
Media as a static definition
|
||||
----------------------------
|
||||
|
||||
The easiest way to define media is as a static definition. Using this method,
|
||||
the media declaration is an inner class. The properties of the inner class
|
||||
define the media requirements.
|
||||
|
||||
Here's a simple example::
|
||||
|
||||
class CalendarWidget(forms.TextInput):
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('pretty.css',)
|
||||
}
|
||||
js = ('animations.js', 'actions.js')
|
||||
|
||||
This code defines a ``CalendarWidget``, which will be based on ``TextInput``.
|
||||
Every time the CalendarWidget is used on a form, that form will be directed
|
||||
to include the CSS file ``pretty.css``, and the JavaScript files
|
||||
``animations.js`` and ``actions.js``.
|
||||
|
||||
This static media definition is converted at runtime into a widget property
|
||||
named ``media``. The media for a CalendarWidget instance can be retrieved
|
||||
through this property::
|
||||
|
||||
>>> w = CalendarWidget()
|
||||
>>> print w.media
|
||||
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
|
||||
|
||||
Here's a list of all possible ``Media`` options. There are no required options.
|
||||
|
||||
``css``
|
||||
~~~~~~~
|
||||
|
||||
A dictionary describing the CSS files required for various forms of output
|
||||
media.
|
||||
|
||||
The values in the dictionary should be a tuple/list of file names. See
|
||||
`the section on media paths`_ for details of how to specify paths to media
|
||||
files.
|
||||
|
||||
.. _the section on media paths: `Paths in media definitions`_
|
||||
|
||||
The keys in the dictionary are the output media types. These are the same
|
||||
types accepted by CSS files in media declarations: 'all', 'aural', 'braille',
|
||||
'embossed', 'handheld', 'print', 'projection', 'screen', 'tty' and 'tv'. If
|
||||
you need to have different stylesheets for different media types, provide
|
||||
a list of CSS files for each output medium. The following example would
|
||||
provide two CSS options -- one for the screen, and one for print::
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'screen': ('pretty.css',),
|
||||
'print': ('newspaper.css',)
|
||||
}
|
||||
|
||||
If a group of CSS files are appropriate for multiple output media types,
|
||||
the dictionary key can be a comma separated list of output media types.
|
||||
In the following example, TV's and projectors will have the same media
|
||||
requirements::
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'screen': ('pretty.css',),
|
||||
'tv,projector': ('lo_res.css',),
|
||||
'print': ('newspaper.css',)
|
||||
}
|
||||
|
||||
If this last CSS definition were to be rendered, it would become the following HTML::
|
||||
|
||||
<link href="http://media.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet" />
|
||||
<link href="http://media.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" />
|
||||
<link href="http://media.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />
|
||||
|
||||
``js``
|
||||
~~~~~~
|
||||
|
||||
A tuple describing the required javascript files. See
|
||||
`the section on media paths`_ for details of how to specify paths to media
|
||||
files.
|
||||
|
||||
``extend``
|
||||
~~~~~~~~~~
|
||||
|
||||
A boolean defining inheritance behavior for media declarations.
|
||||
|
||||
By default, any object using a static media definition will inherit all the
|
||||
media associated with the parent widget. This occurs regardless of how the
|
||||
parent defines its media requirements. For example, if we were to extend our
|
||||
basic Calendar widget from the example above::
|
||||
|
||||
class FancyCalendarWidget(CalendarWidget):
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('fancy.css',)
|
||||
}
|
||||
js = ('whizbang.js',)
|
||||
|
||||
>>> w = FancyCalendarWidget()
|
||||
>>> print w.media
|
||||
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
|
||||
|
||||
The FancyCalendar widget inherits all the media from it's parent widget. If
|
||||
you don't want media to be inherited in this way, add an ``extend=False``
|
||||
declaration to the media declaration::
|
||||
|
||||
class FancyCalendar(Calendar):
|
||||
class Media:
|
||||
extend = False
|
||||
css = {
|
||||
'all': ('fancy.css',)
|
||||
}
|
||||
js = ('whizbang.js',)
|
||||
|
||||
>>> w = FancyCalendarWidget()
|
||||
>>> print w.media
|
||||
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
|
||||
|
||||
If you require even more control over media inheritance, define your media
|
||||
using a `dynamic property`_. Dynamic properties give you complete control over
|
||||
which media files are inherited, and which are not.
|
||||
|
||||
.. _dynamic property: `Media as a dynamic property`_
|
||||
|
||||
Media as a dynamic property
|
||||
---------------------------
|
||||
|
||||
If you need to perform some more sophisticated manipulation of media
|
||||
requirements, you can define the media property directly. This is done
|
||||
by defining a model property that returns an instance of ``forms.Media``.
|
||||
The constructor for ``forms.Media`` accepts ``css`` and ``js`` keyword
|
||||
arguments in the same format as that used in a static media definition.
|
||||
|
||||
For example, the static media definition for our Calendar Widget could
|
||||
also be defined in a dynamic fashion::
|
||||
|
||||
class CalendarWidget(forms.TextInput):
|
||||
def _media(self):
|
||||
return forms.Media(css={'all': ('pretty.css',)},
|
||||
js=('animations.js', 'actions.js'))
|
||||
media = property(_media)
|
||||
|
||||
See the section on `Media objects`_ for more details on how to construct
|
||||
return values for dynamic media properties.
|
||||
|
||||
Paths in media definitions
|
||||
--------------------------
|
||||
|
||||
Paths used to specify media can be either relative or absolute. If a path
|
||||
starts with '/', 'http://' or 'https://', it will be interpreted as an absolute
|
||||
path, and left as-is. All other paths will be prepended with the value of
|
||||
``settings.MEDIA_URL``. For example, if the MEDIA_URL for your site was
|
||||
``http://media.example.com/``::
|
||||
|
||||
class CalendarWidget(forms.TextInput):
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('/css/pretty.css',),
|
||||
}
|
||||
js = ('animations.js', 'http://othersite.com/actions.js')
|
||||
|
||||
>>> w = CalendarWidget()
|
||||
>>> print w.media
|
||||
<link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
|
||||
<script type="text/javascript" src="http://othersite.com/actions.js"></script>
|
||||
|
||||
Media objects
|
||||
-------------
|
||||
|
||||
When you interrogate the media attribute of a widget or form, the value that
|
||||
is returned is a ``forms.Media`` object. As we have already seen, the string
|
||||
representation of a Media object is the HTML required to include media
|
||||
in the ``<head>`` block of your HTML page.
|
||||
|
||||
However, Media objects have some other interesting properties.
|
||||
|
||||
Media subsets
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
If you only want media of a particular type, you can use the subscript operator
|
||||
to filter out a medium of interest. For example::
|
||||
|
||||
>>> w = CalendarWidget()
|
||||
>>> print w.media
|
||||
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
|
||||
|
||||
>>> print w.media['css']
|
||||
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
|
||||
|
||||
When you use the subscript operator, the value that is returned is a new
|
||||
Media object -- but one that only contains the media of interest.
|
||||
|
||||
Combining media objects
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Media objects can also be added together. When two media objects are added,
|
||||
the resulting Media object contains the union of the media from both files::
|
||||
|
||||
class CalendarWidget(forms.TextInput):
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('pretty.css',)
|
||||
}
|
||||
js = ('animations.js', 'actions.js')
|
||||
|
||||
class OtherWidget(forms.TextInput):
|
||||
class Media:
|
||||
js = ('whizbang.js',)
|
||||
|
||||
>>> w1 = CalendarWidget()
|
||||
>>> w2 = OtherWidget()
|
||||
>>> print w1+w2
|
||||
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
|
||||
|
||||
Media on Forms
|
||||
--------------
|
||||
|
||||
Widgets aren't the only objects that can have media definitions -- forms
|
||||
can also define media. The rules for media definitions on forms are the
|
||||
same as the rules for widgets: declarations can be static or dynamic;
|
||||
path and inheritance rules for those declarations are exactly the same.
|
||||
|
||||
Regardless of whether you define a media declaration, *all* Form objects
|
||||
have a media property. The default value for this property is the result
|
||||
of adding the media definitions for all widgets that are part of the form::
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
date = DateField(widget=CalendarWidget)
|
||||
name = CharField(max_length=40, widget=OtherWidget)
|
||||
|
||||
>>> f = ContactForm()
|
||||
>>> f.media
|
||||
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
|
||||
|
||||
If you want to associate additional media with a form -- for example, CSS for form
|
||||
layout -- simply add a media declaration to the form::
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
date = DateField(widget=CalendarWidget)
|
||||
name = CharField(max_length=40, widget=OtherWidget)
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('layout.css',)
|
||||
}
|
||||
|
||||
>>> f = ContactForm()
|
||||
>>> f.media
|
||||
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<link href="http://media.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
|
||||
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
|
||||
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
|
||||
|
||||
Formsets
|
||||
========
|
||||
|
||||
A formset is a layer of abstraction to working with multiple forms on the same
|
||||
page. It can be best compared to a data grid. Let's say you have the following
|
||||
form::
|
||||
|
||||
>>> from django import newforms as forms
|
||||
>>> class ArticleForm(forms.Form):
|
||||
... title = forms.CharField()
|
||||
... pub_date = forms.DateField()
|
||||
|
||||
You might want to allow the user to create several articles at once. To create
|
||||
a formset of ``ArticleForm``s you would do::
|
||||
|
||||
>>> from django.newforms.formsets import formset_factory
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm)
|
||||
|
||||
You now have created a formset named ``ArticleFormSet``. The formset gives you
|
||||
the ability to iterate over the forms in the formset and display them as you
|
||||
would with a regular form::
|
||||
|
||||
>>> formset = ArticleFormSet()
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_table()
|
||||
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
|
||||
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
|
||||
|
||||
As you can see it only displayed one form. This is because by default the
|
||||
``formset_factory`` defines one extra form. This can be controlled with the
|
||||
``extra`` parameter::
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
|
||||
|
||||
Using initial data with a formset
|
||||
---------------------------------
|
||||
|
||||
Initial data is what drives the main usability of a formset. As shown above
|
||||
you can define the number of extra forms. What this means is that you are
|
||||
telling the formset how many additional forms to show in addition to the
|
||||
number of forms it generates from the initial data. Lets take a look at an
|
||||
example::
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
|
||||
>>> formset = ArticleFormSet(initial=[
|
||||
... {'title': u'Django is now open source',
|
||||
... 'pub_date': datetime.date.today()},
|
||||
... ])
|
||||
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_table()
|
||||
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr>
|
||||
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr>
|
||||
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
|
||||
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
|
||||
|
||||
There are now a total of three forms showing above. One for the initial data
|
||||
that was passed in and two extra forms. Also note that we are passing in a
|
||||
list of dictionaries as the initial data.
|
||||
|
||||
Limiting the maximum number of forms
|
||||
------------------------------------
|
||||
|
||||
The ``max_num`` parameter to ``formset_factory`` gives you the ability to
|
||||
force the maximum number of forms the formset will display::
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
|
||||
>>> formset = ArticleFormset()
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_table()
|
||||
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
|
||||
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
|
||||
|
||||
The default value of ``max_num`` is ``0`` which is the same as saying put no
|
||||
limit on the number forms displayed.
|
||||
|
||||
Formset validation
|
||||
------------------
|
||||
|
||||
Validation with a formset is about identical to a regular ``Form``. There is
|
||||
an ``is_valid`` method on the formset to provide a convenient way to validate
|
||||
each form in the formset::
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm)
|
||||
>>> formset = ArticleFormSet({})
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
|
||||
We passed in no data to the formset which is resulting in a valid form. The
|
||||
formset is smart enough to ignore extra forms that were not changed. If we
|
||||
attempt to provide an article, but fail to do so::
|
||||
|
||||
>>> data = {
|
||||
... 'form-TOTAL_FORMS': u'1',
|
||||
... 'form-INITIAL_FORMS': u'1',
|
||||
... 'form-0-title': u'Test',
|
||||
... 'form-0-pub_date': u'',
|
||||
... }
|
||||
>>> formset = ArticleFormSet(data)
|
||||
>>> formset.is_valid()
|
||||
False
|
||||
>>> formset.errors
|
||||
[{'pub_date': [u'This field is required.']}]
|
||||
|
||||
As we can see the formset properly performed validation and gave us the
|
||||
expected errors.
|
||||
|
||||
Understanding the ManagementForm
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You may have noticed the additional data that was required in the formset's
|
||||
data above. This data is coming from the ``ManagementForm``. This form is
|
||||
dealt with internally to the formset. If you don't use it, it will result in
|
||||
an exception::
|
||||
|
||||
>>> data = {
|
||||
... 'form-0-title': u'Test',
|
||||
... 'form-0-pub_date': u'',
|
||||
... }
|
||||
>>> formset = ArticleFormSet(data)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
django.newforms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with']
|
||||
|
||||
It is used to keep track of how many form instances are being displayed. If
|
||||
you are adding new forms via javascript, you should increment the count fields
|
||||
in this form as well.
|
||||
|
||||
Custom formset validation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A formset has a ``clean`` method similar to the one on a ``Form`` class. This
|
||||
is where you define your own validation that deals at the formset level::
|
||||
|
||||
>>> from django.newforms.formsets import BaseFormSet
|
||||
|
||||
>>> class BaseArticleFormSet(BaseFormSet):
|
||||
... def clean(self):
|
||||
... raise forms.ValidationError, u'An error occured.'
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
|
||||
>>> formset = ArticleFormSet({})
|
||||
>>> formset.is_valid()
|
||||
False
|
||||
>>> formset.non_form_errors()
|
||||
[u'An error occured.']
|
||||
|
||||
The formset ``clean`` method is called after all the ``Form.clean`` methods
|
||||
have been called. The errors will be found using the ``non_form_errors()``
|
||||
method on the formset.
|
||||
|
||||
Dealing with ordering and deletion of forms
|
||||
-------------------------------------------
|
||||
|
||||
Common use cases with a formset is dealing with ordering and deletion of the
|
||||
form instances. This has been dealt with for you. The ``formset_factory``
|
||||
provides two optional parameters ``can_order`` and ``can_delete`` that will do
|
||||
the extra work of adding the extra fields and providing simpler ways of
|
||||
getting to that data.
|
||||
|
||||
``can_order``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Default: ``False``
|
||||
|
||||
Lets create a formset with the ability to order::
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
|
||||
>>> formset = ArticleFormSet(initial=[
|
||||
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
|
||||
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
|
||||
... ])
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_table()
|
||||
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
|
||||
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="text" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
|
||||
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
|
||||
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="text" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
|
||||
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
|
||||
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="text" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>
|
||||
|
||||
This adds an additional field to each form. This new field is named ``ORDER``
|
||||
and is an ``forms.IntegerField``. For the forms that came from the initial
|
||||
data it automatically assigned them a numeric value. Lets look at what will
|
||||
happen when the user changes these values::
|
||||
|
||||
>>> data = {
|
||||
... 'form-TOTAL_FORMS': u'3',
|
||||
... 'form-INITIAL_FORMS': u'2',
|
||||
... 'form-0-title': u'Article #1',
|
||||
... 'form-0-pub_date': u'2008-05-10',
|
||||
... 'form-0-ORDER': u'2',
|
||||
... 'form-1-title': u'Article #2',
|
||||
... 'form-1-pub_date': u'2008-05-11',
|
||||
... 'form-1-ORDER': u'1',
|
||||
... 'form-2-title': u'Article #3',
|
||||
... 'form-2-pub_date': u'2008-05-01',
|
||||
... 'form-2-ORDER': u'0',
|
||||
... }
|
||||
|
||||
>>> formset = ArticleFormSet(data, initial=[
|
||||
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
|
||||
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
|
||||
... ])
|
||||
>>> formset.is_valid()
|
||||
True
|
||||
>>> for form in formset.ordered_forms:
|
||||
... print form.cleaned_data
|
||||
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': u'Article #3'}
|
||||
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': u'Article #2'}
|
||||
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': u'Article #1'}
|
||||
|
||||
``can_delete``
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Default: ``False``
|
||||
|
||||
Lets create a formset with the ability to delete::
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
|
||||
>>> formset = ArticleFormSet(initial=[
|
||||
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
|
||||
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
|
||||
... ])
|
||||
>>> for form in formset.forms:
|
||||
.... print form.as_table()
|
||||
<input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" />
|
||||
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
|
||||
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr>
|
||||
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
|
||||
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></td></tr>
|
||||
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
|
||||
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></td></tr>
|
||||
|
||||
Similar to ``can_order`` this adds a new field to each form named ``DELETE``
|
||||
and is a ``forms.BooleanField``. When data comes through marking any of the
|
||||
delete fields you can access them with ``deleted_forms``::
|
||||
|
||||
>>> data = {
|
||||
... 'form-TOTAL_FORMS': u'3',
|
||||
... 'form-INITIAL_FORMS': u'2',
|
||||
... 'form-0-title': u'Article #1',
|
||||
... 'form-0-pub_date': u'2008-05-10',
|
||||
... 'form-0-DELETE': u'on',
|
||||
... 'form-1-title': u'Article #2',
|
||||
... 'form-1-pub_date': u'2008-05-11',
|
||||
... 'form-1-DELETE': u'',
|
||||
... 'form-2-title': u'',
|
||||
... 'form-2-pub_date': u'',
|
||||
... 'form-2-DELETE': u'',
|
||||
... }
|
||||
|
||||
>>> formset = ArticleFormSet(data, initial=[
|
||||
... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
|
||||
... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
|
||||
... ])
|
||||
>>> [form.cleaned_data for form in formset.deleted_forms]
|
||||
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}]
|
||||
|
||||
Adding additional fields to a formset
|
||||
-------------------------------------
|
||||
|
||||
If you need to add additional fields to the formset this can be easily
|
||||
accomplished. The formset base class provides an ``add_fields`` method. You
|
||||
can simply override this method to add your own fields or even redefine the
|
||||
default fields/attributes of the order and deletion fields::
|
||||
|
||||
>>> class BaseArticleFormSet(BaseFormSet):
|
||||
... def add_fields(self, form, index):
|
||||
... super(BaseArticleFormSet, self).add_fields(form, index)
|
||||
... form.fields["my_field"] = forms.CharField()
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
|
||||
>>> formset = ArticleFormSet()
|
||||
>>> for form in formset.forms:
|
||||
... print form.as_table()
|
||||
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
|
||||
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr>
|
||||
|
||||
Using a formsets in views and templates
|
||||
---------------------------------------
|
||||
|
||||
Using a formset inside a view is as easy as using a regular ``Form`` class.
|
||||
The only thing you will want to be aware of is making sure to use the
|
||||
management form inside the template. Lets look at a sample view::
|
||||
|
||||
def manage_articles(request):
|
||||
ArticleFormSet = formset_factory(ArticleForm)
|
||||
if request.method == 'POST':
|
||||
formset = ArticleFormSet(request.POST, request.FILES)
|
||||
if formset.is_valid():
|
||||
# do something with the formset.cleaned_data
|
||||
else:
|
||||
formset = ArticleFormSet()
|
||||
return render_to_response('manage_articles.html', {'formset': formset})
|
||||
|
||||
The ``manage_articles.html`` template might look like this::
|
||||
|
||||
<form method="POST" action="">
|
||||
{{ formset.management_form }}
|
||||
<table>
|
||||
{% for form in formset.forms %}
|
||||
{{ form }}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</form>
|
||||
|
||||
However the above can be slightly shortcutted and let the formset itself deal
|
||||
with the management form::
|
||||
|
||||
<form method="POST" action="">
|
||||
<table>
|
||||
{{ formset }}
|
||||
</table>
|
||||
</form>
|
||||
|
||||
The above ends up calling the ``as_table`` method on the formset class.
|
||||
|
||||
More coming soon
|
||||
================
|
||||
|
||||
|
|
|
@ -31,10 +31,10 @@ activate the admin site for your installation, do these three things:
|
|||
* Add ``"django.contrib.admin"`` to your ``INSTALLED_APPS`` setting.
|
||||
* Run ``python manage.py syncdb``. Since you have added a new application
|
||||
to ``INSTALLED_APPS``, the database tables need to be updated.
|
||||
* Edit your ``mysite/urls.py`` file and uncomment the line below
|
||||
"Uncomment this for admin:". This file is a URLconf; we'll dig into
|
||||
URLconfs in the next tutorial. For now, all you need to know is that it
|
||||
maps URL roots to applications.
|
||||
* Edit your ``mysite/urls.py`` file and uncomment the lines below the
|
||||
"Uncomment this for admin:" comments. This file is a URLconf; we'll dig
|
||||
into URLconfs in the next tutorial. For now, all you need to know is that
|
||||
it maps URL roots to applications.
|
||||
|
||||
Start the development server
|
||||
============================
|
||||
|
@ -71,19 +71,13 @@ Make the poll app modifiable in the admin
|
|||
|
||||
But where's our poll app? It's not displayed on the admin index page.
|
||||
|
||||
Just one thing to do: We need to specify in the ``Poll`` model that ``Poll``
|
||||
Just one thing to do: We need to tell the admin that ``Poll``
|
||||
objects have an admin interface. Edit the ``mysite/polls/models.py`` file and
|
||||
make the following change to add an inner ``Admin`` class::
|
||||
add the following to the bottom of the file::
|
||||
|
||||
class Poll(models.Model):
|
||||
# ...
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
The ``class Admin`` will contain all the settings that control how this model
|
||||
appears in the Django admin. All the settings are optional, however, so
|
||||
creating an empty class means "give this object an admin interface using
|
||||
all the default options."
|
||||
from django.contrib import admin
|
||||
|
||||
admin.site.register(Poll)
|
||||
|
||||
Now reload the Django admin page to see your changes. Note that you don't have
|
||||
to restart the development server -- the server will auto-reload your project,
|
||||
|
@ -92,8 +86,8 @@ so any modifications code will be seen immediately in your browser.
|
|||
Explore the free admin functionality
|
||||
====================================
|
||||
|
||||
Now that ``Poll`` has the inner ``Admin`` class, Django knows that it should be
|
||||
displayed on the admin index page:
|
||||
Now that we've registered ``Poll``, Django knows that it should be displayed on
|
||||
the admin index page:
|
||||
|
||||
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin03t.png
|
||||
:alt: Django admin index page, now with polls displayed
|
||||
|
@ -145,17 +139,26 @@ with the timestamp and username of the person who made the change:
|
|||
Customize the admin form
|
||||
========================
|
||||
|
||||
Take a few minutes to marvel at all the code you didn't have to write.
|
||||
Take a few minutes to marvel at all the code you didn't have to write. When you
|
||||
call ``admin.site.register(Poll)``, Django just lets you edit the object and
|
||||
"guess" at how to display it within the admin. Often you'll want to control how
|
||||
the admin looks and works. You'll do this by telling Django about the options
|
||||
you want when you register the object.
|
||||
|
||||
Let's customize this a bit. We can reorder the fields by explicitly adding a
|
||||
``fields`` parameter to ``Admin``::
|
||||
Let's see how this works by reordering the fields on the edit form. Replace the
|
||||
``admin.site.register(Poll)`` line with::
|
||||
|
||||
class Admin:
|
||||
fields = (
|
||||
(None, {'fields': ('pub_date', 'question')}),
|
||||
)
|
||||
class PollAdmin(admin.ModelAdmin):
|
||||
fields = ['pub_date', 'question']
|
||||
|
||||
admin.site.register(Poll, PollAdmin)
|
||||
|
||||
That made the "Publication date" show up first instead of second:
|
||||
You'll follow this pattern -- create a model admin object, then pass it as the
|
||||
second argument to ``admin.site.register()`` -- any time you need to change the
|
||||
admin options for an object.
|
||||
|
||||
This particular change above makes the "Publication date" come before the
|
||||
"Question" field:
|
||||
|
||||
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin07.png
|
||||
:alt: Fields have been reordered
|
||||
|
@ -166,13 +169,15 @@ of fields, choosing an intuitive order is an important usability detail.
|
|||
And speaking of forms with dozens of fields, you might want to split the form
|
||||
up into fieldsets::
|
||||
|
||||
class Admin:
|
||||
fields = (
|
||||
(None, {'fields': ('question',)}),
|
||||
('Date information', {'fields': ('pub_date',)}),
|
||||
)
|
||||
class PollAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {'fields': ['question']}),
|
||||
('Date information', {'fields': ['pub_date']}),
|
||||
]
|
||||
|
||||
admin.site.register(Poll, PollAdmin)
|
||||
|
||||
The first element of each tuple in ``fields`` is the title of the fieldset.
|
||||
The first element of each tuple in ``fieldsets`` is the title of the fieldset.
|
||||
Here's what our form looks like now:
|
||||
|
||||
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin08t.png
|
||||
|
@ -184,11 +189,11 @@ You can assign arbitrary HTML classes to each fieldset. Django provides a
|
|||
This is useful when you have a long form that contains a number of fields that
|
||||
aren't commonly used::
|
||||
|
||||
class Admin:
|
||||
fields = (
|
||||
(None, {'fields': ('question',)}),
|
||||
('Date information', {'fields': ('pub_date',), 'classes': 'collapse'}),
|
||||
)
|
||||
class PollAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {'fields': ['question']}),
|
||||
('Date information', {'fields': ['pub_date'], 'classes': 'pub_date'}),
|
||||
]
|
||||
|
||||
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin09.png
|
||||
:alt: Fieldset is initially collapsed
|
||||
|
@ -201,14 +206,10 @@ the admin page doesn't display choices.
|
|||
|
||||
Yet.
|
||||
|
||||
There are two ways to solve this problem. The first is to give the ``Choice``
|
||||
model its own inner ``Admin`` class, just as we did with ``Poll``. Here's what
|
||||
that would look like::
|
||||
There are two ways to solve this problem. The first register ``Choice`` with the
|
||||
admin just as we did with ``Poll``. That's easy::
|
||||
|
||||
class Choice(models.Model):
|
||||
# ...
|
||||
class Admin:
|
||||
pass
|
||||
admin.site.register(Choice)
|
||||
|
||||
Now "Choices" is an available option in the Django admin. The "Add choice" form
|
||||
looks like this:
|
||||
|
@ -220,33 +221,35 @@ In that form, the "Poll" field is a select box containing every poll in the
|
|||
database. Django knows that a ``ForeignKey`` should be represented in the admin
|
||||
as a ``<select>`` box. In our case, only one poll exists at this point.
|
||||
|
||||
Also note the "Add Another" link next to "Poll." Every object with a ForeignKey
|
||||
relationship to another gets this for free. When you click "Add Another," you'll
|
||||
get a popup window with the "Add poll" form. If you add a poll in that window
|
||||
and click "Save," Django will save the poll to the database and dynamically add
|
||||
it as the selected choice on the "Add choice" form you're looking at.
|
||||
Also note the "Add Another" link next to "Poll." Every object with a
|
||||
``ForeignKey`` relationship to another gets this for free. When you click "Add
|
||||
Another," you'll get a popup window with the "Add poll" form. If you add a poll
|
||||
in that window and click "Save," Django will save the poll to the database and
|
||||
dynamically add it as the selected choice on the "Add choice" form you're
|
||||
looking at.
|
||||
|
||||
But, really, this is an inefficient way of adding Choice objects to the system.
|
||||
It'd be better if you could add a bunch of Choices directly when you create the
|
||||
Poll object. Let's make that happen.
|
||||
|
||||
Remove the ``Admin`` for the Choice model. Then, edit the ``ForeignKey(Poll)``
|
||||
field like so::
|
||||
Remove the ``register()`` call for the Choice model. Then, edit the ``Poll``
|
||||
registration code to read::
|
||||
|
||||
poll = models.ForeignKey(Poll, edit_inline=models.STACKED, num_in_admin=3)
|
||||
class ChoiceInline(admin.StackedInline):
|
||||
model = Choice
|
||||
extra = 3
|
||||
|
||||
class PollAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {'fields': ['question']}),
|
||||
('Date information', {'fields': ['pub_date'], 'classes': 'pub_date'}),
|
||||
]
|
||||
inlines = [ChoiceInline]
|
||||
|
||||
admin.site.register(Poll, PollAdmin)
|
||||
|
||||
This tells Django: "Choice objects are edited on the Poll admin page. By
|
||||
default, provide enough fields for 3 Choices."
|
||||
|
||||
Then change the other fields in ``Choice`` to give them ``core=True``::
|
||||
|
||||
choice = models.CharField(max_length=200, core=True)
|
||||
votes = models.IntegerField(core=True)
|
||||
|
||||
This tells Django: "When you edit a Choice on the Poll admin page, the 'choice'
|
||||
and 'votes' fields are required. The presence of at least one of them signifies
|
||||
the addition of a new Choice object, and clearing both of them signifies the
|
||||
deletion of that existing Choice object."
|
||||
default, provide enough fields for 3 choices."
|
||||
|
||||
Load the "Add poll" page to see how that looks:
|
||||
|
||||
|
@ -255,19 +258,18 @@ Load the "Add poll" page to see how that looks:
|
|||
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin11.png
|
||||
|
||||
It works like this: There are three slots for related Choices -- as specified
|
||||
by ``num_in_admin`` -- but each time you come back to the "Change" page for an
|
||||
already-created object, you get one extra slot. (This means there's no
|
||||
hard-coded limit on how many related objects can be added.) If you wanted space
|
||||
for three extra Choices each time you changed the poll, you'd use
|
||||
``num_extra_on_change=3``.
|
||||
by ``extra`` -- and each time you come back to the "Change" page for an
|
||||
already-created object, you get another three extra slots.
|
||||
|
||||
One small problem, though. It takes a lot of screen space to display all the
|
||||
fields for entering related Choice objects. For that reason, Django offers an
|
||||
alternate way of displaying inline related objects::
|
||||
tabular way of displaying inline related objects; you just need to change
|
||||
the ``ChoiceInline`` declaration to read::
|
||||
|
||||
poll = models.ForeignKey(Poll, edit_inline=models.TABULAR, num_in_admin=3)
|
||||
class ChoiceInline(admin.TabularInline):
|
||||
#...
|
||||
|
||||
With that ``edit_inline=models.TABULAR`` (instead of ``models.STACKED``), the
|
||||
With that ``TabularInline`` (instead of ``StackedInline``), the
|
||||
related objects are displayed in a more compact, table-based format:
|
||||
|
||||
.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin12.png
|
||||
|
@ -285,21 +287,21 @@ Here's what it looks like at this point:
|
|||
:alt: Polls change list page
|
||||
:target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04.png
|
||||
|
||||
By default, Django displays the ``str()`` of each object. But sometimes it'd
|
||||
be more helpful if we could display individual fields. To do that, use the
|
||||
``list_display`` option, which is a tuple of field names to display, as columns,
|
||||
on the change list page for the object::
|
||||
By default, Django displays the ``str()`` of each object. But sometimes it'd be
|
||||
more helpful if we could display individual fields. To do that, use the
|
||||
``list_display`` admin option, which is a tuple of field names to display, as
|
||||
columns, on the change list page for the object::
|
||||
|
||||
class Poll(models.Model):
|
||||
class PollAdmin(admin.ModelAdmin):
|
||||
# ...
|
||||
class Admin:
|
||||
# ...
|
||||
list_display = ('question', 'pub_date')
|
||||
list_display = ('question', 'pub_date')
|
||||
|
||||
Just for good measure, let's also include the ``was_published_today`` custom
|
||||
method from Tutorial 1::
|
||||
|
||||
list_display = ('question', 'pub_date', 'was_published_today')
|
||||
class PollAdmin(admin.ModelAdmin):
|
||||
# ...
|
||||
list_display = ('question', 'pub_date', 'was_published_today')
|
||||
|
||||
Now the poll change list page looks like this:
|
||||
|
||||
|
@ -318,9 +320,8 @@ method a ``short_description`` attribute::
|
|||
return self.pub_date.date() == datetime.date.today()
|
||||
was_published_today.short_description = 'Published today?'
|
||||
|
||||
|
||||
Let's add another improvement to the Poll change list page: Filters. Add the
|
||||
following line to ``Poll.Admin``::
|
||||
following line to ``PollAdmin``::
|
||||
|
||||
list_filter = ['pub_date']
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue