diff --git a/docs/add_ons.txt b/docs/add_ons.txt index 029e314f120..3f226386fd3 100644 --- a/docs/add_ons.txt +++ b/docs/add_ons.txt @@ -76,7 +76,7 @@ Requires the sites_ contrib package to be installed as well. formtools ========= -A set of high-level abstractions for Django forms (django.newforms). +A set of high-level abstractions for Django forms (django.forms). django.contrib.formtools.preview -------------------------------- diff --git a/docs/api_stability.txt b/docs/api_stability.txt index 769359b75e8..2a10f34a41a 100644 --- a/docs/api_stability.txt +++ b/docs/api_stability.txt @@ -115,6 +115,6 @@ change: .. _template language: ../templates/ .. _transactions: ../transactions/ .. _url dispatch: ../url_dispatch/ -.. _forms and validation: ../forms/ +.. _forms and validation: ../oldforms/ .. _serialization: ../serialization/ .. _authentication: ../authentication/ diff --git a/docs/authentication.txt b/docs/authentication.txt index cd76731bc47..5e6b4b1a8b3 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -517,7 +517,7 @@ It's your responsibility to provide the login form in a template called template context variables: * ``form``: A ``Form`` object representing the login form. See the - `newforms documentation`_ for more on ``Form`` objects. + `forms documentation`_ for more on ``FormWrapper`` 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 @@ -557,7 +557,7 @@ block:: {% endblock %} -.. _newforms documentation: ../newforms/ +.. _forms documentation: ../forms/ .. _site framework docs: ../sites/ Other built-in views diff --git a/docs/custom_model_fields.txt b/docs/custom_model_fields.txt index cbaac873e36..86d2986ffc5 100644 --- a/docs/custom_model_fields.txt +++ b/docs/custom_model_fields.txt @@ -111,7 +111,7 @@ into the precise details of what ``Field`` can do later on; for now, suffice it to say that everything descends from ``Field`` and then customizes key pieces of the class behavior. -.. _form fields: ../newforms/#fields +.. _form fields: ../forms/#fields It's important to realize that a Django field class is not what is stored in your model attributes. The model attributes contain normal Python objects. The @@ -493,8 +493,8 @@ This assumes we're imported a ``MyFormField`` field class (which has its own default widget). This document doesn't cover the details of writing custom form fields. -.. _helper functions: ../newforms/#generating-forms-for-models -.. _forms documentation: ../newforms/ +.. _helper functions: ../forms/#generating-forms-for-models +.. _forms documentation: ../forms/ ``get_internal_type(self)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/form_for_model.txt b/docs/form_for_model.txt index ddca9aae186..a394cd2c547 100644 --- a/docs/form_for_model.txt +++ b/docs/form_for_model.txt @@ -20,13 +20,13 @@ For this reason, Django provides a few helper functions that let you create a ``form_for_model()`` -------------------- -The method ``django.newforms.form_for_model()`` creates a form based on the +The method ``django.forms.form_for_model()`` creates a form based on the definition of a specific model. Pass it the model class, and it will return a ``Form`` class that contains a form field for each model field. For example:: - >>> from django.newforms import form_for_model + >>> from django.forms import form_for_model # Create the form class. >>> ArticleForm = form_for_model(Article) @@ -93,11 +93,11 @@ the full list of conversions: As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field types are special cases: - * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``, + * ``ForeignKey`` is represented by ``django.forms.ModelChoiceField``, which is a ``ChoiceField`` whose choices are a model ``QuerySet``. * ``ManyToManyField`` is represented by - ``django.newforms.ModelMultipleChoiceField``, which is a + ``django.forms.ModelMultipleChoiceField``, which is a ``MultipleChoiceField`` whose choices are a model ``QuerySet``. In addition, each generated form field has attributes set as follows: @@ -228,7 +228,7 @@ Using an alternate base class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want to add custom methods to the form generated by -``form_for_model()``, write a class that extends ``django.newforms.BaseForm`` +``form_for_model()``, write a class that extends ``django.forms.BaseForm`` and contains your custom methods. Then, use the ``form`` argument to ``form_for_model()`` to tell it to use your custom form as its base class. For example:: @@ -412,8 +412,8 @@ note is that the form display in the ``GET`` branch of the function will use the values from the ``message`` instance as initial values for the form field. -.. _contact form: ../newforms/#simple-view-example -.. _`simple example view`: ../newforms/#simple-view-example +.. _contact form: ../forms/#simple-view-example +.. _`simple example view`: ../forms/#simple-view-example When should you use ``form_for_model()`` and ``form_for_instance()``? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/form_preview.txt b/docs/form_preview.txt index e03de361874..171174704cd 100644 --- a/docs/form_preview.txt +++ b/docs/form_preview.txt @@ -13,7 +13,7 @@ Python class. Overview ========= -Given a ``django.newforms.Form`` subclass that you define, this application +Given a ``django.forms.Form`` subclass that you define, this application takes care of the following workflow: 1. Displays the form as HTML on a Web page. @@ -65,7 +65,7 @@ How to use ``FormPreview`` from myapp.preview import SomeModelFormPreview from myapp.models import SomeModel - from django import newforms as forms + from django import forms ...and add the following line to the appropriate model in your URLconf:: diff --git a/docs/form_wizard.txt b/docs/form_wizard.txt index cd9e58ded1a..661127e5b04 100644 --- a/docs/form_wizard.txt +++ b/docs/form_wizard.txt @@ -17,7 +17,7 @@ etc. The term "wizard," in this context, is `explained on Wikipedia`_. .. _explained on Wikipedia: http://en.wikipedia.org/wiki/Wizard_%28software%29 -.. _forms: ../newforms/ +.. _forms: ../forms/ How it works ============ @@ -41,7 +41,7 @@ Usage This application handles as much machinery for you as possible. Generally, you just have to do these things: - 1. Define a number of ``django.newforms`` ``Form`` classes -- one per wizard + 1. Define a number of ``django.forms`` ``Form`` classes -- one per wizard page. 2. Create a ``FormWizard`` class that specifies what to do once all of your forms have been submitted and validated. This also lets you override some @@ -55,8 +55,8 @@ Defining ``Form`` classes ========================= The first step in creating a form wizard is to create the ``Form`` classes. -These should be standard ``django.newforms`` ``Form`` classes, covered in the -`newforms documentation`_. +These should be standard ``django.forms`` ``Form`` classes, covered in the +`forms documentation`_. These classes can live anywhere in your codebase, but convention is to put them in a file called ``forms.py`` in your application. @@ -65,7 +65,7 @@ For example, let's write a "contact form" wizard, where the first page's form collects the sender's e-mail address and subject, and the second page collects the message itself. Here's what the ``forms.py`` might look like:: - from django import newforms as forms + from django import forms class ContactForm1(forms.Form): subject = forms.CharField(max_length=100) @@ -78,7 +78,7 @@ the message itself. Here's what the ``forms.py`` might look like:: data between pages, you may not include a ``FileField`` in any form except the last one. -.. _newforms documentation: ../newforms/ +.. _forms documentation: ../forms/ Creating a ``FormWizard`` class =============================== @@ -94,7 +94,7 @@ which specifies what should happen when the data for *every* form is submitted and validated. This method is passed two arguments: * ``request`` -- an HttpRequest_ object - * ``form_list`` -- a list of ``django.newforms`` ``Form`` classes + * ``form_list`` -- a list of ``django.forms`` ``Form`` classes In this simplistic example, rather than perform any database operation, the method simply renders a template of the validated data:: @@ -209,7 +209,7 @@ Default implementation:: def prefix_for_step(self, step): return str(step) -.. _form prefix documentation: ../newforms/#prefixes-for-forms +.. _form prefix documentation: ../forms/#prefixes-for-forms ``render_hash_failure`` ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/forms.txt b/docs/forms.txt index 18d322a8eb8..7781191e357 100644 --- a/docs/forms.txt +++ b/docs/forms.txt @@ -1,700 +1,2523 @@ -=============================== -Forms, fields, and manipulators -=============================== +================= +The forms library +================= -Forwards-compatibility note -=========================== +``django.forms`` is Django's fantastic new form-handling library. It's a +replacement for the old form/manipulator/validation framework, which has been +moved to ``django.oldforms``. This document explains how to use this new +library. -The legacy forms/manipulators system described in this document is going to be -replaced in the next Django release. If you're starting from scratch, we -strongly encourage you not to waste your time learning this. Instead, learn and -use the django.newforms system, which we have begun to document in the -`newforms documentation`_. +Migration plan +============== -If you have legacy form/manipulator code, read the "Migration plan" section in -that document to understand how we're making the switch. +``django.newforms`` is new in Django's 0.96 release, but, as it won't be new +forever, we plan to rename it to ``django.forms`` in the future. The current +``django.forms`` package will be available as ``django.oldforms`` until Django +1.0, when we plan to remove it for good. -.. _newforms documentation: ../newforms/ +That has direct repercussions on the forward compatibility of your code. Please +read the following migration plan and code accordingly: -Introduction + * The old forms framework (the current ``django.forms``) has been copied to + ``django.oldforms``. Thus, you can start upgrading your code *now*, + rather than waiting for the future backwards-incompatible change, by + changing your import statements like this:: + + from django import forms # old + from django import oldforms as forms # new + + * In the next Django release (0.97), we will move the current + ``django.newforms`` to ``django.forms``. This will be a + backwards-incompatible change, and anybody who is still using the old + version of ``django.forms`` at that time will need to change their import + statements, as described in the previous bullet. + + * We will remove ``django.oldforms`` in the release *after* the next Django + release -- either 0.98 or 1.0, whichever comes first. + +With this in mind, we recommend you use the following import statement when +using ``django.newforms``:: + + from django import newforms as forms + +This way, your code can refer to the ``forms`` module, and when +``django.newforms`` is renamed to ``django.forms``, you'll only have to change +your ``import`` statements. + +If you prefer "``import *``" syntax, you can do the following:: + + from django.newforms import * + +This will import all fields, widgets, form classes and other various utilities +into your local namespace. Some people find this convenient; others find it +too messy. The choice is yours. + +Overview +======== + +As with the ``django.oldforms`` ("manipulators") system before it, +``django.forms`` is intended to handle HTML form display, data processing +(validation) and redisplay. It's what you use if you want to perform +server-side validation for an HTML form. + +For example, if your Web site has a contact form that visitors can use to +send you e-mail, you'd use this library to implement the display of the HTML +form fields, along with the form validation. Any time you need to use an HTML +``
``, you can use this library. + +The library deals with these concepts: + + * **Widget** -- A class that corresponds to an HTML form widget, e.g. + ```` or ```` + ``CheckboxInput`` ``
+ +However the above can be slightly shortcutted and let the formset itself deal +with the management form:: + +
+ + {{ formset }} +
+
+ +The above ends up calling the ``as_table`` method on the formset class. + +More coming soon +================ + +That's all the documentation for now. For more, see the file +http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms +-- the unit tests for ``django.forms``. This can give you a good idea of +what's possible. (Each submodule there contains separate tests.) + +If you're really itching to learn and use this library, please be patient. +We're working hard on finishing both the code and documentation. diff --git a/docs/generic_views.txt b/docs/generic_views.txt index a7602524a98..86a04a700cb 100644 --- a/docs/generic_views.txt +++ b/docs/generic_views.txt @@ -984,12 +984,12 @@ In addition to ``extra_context``, the template's context will be:

