.. _forms-label:
Fun with Forms
==============
So far we have only presented data through the views and templates that we have created. In this chapter, we will run through how to capture data through web forms. Django comes with some neat form handling functionality, making it a pretty straightforward process to gather information from users and send it back to your web application. According to `Django's documentation on forms `_, the form handling functionality allows you to:
#. display an HTML form with automatically generated *form widgets* (like a text field or date picker);
#. check submitted data against a set of validation rules;
#. redisplay a form in case of validation errors; and
#. convert submitted form data to the relevant Python data types.
One of the major advantages of using Django's forms functionality is that it can save you a lot of time and HTML hassle. This part of the tutorial will look at how to implement the necessary infrastructure that will allow users of Rango to add categories and pages to the database via forms.
Basic Workflow
--------------
The basic steps involved in creating a form and allowing users to enter data via the form is as follows.
#. If you haven't already got one, create a ``forms.py`` file within your Django application's directory to store form-related classes.
#. Create a ``ModelForm`` class for each model that you wish to represent as a form.
#. Customise the forms as you desire.
#. Create or update a view to handle the form - including *displaying* the form, *saving* the form data, and *flagging up errors* which may occur when the user enters incorrect data (or no data at all) in the form.
#. Create or update a template to display the form.
#. Add a ``urlpattern`` to map to the new view (if you created a new one).
This workflow is a bit more complicated than previous workflows, and the views that we have to construct have a lot more complexity as well. However, once you undertake the process a few times it will be pretty clear how everything pieces together.
Page and Category Forms
-----------------------
First, create a file called ``forms.py`` within the ``rango`` application directory. While this step is not absolutely necessary, as you could put the forms in the ``models.py``, this makes the codebase a lot cleaner and clearer to understand.
Creating ``ModelForm`` Classes
..............................
Within Rango's ``forms.py`` module, we will be creating a number of classes that inherit from Django's ``ModelForm``. In essence, `a ModelForm `_ is a *helper class* that allows you to create a Django ``Form`` from a pre-existing model. As we've already got two models defined for Rango (``Category`` and ``Page``), we'll create ``ModelForms`` for both.
In ``rango/forms.py`` add the following code.
.. code-block:: python
from django import forms
from rango.models import Page, Category
class CategoryForm(forms.ModelForm):
name = forms.CharField(max_length=128, help_text="Please enter the category name.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
# An inline class to provide additional information on the form.
class Meta:
# Provide an association between the ModelForm and a model
model = Category
class PageForm(forms.ModelForm):
title = forms.CharField(max_length=128, help_text="Please enter the title of the page.")
url = forms.URLField(max_length=200, help_text="Please enter the URL of the page.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
class Meta:
# Provide an association between the ModelForm and a model
model = Page
# What fields do we want to include in our form?
# This way we don't need every field in the model present.
# Some fields may allow NULL values, so we may not want to include them...
# Here, we are hiding the foreign key.
fields = ('title', 'url', 'views')
Django provides us with a number of ways to customise the forms that are created on our behalf. In the code sample above, we've specified the widgets that we wish to use for each field to be displayed. For example, in our ``PageForm`` class, we've defined ``forms.CharField`` for the ``title`` field, and ``forms.URLField`` for ``url`` field. Both fields provide text entry for users. Note the ``max_length`` parameters we supply to our fields - the lengths that we specify are identical to the maximum length of each field we specified in the underlying data models. Go back to Chapter :ref:`model-label` to check for yourself, or have a look at Rango's ``models.py`` file.
You will also notice that we have included several ``IntegerField`` entries for the views and likes fields in each form. Note that we have set the widget to be hidden with the parameter setting ``widget=forms.HiddenInput()``, and then set the value to zero with ``initial=0``. This is one way to set the field to zero without giving the control to the user as the field will be hidden, yet the form will provide the value to the model. However, as you can see in the ``PageForm``, despite the fact that we have a hidden field, we still need to include the field in the form. If in ``fields`` we excluded ``views``, then the form would not contain that field (despite it being specified) and so the form would not return the value zero for that field. This may raise an error depending on how the model has been set up. If in the models we specified that the ``default=0`` for these fields then we can rely on the model to automatically populate field with the default value - and thus avoid a ``not null`` error. In this case, it would not be necessary to have these hidden fields. Essentially, you need to be careful when you define your models and forms to make sure that form is going to contain and pass on all the data that is required to populate your model correctly.
Besides the ``CharField`` and ``IntegerField`` widget, many more are available for use. As an example, Django provides ``EmailField`` (for e-mail address entry), ``ChoiceField`` (for radio input buttons), and ``DateField`` (for date/time entry). There are many other field types you can use, which perform error checking for you (e.g. *is the value provided a valid integer?*). We highly recommend you have a look at the `official Django documentation on widgets `_ to see what components exist and the arguments you can provide to customise them.
Perhaps the most important aspect of a class inheriting from ``ModelForm`` is the need to define *which model we're wanting to provide a form for.* We take care of this through our nested ``Meta`` class. Set the ``model`` attribute of the nested ``Meta`` class to the model you wish to use. For example, our ``CategoryForm`` class has a reference to the ``Category`` model. This is a crucial step enabling Django to take care of creating a form in the image of the specified model. It will also help in handling flagging up any errors along with saving and displaying the data in the form.
We also use the ``Meta`` class to specify which fields that we wish to include in our form through the ``fields`` tuple. Use a tuple of field names to specify the fields you wish to include.
.. note:: We highly recommend you check out the `official Django documentation on forms `_ for further information about how to customise them.
Creating an *Add Category* View
...............................
With our ``CategoryForm`` class now defined, we're now ready to create a new view to display the form and handle the posting of form data. To do this, add the following code to ``rango/views.py``.
.. code-block:: python
from rango.forms import CategoryForm
def add_category(request):
# Get the context from the request.
context = RequestContext(request)
# A HTTP POST?
if request.method == 'POST':
form = CategoryForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# Save the new category to the database.
form.save(commit=True)
# Now call the index() view.
# The user will be shown the homepage.
return index(request)
else:
# The supplied form contained errors - just print them to the terminal.
print form.errors
else:
# If the request was not a POST, display the form to enter details.
form = CategoryForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render_to_response('rango/add_category.html', {'form': form}, context)
The new ``add_category()`` view adds several key pieces of functionality for handling forms. First, we access the context surrounding the HTTP request. This then allows us to determine the type of request being made - whether it is a HTTP ``GET`` or ``POST``. This allows us to handle different requests appropriately - whether we want to show a form (i.e. on ``GET``), or process form data (i.e. on ``POST``) - all from the same URL. The ``add_category()`` view function can handle three different scenarios:
- showing a new, blank form for adding a category;
- saving form data provided by the user to the associated model, and rendering the Rango homepage; and
- if there are errors, redisplay the form with error messages.
.. note::
What do we mean by ``GET`` and ``POST``? They are two different types of *HTTP requests*.
- A HTTP ``GET`` is used to *request a representation of the specified resource.* In other words, we use a HTTP ``GET`` to retrieve a particular resource, whether it is a webpage, image or other file.
- In contrast, a HTTP ``POST`` *submits data from the client's web browser to be processed.* This type of request is used for example when submitting the contents of a HTML form.
- Ultimately, a HTTP ``POST`` may end up being programmed to create a new resource (e.g. a new database entry) on the server. This can later be accessed through a HTTP ``GET`` request.
Django's form handling machinery processes the data returned from a user's browser via a HTTP ``POST`` request. It not only handles the saving of form data into the chosen model, but will also automatically generate any error messages for each form field (if any are required). This means that Django will not store any submitted forms with missing information which could potentially cause problems for your database's referential integrity. For example, supplying no value in the category name field will return an error, as the field cannot be blank.
You'll notice from the line in which we call ``render_to_response()`` that we refer to a new template called ``add_category.html`` which will contain the relevant Django template code and HTML for the form and page.
Creating the *Add Category* Template
....................................
Create the file ``templates/rango/add_category.html``. Within the file, add the following HTML markup and Django template code.
.. code-block:: html
Rango
Add a Category
Now, what does this code do? You can see that within the ```` of the HTML page that we place a ``