diff options
author | Nishanth Amuluru | 2011-01-08 11:20:57 +0530 |
---|---|---|
committer | Nishanth Amuluru | 2011-01-08 11:20:57 +0530 |
commit | 65411d01d448ff0cd4abd14eee14cf60b5f8fc20 (patch) | |
tree | b4c404363c4c63a61d6e2f8bd26c5b057c1fb09d /parts/django/docs/topics/forms | |
parent | 2e35094d43b4cc6974172e1febf76abb50f086ec (diff) | |
download | pytask-65411d01d448ff0cd4abd14eee14cf60b5f8fc20.tar.gz pytask-65411d01d448ff0cd4abd14eee14cf60b5f8fc20.tar.bz2 pytask-65411d01d448ff0cd4abd14eee14cf60b5f8fc20.zip |
Added buildout stuff and made changes accordingly
--HG--
rename : profile/management/__init__.py => eggs/djangorecipe-0.20-py2.6.egg/EGG-INFO/dependency_links.txt
rename : profile/management/__init__.py => eggs/djangorecipe-0.20-py2.6.egg/EGG-INFO/not-zip-safe
rename : profile/management/__init__.py => eggs/infrae.subversion-1.4.5-py2.6.egg/EGG-INFO/dependency_links.txt
rename : profile/management/__init__.py => eggs/infrae.subversion-1.4.5-py2.6.egg/EGG-INFO/not-zip-safe
rename : profile/management/__init__.py => eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/EGG-INFO/dependency_links.txt
rename : profile/management/__init__.py => eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/EGG-INFO/not-zip-safe
rename : profile/management/__init__.py => eggs/py-1.4.0-py2.6.egg/EGG-INFO/dependency_links.txt
rename : profile/management/__init__.py => eggs/py-1.4.0-py2.6.egg/EGG-INFO/not-zip-safe
rename : profile/management/__init__.py => eggs/zc.buildout-1.5.2-py2.6.egg/EGG-INFO/dependency_links.txt
rename : profile/management/__init__.py => eggs/zc.buildout-1.5.2-py2.6.egg/EGG-INFO/not-zip-safe
rename : profile/management/__init__.py => eggs/zc.recipe.egg-1.3.2-py2.6.egg/EGG-INFO/dependency_links.txt
rename : profile/management/__init__.py => eggs/zc.recipe.egg-1.3.2-py2.6.egg/EGG-INFO/not-zip-safe
rename : profile/management/__init__.py => parts/django/Django.egg-info/dependency_links.txt
rename : taskapp/models.py => parts/django/django/conf/app_template/models.py
rename : taskapp/tests.py => parts/django/django/conf/app_template/tests.py
rename : taskapp/views.py => parts/django/django/conf/app_template/views.py
rename : taskapp/views.py => parts/django/django/contrib/gis/tests/geo3d/views.py
rename : profile/management/__init__.py => parts/django/tests/modeltests/delete/__init__.py
rename : profile/management/__init__.py => parts/django/tests/modeltests/files/__init__.py
rename : profile/management/__init__.py => parts/django/tests/modeltests/invalid_models/__init__.py
rename : profile/management/__init__.py => parts/django/tests/modeltests/m2m_signals/__init__.py
rename : profile/management/__init__.py => parts/django/tests/modeltests/model_package/__init__.py
rename : profile/management/__init__.py => parts/django/tests/regressiontests/bash_completion/__init__.py
rename : profile/management/__init__.py => parts/django/tests/regressiontests/bash_completion/management/__init__.py
rename : profile/management/__init__.py => parts/django/tests/regressiontests/bash_completion/management/commands/__init__.py
rename : profile/management/__init__.py => parts/django/tests/regressiontests/bash_completion/models.py
rename : profile/management/__init__.py => parts/django/tests/regressiontests/delete_regress/__init__.py
rename : profile/management/__init__.py => parts/django/tests/regressiontests/file_storage/__init__.py
rename : profile/management/__init__.py => parts/django/tests/regressiontests/max_lengths/__init__.py
rename : profile/forms.py => pytask/profile/forms.py
rename : profile/management/__init__.py => pytask/profile/management/__init__.py
rename : profile/management/commands/seed_db.py => pytask/profile/management/commands/seed_db.py
rename : profile/models.py => pytask/profile/models.py
rename : profile/templatetags/user_tags.py => pytask/profile/templatetags/user_tags.py
rename : taskapp/tests.py => pytask/profile/tests.py
rename : profile/urls.py => pytask/profile/urls.py
rename : profile/utils.py => pytask/profile/utils.py
rename : profile/views.py => pytask/profile/views.py
rename : static/css/base.css => pytask/static/css/base.css
rename : taskapp/tests.py => pytask/taskapp/tests.py
rename : taskapp/views.py => pytask/taskapp/views.py
rename : templates/base.html => pytask/templates/base.html
rename : templates/profile/browse_notifications.html => pytask/templates/profile/browse_notifications.html
rename : templates/profile/edit.html => pytask/templates/profile/edit.html
rename : templates/profile/view.html => pytask/templates/profile/view.html
rename : templates/profile/view_notification.html => pytask/templates/profile/view_notification.html
rename : templates/registration/activate.html => pytask/templates/registration/activate.html
rename : templates/registration/activation_email.txt => pytask/templates/registration/activation_email.txt
rename : templates/registration/activation_email_subject.txt => pytask/templates/registration/activation_email_subject.txt
rename : templates/registration/logged_out.html => pytask/templates/registration/logged_out.html
rename : templates/registration/login.html => pytask/templates/registration/login.html
rename : templates/registration/logout.html => pytask/templates/registration/logout.html
rename : templates/registration/password_change_done.html => pytask/templates/registration/password_change_done.html
rename : templates/registration/password_change_form.html => pytask/templates/registration/password_change_form.html
rename : templates/registration/password_reset_complete.html => pytask/templates/registration/password_reset_complete.html
rename : templates/registration/password_reset_confirm.html => pytask/templates/registration/password_reset_confirm.html
rename : templates/registration/password_reset_done.html => pytask/templates/registration/password_reset_done.html
rename : templates/registration/password_reset_email.html => pytask/templates/registration/password_reset_email.html
rename : templates/registration/password_reset_form.html => pytask/templates/registration/password_reset_form.html
rename : templates/registration/registration_complete.html => pytask/templates/registration/registration_complete.html
rename : templates/registration/registration_form.html => pytask/templates/registration/registration_form.html
rename : utils.py => pytask/utils.py
Diffstat (limited to 'parts/django/docs/topics/forms')
-rw-r--r-- | parts/django/docs/topics/forms/formsets.txt | 440 | ||||
-rw-r--r-- | parts/django/docs/topics/forms/index.txt | 402 | ||||
-rw-r--r-- | parts/django/docs/topics/forms/media.txt | 309 | ||||
-rw-r--r-- | parts/django/docs/topics/forms/modelforms.txt | 885 |
4 files changed, 2036 insertions, 0 deletions
diff --git a/parts/django/docs/topics/forms/formsets.txt b/parts/django/docs/topics/forms/formsets.txt new file mode 100644 index 0000000..72296bc --- /dev/null +++ b/parts/django/docs/topics/forms/formsets.txt @@ -0,0 +1,440 @@ +.. _formsets: + +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 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 out of an ``ArticleForm`` you would do:: + + >>> from django.forms.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 empty form. The number of empty forms +that is displayed is controlled by the ``extra`` parameter. By default, +``formset_factory`` defines one extra form; the following example will +display two blank forms:: + + >>> 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. + +.. seealso:: + + :ref:`Creating formsets from models with model formsets <model-formsets>`. + +.. _formsets-max-num: + +Limiting the maximum number of forms +------------------------------------ + +The ``max_num`` parameter to ``formset_factory`` gives you the ability to +limit the maximum number of empty 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> + +.. versionchanged:: 1.2 + +If the value of ``max_num`` is greater than the number of existing +objects, up to ``extra`` additional blank forms will be added to the formset, +so long as the total number of forms does not exceed ``max_num``. + +A ``max_num`` value of ``None`` (the default) puts no limit on the number of +forms displayed. Please note that the default value of ``max_num`` was changed +from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value. + +Formset validation +------------------ + +Validation with a formset is almost identical to a regular ``Form``. There is +an ``is_valid`` method on the formset to provide a convenient way to validate +all forms 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 +provide an invalid article:: + + >>> data = { + ... 'form-TOTAL_FORMS': u'2', + ... 'form-INITIAL_FORMS': u'0', + ... 'form-MAX_NUM_FORMS': u'', + ... 'form-0-title': u'Test', + ... 'form-0-pub_date': u'16 June 1904', + ... 'form-1-title': u'Test', + ... 'form-1-pub_date': u'', # <-- this date is missing but required + ... } + >>> formset = ArticleFormSet(data) + >>> formset.is_valid() + False + >>> formset.errors + [{}, {'pub_date': [u'This field is required.']}] + +As we can see, ``formset.errors`` is a list whose entries correspond to the +forms in the formset. Validation was performed for each of the two forms, and +the expected error message appears for the second item. + +.. _understanding-the-managementform: + +Understanding the ManagementForm +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may have noticed the additional data (``form-TOTAL_FORMS``, +``form-INITIAL_FORMS`` and ``form-MAX_NUM_FORMS``) that was required +in the formset's data above. This data is required for the +``ManagementForm``. This form is used by the formset to manage the +collection of forms contained in the formset. If you don't provide +this management data, an exception will be raised:: + + >>> data = { + ... 'form-0-title': u'Test', + ... 'form-0-pub_date': u'', + ... } + >>> formset = ArticleFormSet(data) + Traceback (most recent call last): + ... + django.forms.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. + +The management form is available as an attribute of the formset +itself. When rendering a formset in a template, you can include all +the management data by rendering ``{{ my_formset.management_form }}`` +(substituting the name of your formset as appropriate). + +.. versionadded:: 1.1 + +``total_form_count`` and ``initial_form_count`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``BaseFormSet`` has a couple of methods that are closely related to the +``ManagementForm``, ``total_form_count`` and ``initial_form_count``. + +``total_form_count`` returns the total number of forms in this formset. +``initial_form_count`` returns the number of forms in the formset that were +pre-filled, and is also used to determine how many forms are required. You +will probably never need to override either of these methods, so please be +sure you understand what they do before doing so. + +.. versionadded:: 1.2 + +``empty_form`` +~~~~~~~~~~~~~~ + +``BaseFormSet`` provides an additional attribute ``empty_form`` which returns +a form instance with a prefix of ``__prefix__`` for easier use in dynamic +forms with JavaScript. + +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 works at the formset level:: + + >>> from django.forms.formsets import BaseFormSet + + >>> class BaseArticleFormSet(BaseFormSet): + ... def clean(self): + ... """Checks that no two articles have the same title.""" + ... if any(self.errors): + ... # Don't bother validating the formset unless each form is valid on its own + ... return + ... titles = [] + ... for i in range(0, self.total_form_count()): + ... form = self.forms[i] + ... title = form.cleaned_data['title'] + ... if title in titles: + ... raise forms.ValidationError, "Articles in a set must have distinct titles." + ... titles.append(title) + + >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet) + >>> data = { + ... 'form-TOTAL_FORMS': u'2', + ... 'form-INITIAL_FORMS': u'0', + ... 'form-MAX_NUM_FORMS': u'', + ... 'form-0-title': u'Test', + ... 'form-0-pub_date': u'16 June 1904', + ... 'form-1-title': u'Test', + ... 'form-1-pub_date': u'23 June 1912', + ... } + >>> formset = ArticleFormSet(data) + >>> formset.is_valid() + False + >>> formset.errors + [{}, {}] + >>> formset.non_form_errors() + [u'Articles in a set must have distinct titles.'] + +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-MAX_NUM_FORMS': u'', + ... '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" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_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-MAX_NUM_FORMS': u'', + ... '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 formset 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. Let's look at a sample view: + +.. code-block:: python + + 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 + pass + else: + formset = ArticleFormSet() + return render_to_response('manage_articles.html', {'formset': formset}) + +The ``manage_articles.html`` template might look like this: + +.. code-block:: html+django + + <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: + +.. code-block:: html+django + + <form method="post" action=""> + <table> + {{ formset }} + </table> + </form> + +The above ends up calling the ``as_table`` method on the formset class. + +Using more than one formset in a view +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You are able to use more than one formset in a view if you like. Formsets +borrow much of its behavior from forms. With that said you are able to use +``prefix`` to prefix formset form field names with a given value to allow +more than one formset to be sent to a view without name clashing. Lets take +a look at how this might be accomplished: + +.. code-block:: python + + def manage_articles(request): + ArticleFormSet = formset_factory(ArticleForm) + BookFormSet = formset_factory(BookForm) + if request.method == 'POST': + article_formset = ArticleFormSet(request.POST, request.FILES, prefix='articles') + book_formset = BookFormSet(request.POST, request.FILES, prefix='books') + if article_formset.is_valid() and book_formset.is_valid(): + # do something with the cleaned_data on the formsets. + pass + else: + article_formset = ArticleFormSet(prefix='articles') + book_formset = BookFormSet(prefix='books') + return render_to_response('manage_articles.html', { + 'article_formset': article_formset, + 'book_formset': book_formset, + }) + +You would then render the formsets as normal. It is important to point out +that you need to pass ``prefix`` on both the POST and non-POST cases so that +it is rendered and processed correctly. diff --git a/parts/django/docs/topics/forms/index.txt b/parts/django/docs/topics/forms/index.txt new file mode 100644 index 0000000..30b09c0 --- /dev/null +++ b/parts/django/docs/topics/forms/index.txt @@ -0,0 +1,402 @@ +================== +Working with forms +================== + +.. admonition:: About this document + + This document provides an introduction to Django's form handling features. + For a more detailed look at specific areas of the forms API, see + :doc:`/ref/forms/api`, :doc:`/ref/forms/fields`, and + :doc:`/ref/forms/validation`. + +.. highlightlang:: html+django + +``django.forms`` is Django's form-handling library. + +While it is possible to process form submissions just using Django's +:class:`~django.http.HttpRequest` class, using the form library takes care of a +number of common form-related tasks. Using it, you can: + + 1. Display an HTML form with automatically generated form widgets. + 2. Check submitted data against a set of validation rules. + 3. Redisplay a form in the case of validation errors. + 4. Convert submitted form data to the relevant Python data types. + +Overview +======== + +The library deals with these concepts: + +.. glossary:: + + Widget + A class that corresponds to an HTML form widget, e.g. + ``<input type="text">`` or ``<textarea>``. This handles rendering of the + widget as HTML. + + Field + A class that is responsible for doing validation, e.g. + an ``EmailField`` that makes sure its data is a valid e-mail address. + + Form + A collection of fields that knows how to validate itself and + display itself as HTML. + + Form Media + 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 +you're not required to be using internationalization features to use this +library). + +Form objects +============ + +A Form object encapsulates a sequence of form fields and a collection of +validation rules that must be fulfilled in order for the form to be accepted. +Form classes are created as subclasses of ``django.forms.Form`` and +make use of a declarative style that you'll be familiar with if you've used +Django's database models. + +For example, consider a form used to implement "contact me" functionality on a +personal Web site: + +.. code-block:: python + + from django import forms + + class ContactForm(forms.Form): + subject = forms.CharField(max_length=100) + message = forms.CharField() + sender = forms.EmailField() + cc_myself = forms.BooleanField(required=False) + +A form is composed of ``Field`` objects. In this case, our form has four +fields: ``subject``, ``message``, ``sender`` and ``cc_myself``. ``CharField``, +``EmailField`` and ``BooleanField`` are just three of the available field types; +a full list can be found in :doc:`/ref/forms/fields`. + +If your form is going to be used to directly add or edit a Django model, you can +use a :doc:`ModelForm </topics/forms/modelforms>` to avoid duplicating your model +description. + +Using a form in a view +---------------------- + +The standard pattern for processing a form in a view looks like this: + +.. code-block:: python + + def contact(request): + if request.method == 'POST': # If the form has been submitted... + form = ContactForm(request.POST) # A form bound to the POST data + if form.is_valid(): # All validation rules pass + # Process the data in form.cleaned_data + # ... + return HttpResponseRedirect('/thanks/') # Redirect after POST + else: + form = ContactForm() # An unbound form + + return render_to_response('contact.html', { + 'form': form, + }) + + +There are three code paths here: + + 1. If the form has not been submitted, an unbound instance of ContactForm is + created and passed to the template. + 2. If the form has been submitted, a bound instance of the form is created + using ``request.POST``. If the submitted data is valid, it is processed + and the user is re-directed to a "thanks" page. + 3. If the form has been submitted but is invalid, the bound form instance is + passed on to the template. + +.. versionchanged:: 1.0 + The ``cleaned_data`` attribute was called ``clean_data`` in earlier releases. + +The distinction between **bound** and **unbound** forms is important. An unbound +form does not have any data associated with it; when rendered to the user, it +will be empty or will contain default values. A bound form does have submitted +data, and hence can be used to tell if that data is valid. If an invalid bound +form is rendered it can include inline error messages telling the user where +they went wrong. + +See :ref:`ref-forms-api-bound-unbound` for further information on the +differences between bound and unbound forms. + +Handling file uploads with a form +--------------------------------- + +To see how to handle file uploads with your form see +:ref:`binding-uploaded-files` for more information. + +Processing the data from a form +------------------------------- + +Once ``is_valid()`` returns ``True``, you can process the form submission safe +in the knowledge that it conforms to the validation rules defined by your form. +While you could access ``request.POST`` directly at this point, it is better to +access ``form.cleaned_data``. This data has not only been validated but will +also be converted in to the relevant Python types for you. In the above example, +``cc_myself`` will be a boolean value. Likewise, fields such as ``IntegerField`` +and ``FloatField`` convert values to a Python int and float respectively. + +Extending the above example, here's how the form data could be processed: + +.. code-block:: python + + if form.is_valid(): + subject = form.cleaned_data['subject'] + message = form.cleaned_data['message'] + sender = form.cleaned_data['sender'] + cc_myself = form.cleaned_data['cc_myself'] + + recipients = ['info@example.com'] + if cc_myself: + recipients.append(sender) + + from django.core.mail import send_mail + send_mail(subject, message, sender, recipients) + return HttpResponseRedirect('/thanks/') # Redirect after POST + +For more on sending e-mail from Django, see :doc:`/topics/email`. + +Displaying a form using a template +---------------------------------- + +Forms are designed to work with the Django template language. In the above +example, we passed our ``ContactForm`` instance to the template using the +context variable ``form``. Here's a simple example template:: + + <form action="/contact/" method="post"> + {{ form.as_p }} + <input type="submit" value="Submit" /> + </form> + +The form only outputs its own fields; it is up to you to provide the surrounding +``<form>`` tags and the submit button. + +``form.as_p`` will output the form with each form field and accompanying label +wrapped in a paragraph. Here's the output for our example template:: + + <form action="/contact/" method="post"> + <p><label for="id_subject">Subject:</label> + <input id="id_subject" type="text" name="subject" maxlength="100" /></p> + <p><label for="id_message">Message:</label> + <input type="text" name="message" id="id_message" /></p> + <p><label for="id_sender">Sender:</label> + <input type="text" name="sender" id="id_sender" /></p> + <p><label for="id_cc_myself">Cc myself:</label> + <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p> + <input type="submit" value="Submit" /> + </form> + +Note that each form field has an ID attribute set to ``id_<field-name>``, which +is referenced by the accompanying label tag. This is important for ensuring +forms are accessible to assistive technology such as screen reader software. You +can also :ref:`customize the way in which labels and ids are generated +<ref-forms-api-configuring-label>`. + +You can also use ``form.as_table`` to output table rows (you'll need to provide +your own ``<table>`` tags) and ``form.as_ul`` to output list items. + +Customizing the form template +----------------------------- + +If the default generated HTML is not to your taste, you can completely customize +the way a form is presented using the Django template language. Extending the +above example:: + + <form action="/contact/" method="post"> + {{ form.non_field_errors }} + <div class="fieldWrapper"> + {{ form.subject.errors }} + <label for="id_subject">E-mail subject:</label> + {{ form.subject }} + </div> + <div class="fieldWrapper"> + {{ form.message.errors }} + <label for="id_message">Your message:</label> + {{ form.message }} + </div> + <div class="fieldWrapper"> + {{ form.sender.errors }} + <label for="id_sender">Your email address:</label> + {{ form.sender }} + </div> + <div class="fieldWrapper"> + {{ form.cc_myself.errors }} + <label for="id_cc_myself">CC yourself?</label> + {{ form.cc_myself }} + </div> + <p><input type="submit" value="Send message" /></p> + </form> + +Each named form-field can be output to the template using +``{{ form.name_of_field }}``, which will produce the HTML needed to display the +form widget. Using ``{{ form.name_of_field.errors }}`` displays a list of form +errors, rendered as an unordered list. This might look like:: + + <ul class="errorlist"> + <li>Sender is required.</li> + </ul> + +The list has a CSS class of ``errorlist`` to allow you to style its appearance. +If you wish to further customize the display of errors you can do so by looping +over them:: + + {% if form.subject.errors %} + <ol> + {% for error in form.subject.errors %} + <li><strong>{{ error|escape }}</strong></li> + {% endfor %} + </ol> + {% endif %} + +Looping over the form's fields +------------------------------ + +If you're using the same HTML for each of your form fields, you can reduce +duplicate code by looping through each field in turn using a ``{% for %}`` +loop:: + + <form action="/contact/" method="post"> + {% for field in form %} + <div class="fieldWrapper"> + {{ field.errors }} + {{ field.label_tag }}: {{ field }} + </div> + {% endfor %} + <p><input type="submit" value="Send message" /></p> + </form> + +Within this loop, ``{{ field }}`` is an instance of :class:`BoundField`. +``BoundField`` also has the following attributes, which can be useful in your +templates: + + ``{{ field.label }}`` + The label of the field, e.g. ``E-mail address``. + + ``{{ field.label_tag }}`` + The field's label wrapped in the appropriate HTML ``<label>`` tag, + e.g. ``<label for="id_email">E-mail address</label>`` + + ``{{ field.html_name }}`` + The name of the field that will be used in the input element's name + field. This takes the form prefix into account, if it has been set. + + ``{{ field.help_text }}`` + Any help text that has been associated with the field. + + ``{{ field.errors }}`` + Outputs a ``<ul class="errorlist">`` containing any validation errors + corresponding to this field. You can customize the presentation of + the errors with a ``{% for error in field.errors %}`` loop. In this + case, each object in the loop is a simple string containing the error + message. + + ``field.is_hidden`` + This attribute is ``True`` if the form field is a hidden field and + ``False`` otherwise. It's not particularly useful as a template + variable, but could be useful in conditional tests such as:: + + {% if field.is_hidden %} + {# Do something special #} + {% endif %} + +Looping over hidden and visible fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you're manually laying out a form in a template, as opposed to relying on +Django's default form layout, you might want to treat ``<input type="hidden">`` +fields differently than non-hidden fields. For example, because hidden fields +don't display anything, putting error messages "next to" the field could cause +confusion for your users -- so errors for those fields should be handled +differently. + +Django provides two methods on a form that allow you to loop over the hidden +and visible fields independently: ``hidden_fields()`` and +``visible_fields()``. Here's a modification of an earlier example that uses +these two methods:: + + <form action="/contact/" method="post"> + {% for field in form.visible_fields %} + <div class="fieldWrapper"> + + {# Include the hidden fields in the form #} + {% if forloop.first %} + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + {% endif %} + + {{ field.errors }} + {{ field.label_tag }}: {{ field }} + </div> + {% endfor %} + <p><input type="submit" value="Send message" /></p> + </form> + +This example does not handle any errors in the hidden fields. Usually, an +error in a hidden field is a sign of form tampering, since normal form +interaction won't alter them. However, you could easily insert some error +displays for those form errors, as well. + +.. versionadded:: 1.1 + The ``hidden_fields`` and ``visible_fields`` methods are new in Django + 1.1. + +Reusable form templates +----------------------- + +If your site uses the same rendering logic for forms in multiple places, you +can reduce duplication by saving the form's loop in a standalone template and +using the :ttag:`include` tag to reuse it in other templates:: + + <form action="/contact/" method="post"> + {% include "form_snippet.html" %} + <p><input type="submit" value="Send message" /></p> + </form> + + # In form_snippet.html: + + {% for field in form %} + <div class="fieldWrapper"> + {{ field.errors }} + {{ field.label_tag }}: {{ field }} + </div> + {% endfor %} + +If the form object passed to a template has a different name within the +context, you can alias it using the :ttag:`with` tag:: + + <form action="/comments/add/" method="post"> + {% with comment_form as form %} + {% include "form_snippet.html" %} + {% endwith %} + <p><input type="submit" value="Submit comment" /></p> + </form> + +If you find yourself doing this often, you might consider creating a custom +:ref:`inclusion tag<howto-custom-template-tags-inclusion-tags>`. + +Further topics +============== + +This covers the basics, but forms can do a whole lot more: + +.. toctree:: + :maxdepth: 2 + + modelforms + formsets + media + +.. seealso:: + + :doc:`The Forms Reference </ref/forms/index>` + Covers the full API reference, including form fields, form widgets, + and form and field validation. diff --git a/parts/django/docs/topics/forms/media.txt b/parts/django/docs/topics/forms/media.txt new file mode 100644 index 0000000..fe70894 --- /dev/null +++ b/parts/django/docs/topics/forms/media.txt @@ -0,0 +1,309 @@ +Form 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 FancyCalendarWidget(CalendarWidget): + ... 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 widget 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.media + w2.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> + +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> diff --git a/parts/django/docs/topics/forms/modelforms.txt b/parts/django/docs/topics/forms/modelforms.txt new file mode 100644 index 0000000..23ed9a7 --- /dev/null +++ b/parts/django/docs/topics/forms/modelforms.txt @@ -0,0 +1,885 @@ +========================== +Creating forms from models +========================== + +.. module:: django.forms.models + :synopsis: ModelForm and ModelFormset. + +.. currentmodule:: django.forms + +``ModelForm`` +============= +.. class:: ModelForm + +If you're building a database-driven app, chances are you'll have forms that +map closely to Django models. For instance, you might have a ``BlogComment`` +model, and you want to create a form that lets people submit comments. In this +case, it would be redundant to define the field types in your form, because +you've already defined the fields in your model. + +For this reason, Django provides a helper class that let you create a ``Form`` +class from a Django model. + +For example:: + + >>> from django.forms import ModelForm + + # Create the form class. + >>> class ArticleForm(ModelForm): + ... class Meta: + ... model = Article + + # Creating a form to add an article. + >>> form = ArticleForm() + + # Creating a form to change an existing article. + >>> article = Article.objects.get(pk=1) + >>> form = ArticleForm(instance=article) + +Field types +----------- + +The generated ``Form`` class will have a form field for every model field. Each +model field has a corresponding default form field. For example, a +``CharField`` on a model is represented as a ``CharField`` on a form. A +model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is +the full list of conversions: + + =============================== ======================================== + Model field Form field + =============================== ======================================== + ``AutoField`` Not represented in the form + + ``BigIntegerField`` ``IntegerField`` with ``min_value`` set + to -9223372036854775808 and ``max_value`` + set to 9223372036854775807. + + ``BooleanField`` ``BooleanField`` + + ``CharField`` ``CharField`` with ``max_length`` set to + the model field's ``max_length`` + + ``CommaSeparatedIntegerField`` ``CharField`` + + ``DateField`` ``DateField`` + + ``DateTimeField`` ``DateTimeField`` + + ``DecimalField`` ``DecimalField`` + + ``EmailField`` ``EmailField`` + + ``FileField`` ``FileField`` + + ``FilePathField`` ``CharField`` + + ``FloatField`` ``FloatField`` + + ``ForeignKey`` ``ModelChoiceField`` (see below) + + ``ImageField`` ``ImageField`` + + ``IntegerField`` ``IntegerField`` + + ``IPAddressField`` ``IPAddressField`` + + ``ManyToManyField`` ``ModelMultipleChoiceField`` (see + below) + + ``NullBooleanField`` ``CharField`` + + ``PhoneNumberField`` ``USPhoneNumberField`` + (from ``django.contrib.localflavor.us``) + + ``PositiveIntegerField`` ``IntegerField`` + + ``PositiveSmallIntegerField`` ``IntegerField`` + + ``SlugField`` ``SlugField`` + + ``SmallIntegerField`` ``IntegerField`` + + ``TextField`` ``CharField`` with + ``widget=forms.Textarea`` + + ``TimeField`` ``TimeField`` + + ``URLField`` ``URLField`` with ``verify_exists`` set + to the model field's ``verify_exists`` + + ``XMLField`` ``CharField`` with + ``widget=forms.Textarea`` + =============================== ======================================== + + +.. versionadded:: 1.0 + The ``FloatField`` form field and ``DecimalField`` model and form fields + are new in Django 1.0. + +.. versionadded:: 1.2 + The ``BigIntegerField`` is new in Django 1.2. + + +As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field +types are special cases: + + * ``ForeignKey`` is represented by ``django.forms.ModelChoiceField``, + which is a ``ChoiceField`` whose choices are a model ``QuerySet``. + + * ``ManyToManyField`` is represented by + ``django.forms.ModelMultipleChoiceField``, which is a + ``MultipleChoiceField`` whose choices are a model ``QuerySet``. + +In addition, each generated form field has attributes set as follows: + + * If the model field has ``blank=True``, then ``required`` is set to + ``False`` on the form field. Otherwise, ``required=True``. + + * The form field's ``label`` is set to the ``verbose_name`` of the model + field, with the first character capitalized. + + * The form field's ``help_text`` is set to the ``help_text`` of the model + field. + + * If the model field has ``choices`` set, then the form field's ``widget`` + will be set to ``Select``, with choices coming from the model field's + ``choices``. The choices will normally include the blank choice which is + selected by default. If the field is required, this forces the user to + make a selection. The blank choice will not be included if the model + field has ``blank=False`` and an explicit ``default`` value (the + ``default`` value will be initially selected instead). + +Finally, note that you can override the form field used for a given model +field. See `Overriding the default field types or widgets`_ below. + +A full example +-------------- + +Consider this set of models:: + + from django.db import models + from django.forms import ModelForm + + TITLE_CHOICES = ( + ('MR', 'Mr.'), + ('MRS', 'Mrs.'), + ('MS', 'Ms.'), + ) + + class Author(models.Model): + name = models.CharField(max_length=100) + title = models.CharField(max_length=3, choices=TITLE_CHOICES) + birth_date = models.DateField(blank=True, null=True) + + def __unicode__(self): + return self.name + + class Book(models.Model): + name = models.CharField(max_length=100) + authors = models.ManyToManyField(Author) + + class AuthorForm(ModelForm): + class Meta: + model = Author + + class BookForm(ModelForm): + class Meta: + model = Book + +With these models, the ``ModelForm`` subclasses above would be roughly +equivalent to this (the only difference being the ``save()`` method, which +we'll discuss in a moment.):: + + class AuthorForm(forms.Form): + name = forms.CharField(max_length=100) + title = forms.CharField(max_length=3, + widget=forms.Select(choices=TITLE_CHOICES)) + birth_date = forms.DateField(required=False) + + class BookForm(forms.Form): + name = forms.CharField(max_length=100) + authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all()) + +The ``is_valid()`` method and ``errors`` +---------------------------------------- + +.. versionchanged:: 1.2 + +The first time you call ``is_valid()`` or access the ``errors`` attribute of a +``ModelForm`` has always triggered form validation, but as of Django 1.2, it +will also trigger :ref:`model validation <validating-objects>`. This has the +side-effect of cleaning the model you pass to the ``ModelForm`` constructor. +For instance, calling ``is_valid()`` on your form will convert any date fields +on your model to actual date objects. + + +The ``save()`` method +--------------------- + +Every form produced by ``ModelForm`` also has a ``save()`` +method. This method creates and saves a database object from the data +bound to the form. A subclass of ``ModelForm`` can accept an existing +model instance as the keyword argument ``instance``; if this is +supplied, ``save()`` will update that instance. If it's not supplied, +``save()`` will create a new instance of the specified model:: + + # Create a form instance from POST data. + >>> f = ArticleForm(request.POST) + + # Save a new Article object from the form's data. + >>> new_article = f.save() + + # Create a form to edit an existing Article. + >>> a = Article.objects.get(pk=1) + >>> f = ArticleForm(instance=a) + >>> f.save() + + # Create a form to edit an existing Article, but use + # POST data to populate the form. + >>> a = Article.objects.get(pk=1) + >>> f = ArticleForm(request.POST, instance=a) + >>> f.save() + +Note that ``save()`` will raise a ``ValueError`` if the data in the form +doesn't validate -- i.e., if form.errors evaluates to True. + +This ``save()`` method accepts an optional ``commit`` keyword argument, which +accepts either ``True`` or ``False``. If you call ``save()`` with +``commit=False``, then it will return an object that hasn't yet been saved to +the database. In this case, it's up to you to call ``save()`` on the resulting +model instance. This is useful if you want to do custom processing on the +object before saving it, or if you want to use one of the specialized +:ref:`model saving options <ref-models-force-insert>`. ``commit`` is ``True`` +by default. + +Another side effect of using ``commit=False`` is seen when your model has +a many-to-many relation with another model. If your model has a many-to-many +relation and you specify ``commit=False`` when you save a form, Django cannot +immediately save the form data for the many-to-many relation. This is because +it isn't possible to save many-to-many data for an instance until the instance +exists in the database. + +To work around this problem, every time you save a form using ``commit=False``, +Django adds a ``save_m2m()`` method to your ``ModelForm`` subclass. After +you've manually saved the instance produced by the form, you can invoke +``save_m2m()`` to save the many-to-many form data. For example:: + + # Create a form instance with POST data. + >>> f = AuthorForm(request.POST) + + # Create, but don't save the new author instance. + >>> new_author = f.save(commit=False) + + # Modify the author in some way. + >>> new_author.some_field = 'some_value' + + # Save the new instance. + >>> new_author.save() + + # Now, save the many-to-many data for the form. + >>> f.save_m2m() + +Calling ``save_m2m()`` is only required if you use ``save(commit=False)``. +When you use a simple ``save()`` on a form, all data -- including +many-to-many data -- is saved without the need for any additional method calls. +For example:: + + # Create a form instance with POST data. + >>> a = Author() + >>> f = AuthorForm(request.POST, instance=a) + + # Create and save the new author instance. There's no need to do anything else. + >>> new_author = f.save() + +Other than the ``save()`` and ``save_m2m()`` methods, a ``ModelForm`` works +exactly the same way as any other ``forms`` form. For example, the +``is_valid()`` method is used to check for validity, the ``is_multipart()`` +method is used to determine whether a form requires multipart file upload (and +hence whether ``request.FILES`` must be passed to the form), etc. See +:ref:`binding-uploaded-files` for more information. + +Using a subset of fields on the form +------------------------------------ + +In some cases, you may not want all the model fields to appear on the generated +form. There are three ways of telling ``ModelForm`` to use only a subset of the +model fields: + +1. Set ``editable=False`` on the model field. As a result, *any* form + created from the model via ``ModelForm`` will not include that + field. + +2. Use the ``fields`` attribute of the ``ModelForm``'s inner ``Meta`` + class. This attribute, if given, should be a list of field names + to include in the form. + + .. versionchanged:: 1.1 + + The form will render the fields in the same order they are specified in the + ``fields`` attribute. + +3. Use the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` + class. This attribute, if given, should be a list of field names + to exclude from the form. + +For example, if you want a form for the ``Author`` model (defined +above) that includes only the ``name`` and ``title`` fields, you would +specify ``fields`` or ``exclude`` like this:: + + class PartialAuthorForm(ModelForm): + class Meta: + model = Author + fields = ('name', 'title') + + class PartialAuthorForm(ModelForm): + class Meta: + model = Author + exclude = ('birth_date',) + +Since the Author model has only 3 fields, 'name', 'title', and +'birth_date', the forms above will contain exactly the same fields. + +.. note:: + + If you specify ``fields`` or ``exclude`` when creating a form with + ``ModelForm``, then the fields that are not in the resulting form will not + be set by the form's ``save()`` method. Django will prevent any attempt to + save an incomplete model, so if the model does not allow the missing fields + to be empty, and does not provide a default value for the missing fields, + any attempt to ``save()`` a ``ModelForm`` with missing fields will fail. + To avoid this failure, you must instantiate your model with initial values + for the missing, but required fields:: + + author = Author(title='Mr') + form = PartialAuthorForm(request.POST, instance=author) + form.save() + + Alternatively, you can use ``save(commit=False)`` and manually set + any extra required fields:: + + form = PartialAuthorForm(request.POST) + author = form.save(commit=False) + author.title = 'Mr' + author.save() + + See the `section on saving forms`_ for more details on using + ``save(commit=False)``. + +.. _section on saving forms: `The save() method`_ + +Overriding the default field types or widgets +--------------------------------------------- + +.. versionadded:: 1.2 + The ``widgets`` attribute is new in Django 1.2. + +The default field types, as described in the `Field types`_ table above, are +sensible defaults. If you have a ``DateField`` in your model, chances are you'd +want that to be represented as a ``DateField`` in your form. But +``ModelForm`` gives you the flexibility of changing the form field type and +widget for a given model field. + +To specify a custom widget for a field, use the ``widgets`` attribute of the +inner ``Meta`` class. This should be a dictionary mapping field names to widget +classes or instances. + +For example, if you want the a ``CharField`` for the ``name`` +attribute of ``Author`` to be represented by a ``<textarea>`` instead +of its default ``<input type="text">``, you can override the field's +widget:: + + from django.forms import ModelForm, Textarea + + class AuthorForm(ModelForm): + class Meta: + model = Author + fields = ('name', 'title', 'birth_date') + widgets = { + 'name': Textarea(attrs={'cols': 80, 'rows': 20}), + } + +The ``widgets`` dictionary accepts either widget instances (e.g., +``Textarea(...)``) or classes (e.g., ``Textarea``). + +If you want to further customize a field -- including its type, label, etc. -- +you can do this by declaratively specifying fields like you would in a regular +``Form``. Declared fields will override the default ones generated by using the +``model`` attribute. + +For example, if you wanted to use ``MyDateFormField`` for the ``pub_date`` +field, you could do the following:: + + class ArticleForm(ModelForm): + pub_date = MyDateFormField() + + class Meta: + model = Article + +If you want to override a field's default label, then specify the ``label`` +parameter when declaring the form field:: + + >>> class ArticleForm(ModelForm): + ... pub_date = DateField(label='Publication date') + ... + ... class Meta: + ... model = Article + +.. note:: + + If you explicitly instantiate a form field like this, Django assumes that you + want to completely define its behavior; therefore, default attributes (such as + ``max_length`` or ``required``) are not drawn from the corresponding model. If + you want to maintain the behavior specified in the model, you must set the + relevant arguments explicitly when declaring the form field. + + For example, if the ``Article`` model looks like this:: + + class Article(models.Model): + headline = models.CharField(max_length=200, null=True, blank=True, + help_text="Use puns liberally") + content = models.TextField() + + and you want to do some custom validation for ``headline``, while keeping + the ``blank`` and ``help_text`` values as specified, you might define + ``ArticleForm`` like this:: + + class ArticleForm(ModelForm): + headline = MyFormField(max_length=200, required=False, + help_text="Use puns liberally") + + class Meta: + model = Article + + See the :doc:`form field documentation </ref/forms/fields>` for more information + on fields and their arguments. + +Changing the order of fields +---------------------------- + +.. versionadded:: 1.1 + +By default, a ``ModelForm`` will render fields in the same order that they are +defined on the model, with ``ManyToManyField`` instances appearing last. If +you want to change the order in which fields are rendered, you can use the +``fields`` attribute on the ``Meta`` class. + +The ``fields`` attribute defines the subset of model fields that will be +rendered, and the order in which they will be rendered. For example given this +model:: + + class Book(models.Model): + author = models.ForeignKey(Author) + title = models.CharField(max_length=100) + +the ``author`` field would be rendered first. If we wanted the title field +to be rendered first, we could specify the following ``ModelForm``:: + + >>> class BookForm(ModelForm): + ... class Meta: + ... model = Book + ... fields = ('title', 'author') + +.. _overriding-modelform-clean-method: + +Overriding the clean() method +----------------------------- + +You can override the ``clean()`` method on a model form to provide additional +validation in the same way you can on a normal form. + +In this regard, model forms have two specific characteristics when compared to +forms: + +By default the ``clean()`` method validates the uniqueness of fields that are +marked as ``unique``, ``unique_together`` or ``unique_for_date|month|year`` on +the model. Therefore, if you would like to override the ``clean()`` method and +maintain the default validation, you must call the parent class's ``clean()`` +method. + +Also, a model form instance bound to a model object will contain a +``self.instance`` attribute that gives model form methods access to that +specific model instance. + +Form inheritance +---------------- + +As with basic forms, you can extend and reuse ``ModelForms`` by inheriting +them. This is useful if you need to declare extra fields or extra methods on a +parent class for use in a number of forms derived from models. For example, +using the previous ``ArticleForm`` class:: + + >>> class EnhancedArticleForm(ArticleForm): + ... def clean_pub_date(self): + ... ... + +This creates a form that behaves identically to ``ArticleForm``, except there's +some extra validation and cleaning for the ``pub_date`` field. + +You can also subclass the parent's ``Meta`` inner class if you want to change +the ``Meta.fields`` or ``Meta.excludes`` lists:: + + >>> class RestrictedArticleForm(EnhancedArticleForm): + ... class Meta(ArticleForm.Meta): + ... exclude = ('body',) + +This adds the extra method from the ``EnhancedArticleForm`` and modifies +the original ``ArticleForm.Meta`` to remove one field. + +There are a couple of things to note, however. + + * Normal Python name resolution rules apply. If you have multiple base + classes that declare a ``Meta`` inner class, only the first one will be + used. This means the child's ``Meta``, if it exists, otherwise the + ``Meta`` of the first parent, etc. + + * For technical reasons, a subclass cannot inherit from both a ``ModelForm`` + and a ``Form`` simultaneously. + +Chances are these notes won't affect you unless you're trying to do something +tricky with subclassing. + +Interaction with model validation +--------------------------------- + +As part of its validation process, ``ModelForm`` will call the ``clean()`` +method of each field on your model that has a corresponding field on your form. +If you have excluded any model fields, validation will not be run on those +fields. See the :doc:`form validation </ref/forms/validation>` documentation +for more on how field cleaning and validation work. Also, your model's +``clean()`` method will be called before any uniqueness checks are made. See +:ref:`Validating objects <validating-objects>` for more information on the +model's ``clean()`` hook. + +.. _model-formsets: + +Model formsets +============== + +Like :doc:`regular formsets </topics/forms/formsets>`, Django provides a couple +of enhanced formset classes that make it easy to work with Django models. Let's +reuse the ``Author`` model from above:: + + >>> from django.forms.models import modelformset_factory + >>> AuthorFormSet = modelformset_factory(Author) + +This will create a formset that is capable of working with the data associated +with 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" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_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:: + ``modelformset_factory`` uses ``formset_factory`` to generate formsets. + This means that a model formset is just an extension of a basic formset + that knows how to interact with a particular model. + +Changing the queryset +--------------------- + +By default, when you create a formset from a model, the formset will use a +queryset that includes all objects in the model (e.g., +``Author.objects.all()``). You can override this behavior by using the +``queryset`` argument:: + + >>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O')) + +Alternatively, you can create a subclass that sets ``self.queryset`` in +``__init__``:: + + from django.forms.models import BaseModelFormSet + + class BaseAuthorFormSet(BaseModelFormSet): + def __init__(self, *args, **kwargs): + super(BaseAuthorFormSet, self).__init__(*args, **kwargs) + self.queryset = Author.objects.filter(name__startswith='O') + +Then, pass your ``BaseAuthorFormSet`` class to the factory function:: + + >>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet) + +If you want to return a formset that doesn't include *any* pre-existing +instances of the model, you can specify an empty QuerySet:: + + >>> AuthorFormSet(queryset=Author.objects.none()) + + +Controlling which fields are used with ``fields`` and ``exclude`` +----------------------------------------------------------------- + +By default, a model formset uses all fields in the model that are not marked +with ``editable=False``. However, this can be overridden at the formset level:: + + >>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title')) + +Using ``fields`` restricts the formset to use only the given fields. +Alternatively, you can take an "opt-out" approach, specifying which fields to +exclude:: + + >>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',)) + +.. _saving-objects-in-the-formset: + +Saving objects in the formset +----------------------------- + +As with a ``ModelForm``, you can save the data as a model object. This is done +with the formset's ``save()`` method:: + + # Create a formset instance with POST data. + >>> formset = AuthorFormSet(request.POST) + + # Assuming all is valid, save the data. + >>> instances = formset.save() + +The ``save()`` method returns the instances that have been saved to the +database. If a given instance's data didn't change in the bound data, the +instance won't be saved to the database and won't be included in the return +value (``instances``, in the above example). + +Pass ``commit=False`` to return the unsaved model instances:: + + # 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'll also +need to call ``formset.save_m2m()`` to ensure the many-to-many relationships +are saved properly. + +.. _model-formsets-max-num: + +Limiting the number of editable objects +--------------------------------------- + +.. versionchanged:: 1.2 + +As with regular formsets, you can use the ``max_num`` and ``extra`` parameters +to ``modelformset_factory`` to limit the number of extra forms displayed. + +``max_num`` does not prevent existing objects from being displayed:: + + >>> Author.objects.order_by('name') + [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>] + + >>> AuthorFormSet = modelformset_factory(Author, max_num=1) + >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name')) + >>> [x.name for x in formset.get_queryset()] + [u'Charles Baudelaire', u'Paul Verlaine', u'Walt Whitman'] + +If the value of ``max_num`` is greater than the number of existing related +objects, up to ``extra`` additional blank forms will be added to the formset, +so long as the total number of forms does not exceed ``max_num``:: + + >>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=2) + >>> 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> + +.. versionchanged:: 1.2 + +A ``max_num`` value of ``None`` (the default) puts no limit on the number of +forms displayed. + +Using a model formset in a view +------------------------------- + +Model formsets are very similar to formsets. Let's say we want to present a +formset to edit ``Author`` model instances:: + + def manage_authors(request): + AuthorFormSet = modelformset_factory(Author) + if request.method == 'POST': + formset = AuthorFormSet(request.POST, request.FILES) + if formset.is_valid(): + formset.save() + # do something. + else: + formset = AuthorFormSet() + return render_to_response("manage_authors.html", { + "formset": formset, + }) + +As you can see, the view logic of a model formset isn't drastically different +than that of a "normal" formset. The only difference is that we call +``formset.save()`` to save the data into the database. (This was described +above, in :ref:`saving-objects-in-the-formset`.) + +Overiding ``clean()`` on a ``model_formset`` +-------------------------------------------- + +Just like with ``ModelForms``, by default the ``clean()`` method of a +``model_formset`` will validate that none of the items in the formset violate +the unique constraints on your model (either ``unique``, ``unique_together`` or +``unique_for_date|month|year``). If you want to overide the ``clean()`` method +on a ``model_formset`` and maintain this validation, you must call the parent +class's ``clean`` method:: + + class MyModelFormSet(BaseModelFormSet): + def clean(self): + super(MyModelFormSet, self).clean() + # example custom validation across forms in the formset: + for form in self.forms: + # your custom formset validation + +Using a custom queryset +----------------------- + +As stated earlier, you can override the default queryset used by the model +formset:: + + def manage_authors(request): + AuthorFormSet = modelformset_factory(Author) + if request.method == "POST": + formset = AuthorFormSet(request.POST, request.FILES, + queryset=Author.objects.filter(name__startswith='O')) + if formset.is_valid(): + formset.save() + # Do something. + else: + formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O')) + return render_to_response("manage_authors.html", { + "formset": formset, + }) + +Note that we pass the ``queryset`` argument in both the ``POST`` and ``GET`` +cases in this example. + +Using the formset in the template +--------------------------------- + +.. highlight:: html+django + +There are three ways to render a formset in a Django template. + +First, you can let the formset do most of the work:: + + <form method="post" action=""> + {{ formset }} + </form> + +Second, you can manually render the formset, but let the form deal with +itself:: + + <form method="post" action=""> + {{ formset.management_form }} + {% for form in formset.forms %} + {{ form }} + {% endfor %} + </form> + +When you manually render the forms yourself, be sure to render the management +form as shown above. See the :ref:`management form documentation +<understanding-the-managementform>`. + +Third, you can manually render each field:: + + <form method="post" action=""> + {{ formset.management_form }} + {% for form in formset.forms %} + {% for field in form %} + {{ field.label_tag }}: {{ field }} + {% endfor %} + {% endfor %} + </form> + +If you opt to use this third method and you don't iterate over the fields with +a ``{% for %}`` loop, you'll need to render the primary key field. For example, +if you were rendering the ``name`` and ``age`` fields of a model:: + + <form method="post" action=""> + {{ formset.management_form }} + {% for form in formset.forms %} + {{ form.id }} + <ul> + <li>{{ form.name }}</li> + <li>{{ form.age }}</li> + </ul> + {% endfor %} + </form> + +Notice how we need to explicitly render ``{{ form.id }}``. This ensures that +the model formset, in the ``POST`` case, will work correctly. (This example +assumes a primary key named ``id``. If you've explicitly defined your own +primary key that isn't called ``id``, make sure it gets rendered.) + +.. highlight:: python + +Inline formsets +=============== + +Inline formsets is a small abstraction layer on top of model formsets. These +simplify the case of working with related objects via a foreign key. Suppose +you have these two models:: + + class Author(models.Model): + name = models.CharField(max_length=100) + + class Book(models.Model): + author = models.ForeignKey(Author) + title = models.CharField(max_length=100) + +If you want to create a formset that allows you to edit books belonging to +a particular author, you could do this:: + + >>> from django.forms.models import inlineformset_factory + >>> BookFormSet = inlineformset_factory(Author, Book) + >>> author = Author.objects.get(name=u'Mike Royko') + >>> formset = BookFormSet(instance=author) + +.. note:: + ``inlineformset_factory`` uses ``modelformset_factory`` and marks + ``can_delete=True``. + +More than one foreign key to the same model +------------------------------------------- + +If your model contains more than one foreign key to the same model, you'll +need to resolve the ambiguity manually using ``fk_name``. For example, consider +the following model:: + + class Friendship(models.Model): + from_friend = models.ForeignKey(Friend) + to_friend = models.ForeignKey(Friend) + length_in_months = models.IntegerField() + +To resolve this, you can use ``fk_name`` to ``inlineformset_factory``:: + + >>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name="from_friend") + +Using an inline formset in a view +--------------------------------- + +You may want to provide a view that allows a user to edit the related objects +of a model. Here's how you can do that:: + + def manage_books(request, author_id): + author = Author.objects.get(pk=author_id) + BookInlineFormSet = inlineformset_factory(Author, Book) + if request.method == "POST": + formset = BookInlineFormSet(request.POST, request.FILES, instance=author) + if formset.is_valid(): + formset.save() + # Do something. + else: + formset = BookInlineFormSet(instance=author) + return render_to_response("manage_books.html", { + "formset": formset, + }) + +Notice how we pass ``instance`` in both the ``POST`` and ``GET`` cases. |