{{ form.address.label_tag }} {{ form.address }}

- See the `newforms documentation`_ for more information about using + See the `forms documentation`_ for more information about using ``Form`` objects in templates. .. _authentication system: ../authentication/ .. _ModelForm docs: ../newforms/modelforms -.. _newforms documentation: ../newforms/ +.. _forms documentation: ../forms/ ``django.views.generic.create_update.update_object`` ---------------------------------------------------- diff --git a/docs/index.txt b/docs/index.txt index 385ada455ca..36a02738196 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -33,7 +33,7 @@ Reference transactions templates templates_python - newforms + forms modelforms testing sessions diff --git a/docs/localflavor.txt b/docs/localflavor.txt index f30c6a542bf..74224ab4135 100644 --- a/docs/localflavor.txt +++ b/docs/localflavor.txt @@ -11,7 +11,7 @@ Inside that package, country- or culture-specific code is organized into subpackages, named using `ISO 3166 country codes`_. Most of the ``localflavor`` add-ons are localized form components deriving from -the newforms_ framework -- for example, a ``USStateField`` that knows how to +the forms_ framework -- for example, a ``USStateField`` that knows how to validate U.S. state abbreviations, and a ``FISocialSecurityNumber`` that knows how to validate Finnish social security numbers. @@ -19,7 +19,7 @@ To use one of these localized components, just import the relevant subpackage. 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 import forms from django.contrib.localflavor import fr class MyForm(forms.Form): @@ -58,10 +58,10 @@ Countries currently supported by ``localflavor`` are: The ``localflavor`` package also includes a ``generic`` subpackage, containing useful code that is not specific to one particular country or culture. Currently, it defines date and datetime input fields based on those from -newforms_, but with non-US default formats. Here's an example of how to use +forms_, but with non-US default formats. Here's an example of how to use them:: - from django import newforms as forms + from django import forms from django.contrib.localflavor import generic class MyForm(forms.Form): @@ -92,7 +92,7 @@ them:: .. _Switzerland: `Switzerland (django.contrib.localflavor.ch)`_ .. _United Kingdom: `United Kingdom (django.contrib.localflavor.uk)`_ .. _United States of America: `United States of America (django.contrib.localflavor.us)`_ -.. _newforms: ../newforms/ +.. _forms: ../forms/ Adding flavors ============== diff --git a/docs/model-api.txt b/docs/model-api.txt index 4975953b976..9a353c0ec41 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -716,7 +716,7 @@ that takes the parameters ``field_data, all_data`` and raises Django comes with quite a few validators. They're in ``django.core.validators``. -.. _validator docs: ../forms/#validators +.. _validator docs: ../oldforms/#validators Verbose field names ------------------- diff --git a/docs/modelforms.txt b/docs/modelforms.txt index 9c06bc409d5..91d3a9fac97 100644 --- a/docs/modelforms.txt +++ b/docs/modelforms.txt @@ -1,6 +1,6 @@ -========================== -Using newforms with models -========================== +======================= +Using forms with models +======================= ``ModelForm`` ============= @@ -16,7 +16,7 @@ class from a Django model. For example:: - >>> from django.newforms import ModelForm + >>> from django.forms import ModelForm # Create the form class. >>> class ArticleForm(ModelForm): @@ -86,11 +86,11 @@ the full list of conversions: As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field types are special cases: - * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``, + * ``ForeignKey`` is represented by ``django.forms.ModelChoiceField``, which is a ``ChoiceField`` whose choices are a model ``QuerySet``. * ``ManyToManyField`` is represented by - ``django.newforms.ModelMultipleChoiceField``, which is a + ``django.forms.ModelMultipleChoiceField``, which is a ``MultipleChoiceField`` whose choices are a model ``QuerySet``. In addition, each generated form field has attributes set as follows: @@ -121,7 +121,7 @@ A full example Consider this set of models:: from django.db import models - from django.newforms import ModelForm + from django.forms import ModelForm TITLE_CHOICES = ( ('MR', 'Mr.'), @@ -240,14 +240,14 @@ For example:: >>> new_author = f.save() Other than the ``save()`` and ``save_m2m()`` methods, a ``ModelForm`` -works exactly the same way as any other ``newforms`` form. For +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 `the standard newforms documentation`_ +passed to the form), etc. See `the standard forms documentation`_ for more information. -.. _the standard newforms documentation: ../newforms/ +.. _the standard forms documentation: ../forms/ Using a subset of fields on the form ------------------------------------ diff --git a/docs/newforms.txt b/docs/newforms.txt deleted file mode 100644 index 88b25be9158..00000000000 --- a/docs/newforms.txt +++ /dev/null @@ -1,2522 +0,0 @@ -==================== -The newforms library -==================== - -``django.newforms`` is Django's fantastic new form-handling library. It's a -replacement for ``django.forms``, the old form/manipulator/validation -framework. This document explains how to use this new library. - -Migration plan -============== - -``django.newforms`` is new in Django's 0.96 release, but, as it won't be new -forever, we plan to rename it to ``django.forms`` in the future. The current -``django.forms`` package will be available as ``django.oldforms`` until Django -1.0, when we plan to remove it for good. - -That has direct repercussions on the forward compatibility of your code. Please -read the following migration plan and code accordingly: - - * The old forms framework (the current ``django.forms``) has been copied to - ``django.oldforms``. Thus, you can start upgrading your code *now*, - rather than waiting for the future backwards-incompatible change, by - changing your import statements like this:: - - from django import forms # old - from django import oldforms as forms # new - - * In the next Django release (0.97), we will move the current - ``django.newforms`` to ``django.forms``. This will be a - backwards-incompatible change, and anybody who is still using the old - version of ``django.forms`` at that time will need to change their import - statements, as described in the previous bullet. - - * We will remove ``django.oldforms`` in the release *after* the next Django - release -- either 0.98 or 1.0, whichever comes first. - -With this in mind, we recommend you use the following import statement when -using ``django.newforms``:: - - from django import newforms as forms - -This way, your code can refer to the ``forms`` module, and when -``django.newforms`` is renamed to ``django.forms``, you'll only have to change -your ``import`` statements. - -If you prefer "``import *``" syntax, you can do the following:: - - from django.newforms import * - -This will import all fields, widgets, form classes and other various utilities -into your local namespace. Some people find this convenient; others find it -too messy. The choice is yours. - -Overview -======== - -As with the ``django.forms`` ("manipulators") system before it, -``django.newforms`` is intended to handle HTML form display, data processing -(validation) and redisplay. It's what you use if you want to perform -server-side validation for an HTML form. - -For example, if your Web site has a contact form that visitors can use to -send you e-mail, you'd use this library to implement the display of the HTML -form fields, along with the form validation. Any time you need to use an HTML -``
``, you can use this library. - -The library deals with these concepts: - - * **Widget** -- A class that corresponds to an HTML form widget, e.g. - ```` or ```` - ``CheckboxInput`` ``
- -However the above can be slightly shortcutted and let the formset itself deal -with the management form:: - -
- - {{ formset }} -
-
- -The above ends up calling the ``as_table`` method on the formset class. - -More coming soon -================ - -That's all the documentation for now. For more, see the file -http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms --- the unit tests for ``django.newforms``. This can give you a good idea of -what's possible. (Each submodule there contains separate tests.) - -If you're really itching to learn and use this library, please be patient. -We're working hard on finishing both the code and documentation. diff --git a/docs/oldforms.txt b/docs/oldforms.txt new file mode 100644 index 00000000000..7ee2cf37359 --- /dev/null +++ b/docs/oldforms.txt @@ -0,0 +1,700 @@ +=============================== +Forms, fields, and manipulators +=============================== + +Forwards-compatibility note +=========================== + +The legacy forms/manipulators system described in this document is going to be +replaced in the next Django release. If you're starting from scratch, we +strongly encourage you not to waste your time learning this. Instead, learn and +use the django.forms system, which we have begun to document in the +`forms documentation`_. + +If you have legacy form/manipulator code, read the "Migration plan" section in +that document to understand how we're making the switch. + +.. _forms documentation: ../forms/ + +Introduction +============ + +Once you've got a chance to play with Django's admin interface, you'll probably +wonder if the fantastic form validation framework it uses is available to user +code. It is, and this document explains how the framework works. + +We'll take a top-down approach to examining Django's form validation framework, +because much of the time you won't need to use the lower-level APIs. Throughout +this document, we'll be working with the following model, a "place" object:: + + from django.db import models + + PLACE_TYPES = ( + (1, 'Bar'), + (2, 'Restaurant'), + (3, 'Movie Theater'), + (4, 'Secret Hideout'), + ) + + class Place(models.Model): + name = models.CharField(max_length=100) + address = models.CharField(max_length=100, blank=True) + city = models.CharField(max_length=50, blank=True) + state = models.USStateField() + zip_code = models.CharField(max_length=5, blank=True) + place_type = models.IntegerField(choices=PLACE_TYPES) + + class Admin: + pass + + def __unicode__(self): + return self.name + +Defining the above class is enough to create an admin interface to a ``Place``, +but what if you want to allow public users to submit places? + +Automatic Manipulators +====================== + +The highest-level interface for object creation and modification is the +**automatic Manipulator** framework. An automatic manipulator is a utility +class tied to a given model that "knows" how to create or modify instances of +that model and how to validate data for the object. Automatic Manipulators come +in two flavors: ``AddManipulators`` and ``ChangeManipulators``. Functionally +they are quite similar, but the former knows how to create new instances of the +model, while the latter modifies existing instances. Both types of classes are +automatically created when you define a new class:: + + >>> from mysite.myapp.models import Place + >>> Place.AddManipulator + + >>> Place.ChangeManipulator + + +Using the ``AddManipulator`` +---------------------------- + +We'll start with the ``AddManipulator``. Here's a very simple view that takes +POSTed data from the browser and creates a new ``Place`` object:: + + from django.shortcuts import render_to_response + from django.http import Http404, HttpResponse, HttpResponseRedirect + from django import oldforms as forms + from mysite.myapp.models import Place + + def naive_create_place(request): + """A naive approach to creating places; don't actually use this!""" + # Create the AddManipulator. + manipulator = Place.AddManipulator() + + # Make a copy of the POSTed data so that do_html2python can + # modify it in place (request.POST is immutable). + new_data = request.POST.copy() + + # Convert the request data (which will all be strings) into the + # appropriate Python types for those fields. + manipulator.do_html2python(new_data) + + # Save the new object. + new_place = manipulator.save(new_data) + + # It worked! + return HttpResponse("Place created: %s" % new_place) + +The ``naive_create_place`` example works, but as you probably can tell, this +view has a number of problems: + + * No validation of any sort is performed. If, for example, the ``name`` field + isn't given in ``request.POST``, the save step will cause a database error + because that field is required. Ugly. + + * Even if you *do* perform validation, there's still no way to give that + information to the user in any sort of useful way. + + * You'll have to separately create a form (and view) that submits to this + page, which is a pain and is redundant. + +Let's dodge these problems momentarily to take a look at how you could create a +view with a form that submits to this flawed creation view:: + + def naive_create_place_form(request): + """Simplistic place form view; don't actually use anything like this!""" + # Create a FormWrapper object that the template can use. Ignore + # the last two arguments to FormWrapper for now. + form = forms.FormWrapper(Place.AddManipulator(), {}, {}) + return render_to_response('places/naive_create_form.html', {'form': form}) + +(This view, as well as all the following ones, has the same imports as in the +first example above.) + +The ``forms.FormWrapper`` object is a wrapper that templates can +easily deal with to create forms. Here's the ``naive_create_form.html`` +template:: + + {% extends "base.html" %} + + {% block content %} +

