diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/admin.txt | 678 | ||||
| -rw-r--r-- | docs/authentication.txt | 44 | ||||
| -rw-r--r-- | docs/custom_model_fields.txt | 1 | ||||
| -rw-r--r-- | docs/localflavor.txt | 4 | ||||
| -rw-r--r-- | docs/model-api.txt | 478 | ||||
| -rw-r--r-- | docs/modelforms.txt | 122 | ||||
| -rw-r--r-- | docs/newforms.txt | 640 | ||||
| -rw-r--r-- | docs/tutorial02.txt | 163 |
8 files changed, 1546 insertions, 584 deletions
diff --git a/docs/admin.txt b/docs/admin.txt new file mode 100644 index 0000000000..fbdd19bc90 --- /dev/null +++ b/docs/admin.txt @@ -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), + ) diff --git a/docs/authentication.txt b/docs/authentication.txt index 4ec367a8b5..cd76731bc4 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -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 --------------------------------------------------- diff --git a/docs/custom_model_fields.txt b/docs/custom_model_fields.txt index 2b344921ef..cbaac873e3 100644 --- a/docs/custom_model_fields.txt +++ b/docs/custom_model_fields.txt @@ -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 diff --git a/docs/localflavor.txt b/docs/localflavor.txt index b4bccfb138..5a2e5b8fda 100644 --- a/docs/localflavor.txt +++ b/docs/localflavor.txt @@ -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 =================== diff --git a/docs/model-api.txt b/docs/model-api.txt index 4e7b5c3096..4accad122a 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -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 ======== diff --git a/docs/modelforms.txt b/docs/modelforms.txt index 73335a03a2..9c06bc409d 100644 --- a/docs/modelforms.txt +++ b/docs/modelforms.txt @@ -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) diff --git a/docs/newforms.txt b/docs/newforms.txt index 4240fe9985..530c9ce828 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -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 ================ diff --git a/docs/tutorial02.txt b/docs/tutorial02.txt index 42c9800591..c69fd1459f 100644 --- a/docs/tutorial02.txt +++ b/docs/tutorial02.txt @@ -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) - -This tells Django: "Choice objects are edited on the Poll admin page. By -default, provide enough fields for 3 Choices." + 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] -Then change the other fields in ``Choice`` to give them ``core=True``:: + admin.site.register(Poll, PollAdmin) - 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." +This tells Django: "Choice objects are edited on the Poll admin page. By +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'] |