Create a place:

+ +
+

{{ form.name }}

+

{{ form.address }}

+

{{ form.city }}

+

{{ form.state }}

+

{{ form.zip_code }}

+

{{ form.place_type }}

+ +
+ {% endblock %} + +Before we get back to the problems with these naive set of views, let's go over +some salient points of the above template: + + * Field "widgets" are handled for you: ``{{ form.field }}`` automatically + creates the "right" type of widget for the form, as you can see with the + ``place_type`` field above. + + * There isn't a way just to spit out the form. You'll still need to define + how the form gets laid out. This is a feature: Every form should be + designed differently. Django doesn't force you into any type of mold. + If you must use tables, use tables. If you're a semantic purist, you can + probably find better HTML than in the above template. + + * To avoid name conflicts, the ``id`` values of form elements take the + form "id_*fieldname*". + +By creating a creation form we've solved problem number 3 above, but we still +don't have any validation. Let's revise the validation issue by writing a new +creation view that takes validation into account:: + + def create_place_with_validation(request): + manipulator = Place.AddManipulator() + new_data = request.POST.copy() + + # Check for validation errors + errors = manipulator.get_validation_errors(new_data) + manipulator.do_html2python(new_data) + if errors: + return render_to_response('places/errors.html', {'errors': errors}) + else: + new_place = manipulator.save(new_data) + return HttpResponse("Place created: %s" % new_place) + +In this new version, errors will be found -- ``manipulator.get_validation_errors`` +handles all the validation for you -- and those errors can be nicely presented +on an error page (templated, of course):: + + {% extends "base.html" %} + + {% block content %} + +

Please go back and correct the following error{{ errors|pluralize }}:

+ + + {% endblock %} + +Still, this has its own problems: + + * There's still the issue of creating a separate (redundant) view for the + submission form. + + * Errors, though nicely presented, are on a separate page, so the user will + have to use the "back" button to fix errors. That's ridiculous and unusable. + +The best way to deal with these issues is to collapse the two views -- the form +and the submission -- into a single view. This view will be responsible for +creating the form, validating POSTed data, and creating the new object (if the +data is valid). An added bonus of this approach is that errors and the form will +both be available on the same page, so errors with fields can be presented in +context. + +.. admonition:: Philosophy: + + Finally, for the HTTP purists in the audience (and the authorship), this + nicely matches the "true" meanings of HTTP GET and HTTP POST: GET fetches + the form, and POST creates the new object. + +Below is the finished view:: + + def create_place(request): + manipulator = Place.AddManipulator() + + if request.method == 'POST': + # If data was POSTed, we're trying to create a new Place. + new_data = request.POST.copy() + + # Check for errors. + errors = manipulator.get_validation_errors(new_data) + manipulator.do_html2python(new_data) + + if not errors: + # No errors. This means we can save the data! + new_place = manipulator.save(new_data) + + # Redirect to the object's "edit" page. Always use a redirect + # after POST data, so that reloads don't accidently create + # duplicate entires, and so users don't see the confusing + # "Repost POST data?" alert box in their browsers. + return HttpResponseRedirect("/places/edit/%i/" % new_place.id) + else: + # No POST, so we want a brand new form without any data or errors. + errors = new_data = {} + + # Create the FormWrapper, template, context, response. + form = forms.FormWrapper(manipulator, new_data, errors) + return render_to_response('places/create_form.html', {'form': form}) + +and here's the ``create_form`` template:: + + {% extends "base.html" %} + + {% block content %} +

Create a place:

+ + {% if form.has_errors %} +

Please correct the following error{{ form.error_dict|pluralize }}:

+ {% endif %} + +
+

+ {{ form.name }} + {% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %} +

+

+ {{ form.address }} + {% if form.address.errors %}*** {{ form.address.errors|join:", " }}{% endif %} +

+

+ {{ form.city }} + {% if form.city.errors %}*** {{ form.city.errors|join:", " }}{% endif %} +

+

+ {{ form.state }} + {% if form.state.errors %}*** {{ form.state.errors|join:", " }}{% endif %} +

+

+ {{ form.zip_code }} + {% if form.zip_code.errors %}*** {{ form.zip_code.errors|join:", " }}{% endif %} +

+

+ {{ form.place_type }} + {% if form.place_type.errors %}*** {{ form.place_type.errors|join:", " }}{% endif %} +

+ +
+ {% endblock %} + +The second two arguments to ``FormWrapper`` (``new_data`` and ``errors``) +deserve some mention. + +The first is any "default" data to be used as values for the fields. Pulling +the data from ``request.POST``, as is done above, makes sure that if there are +errors, the values the user put in aren't lost. If you try the above example, +you'll see this in action. + +The second argument is the error list retrieved from +``manipulator.get_validation_errors``. When passed into the ``FormWrapper``, +this gives each field an ``errors`` item (which is a list of error messages +associated with the field) as well as a ``html_error_list`` item, which is a +``