CHAPTER 5

image

Forms

One of the key ingredients of modern Web applications is interactivity—the ability to accept input from users, which helps shape their experience. That input can be just about anything, from a simple search term to entire user-submitted novels. The key is the ability to process this input and turn it into a meaningful feature that enriches the experience for all the users of the site.

The process begins by sending an HTML form to the Web browser, where a user can fill it in and submit it back to the server. When the data arrives, it must be validated to make sure the user didn’t forget any fields or enter anything inappropriate. If there was anything wrong with the submitted data, it has to be sent back to the user for corrections. Once all the data is known to be valid, the application can finally perform a meaningful task with it.

It’s possible to do all this without a framework, but doing so involves a lot of duplication if multiple forms are involved. Managing forms manually also introduces a high risk of the programmer taking shortcuts in the process. It’s very common to have a form skip essential validations, either from lack of time or a perceived lack of necessity. Many exploited security holes can be attributed directly to this type of negligence.

Django addresses this by providing a framework to manage those finer details. Once a form is defined, Django handles the details of generating HTML, receiving input and validating data. After that, the application can do whatever it likes with the data received. Like everything else in Django, you’re also able to bypass this form handling and process things manually if necessary.

Declaring and Identifying Fields

Django’s forms, like its models, use a declarative syntax where fields are assigned as attributes to the form’s class definition. This is one of the most identifiable features of Django, and is used to great effect here as well. It allows a form to be declared as just a simple class while supplying a great deal of additional functionality behind the scenes.

The first difference between models and forms is how they recognize fields. Models don’t actually recognize fields at all; they just check to see if an attribute has a contribute_to_class() method and call it, regardless of what type of object it’s attached to. Forms do actually check the type of each attribute on the class to determine if it’s a field, looking specifically for instances of django.forms.fields.Field.

Like models, forms keep a reference to all the fields that were declared, though forms do so a bit differently. There are two separate lists of fields that may be found on a form, depending on what stage it’s in, each with its own purpose.

The first, base_fields, is a list of all the fields that were found when the metaclass executed. These are stored on the form class itself, and are available to all instances as well. Thus, this list should only be edited in extreme circumstances, as doing so would affect all future instances of the form. It’s always useful as a reference when looking at a form class itself or when identifying those fields that were actually declared directly on the class.

All form instances also get a fields attribute, which contains those fields that will actually be used to generate the HTML for the form, as well as validate user input. Most of the time, this list will be identical to base_fields, since it starts as just a copy of it. Sometimes, however, a form will need to customize its fields based on some other information, so that individual instances will behave differently in different situations.

For example, a contact form may accept a User object to determine whether the user is logged in or not. If not, the form can add another field to accept the user’s name.

from django import forms
 
class ContactForm(forms.Form):
    def __init__(self, user, *args, **kwargs):
        super(ContactForm, self).__init__(*args, **kwargs)
        if not user.is_authenticated():
            # Add a name field since the user doesn't have a name
            self.fields['name'] = forms.CharField(label='Full name')

Binding to User Input

Since forms exist specifically to accept user input, that activity must be performed before any others. It’s so important that instantiated forms are considered to be in one of two states: bound or unbound. A bound form was given user input, which it can then use to do further work, while an unbound form has no data associated with it, and is generally used only to ask the user for the necessary data.

The difference between the two is made when the form is instantiated, based on whether a dictionary of data was passed in or not. This dictionary maps field names to their values, and is always the first positional argument to the form, if it’s passed in. Even passing an empty dictionary will cause the form to be considered bound, though its usefulness is limited, given that without data, the form is unlikely to validate. Once a form has been instantiated, it’s easy to determine whether it was bound to data by inspecting its Boolean is_bound attribute.

>>> from django import forms
>>> class MyForm(forms.Form):
...     title = forms.CharField()
...     age = forms.IntegerField()
...     photo = forms.ImageField()
...
>>> MyForm().is_bound
False
>>> MyForm({'title': u'New Title', 'age': u'25'}).is_bound
True
>>> MyForm({}).is_bound
True

Also note that all values are passed as strings. Some fields may accept other types, such as integers, but strings are the standard, and all fields know how to handle them. This is to support the most common way to instantiate a form, using the request.POST dictionary available within a view.

from my_app.forms import MyForm
 
def my_view(request):
    if request.method == 'POST':
        form = MyForm(request.POST)
    else:
        form = MyForm()
    ...

Sometimes a form may also accept files, which are provided a bit differently than other types of input. Files can be accessed as the FILES attribute of the incoming request object, which forms use by accepting this attribute as a second positional argument.

from my_app.forms import MyForm
 
def my_view(request):
    if request.method == 'POST':
        form = MyForm(request.POST, request.FILES)
    else:
        form = MyForm()
    ...

Regardless of which way it was instantiated, any instance of a form will have a data attribute, which contains a dictionary of whatever data was passed into it. In the case of an unbound form, this will be an empty dictionary. Using data on its own isn’t safe, because there’s no guarantee that the user-submitted data is appropriate to what the form needs, and it could in fact pose a security risk. This data must always be validated before being used.

Validating Input

Once a form has been bound to a set of incoming data, it can check the validity of that data, and should always do so before continuing. This prevents your code from making invalid assumptions about the quality of the data, and can thus prevent many security problems.

On the surface, the process of validating user input is quite simple, consisting of a single call to the form’s is_valid() method. This returns a Boolean indicating whether the data was indeed valid according to the rules set by the form’s fields. This alone is enough to determine whether to continue processing the form or to redisplay it for the user to correct the errors.

def my_view(request):
    if request.method == 'POST':
        form = MyForm(request.POST, request.FILES)
        if form.is_valid():
            # Do more work here, since the data is known to be good
    else:
        form = MyForm()
    ...

NEVER TRUST USER INPUT

There’s an old adage in the world of Web development, which is often phrased, “User input is evil.” That’s a bit of an extreme, but the basic idea is that Web applications don’t run in a vacuum, but are instead exposed to the outside world for a wide variety of users to interact with. Most of these users are upstanding citizens of the Web, looking only to use a site the way it was intended to be used. Others, however, would like nothing more than to bring your precious application to its knees.

Any application that takes action based on user input potentially opens itself up to some risks. Since decisions are being made based on what a user supplies, that user has a great deal of control over how the application behaves. In some cases, user input is passed directly through to database or filesystem operations, with an assumption that the input will be within some established range of known values.

Once someone comes along with malicious intent, he can use this fact to his advantage, pushing other data into the application in hopes of convincing it to do something it shouldn’t, such as read content the user shouldn’t have access to, write to areas that should be read-only, bring the application down so no one can use it at all or, worst of all, getting full access to the system without you even knowing it. These types of attacks are generally placed into categories, such as SQL injection, Cross-site Scripting, Cross-site Request Forgery and Form Manipulation, but one theme ties them together: they all rely on an application being too trusting of incoming data.

The solution to these types of attacks is to vigorously guard your application from malicious input, by meticulously validating everything that comes in. Django’s forms have a variety of ways to control this validation, but the is_valid() method makes sure they all run, so that the application can know if the input should be used. This step should never be skipped, as doing so will make your application vulnerable to many of these attacks.

It’s also important to realize that validation must always take place on the server, by way of form.is_valid(), regardless of what happens inside the user’s Web browser. In this age of Web 2.0 and rich Web applications, much work is done in JavaScript inside the browser, and it’s easy to think that this is a sufficient way to ensure the quality of incoming data, before it even arrives at the server.

However, a lot can happen between the browser and the server, and there are a great many tools freely available to help users manipulate the submitted data after it’s been processed by JavaScript. HTTP is an easy protocol to work with as well, so it’s trivial to bypass the browser altogether. No amount of client-side validation is sufficient to keep an application safe from attack; everything must be checked on the server.

Behind the scenes, is_valid() does even more work, by indirectly calling the form’s full_clean() method, which populates two more attributes. The first, cleaned_data, is a dictionary analogous to the data attribute previously mentioned, except that its values have already been processed by the form’s fields and converted to appropriate Python data types. The second is errors, a dictionary containing information about all the problems that were encountered with the incoming data.

These two attributes are somewhat tied to each other, in that no field should be identified in both attributes at the same time. That is, if a field’s name is in cleaned_data, it’s not in errors, and vice versa. Therefore, in an ideal situation, cleaned_data would contain data for every field, while errors would be empty.

The exact details of what data is considered valid and what errors would be returned otherwise are typically specified by each field, using its clean() method. For most forms, this is sufficient, but some may need additional validation that goes beyond a single field. To support this, Django provides a way to inject additional validation rules into a form.

Special methods may be defined on the form to assist in this process, and are named according to the fields they’re associated with. For example, a method designed to validate and clean the title field would be called clean_title(). Each method defined this way is responsible for looking up its value in cleaned_data, validating it against whatever rules are appropriate for the form. If the value needs additional cleaning, the method must also replace the value in cleaned_data with an appropriately cleaned value.

Using Class-Based Views

Looking at the views shown so far, you’ll notice that they tend to follow a common pattern. In fact, most form-processing views you’ll run into will look a lot like this:

from django.shortcuts import render, redirect
 
def my_view(request):
    if request.method == 'POST':
        form = MyForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect('/success/')
        return render(request, 'form.html', {'form': form})
    else:
        form = MyForm()
        return render(request, 'form.html', {'form': form})

As seen in Chapter 4, this can be made more manageable by handling the GET and POST cases separately in a class-based view.

from django.shortcuts import render, redirect
from django.views.generic.base import View
 
class MyView(View):
    def get(self, request):
        form = MyForm()
        return render(request, 'form.html', {'form': form})
 
    def post(self, request):
        form = MyForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect('/success/')
        return render(request, 'form.html', {'form': form})

This is certainly an improvement, but there’s still a lot of boilerplate here. You’re almost always going to instantiate and validate your forms the same way, and the template rendering is identical when initially displaying the form and displaying errors. When it comes down to it, the only really interesting part about the view is where you do something with a valid form. In this case, it’s just calling form.save(), but you could use it to send an email, transfer some files around, trigger a payment transaction or any number of other things.

To avoid all this duplication, Django provides another class-based view, called FormView. It abstracts away those commonalities, so you can just provide some basic details and a method named form_valid() that receives a valid form as its only argument.

from django.shortcuts import render, redirect
from django.views.generic.edit import FormView
 
class MyView(FormView):
    form_class = MyForm
    template_name = 'form.html'
    success_url = '/success/'
 
    def form_valid(self, form):
        form.save()
        return super(MyView, self).form_valid(form)

That makes things a lot simpler. In fact, you don’t even have to provide form_valid(), but given that it just redirects to success_url by default, without doing anything with the form at all, you’ll nearly always want to provide at least this much. There are a number of other methods you can define as well, to control various other aspects of its behavior, as needed.

  • get_form_class(self)—Returns the form class to use throughout the process. By default, it simply returns the contents of the form_class attribute, which is None if you don’t supply one.
  • get_initial(self)—Returns a dictionary to pass into the initial argument of the form. On its own, this just returns the contents of the initial attribute of the view, which is an empty dictionary by default.
  • get_form_kwargs(self)—Returns a dictionary to use as keyword arguments when instantiating the form for each request. By default, this includes the result of get_initial(), and if the request was a POST or PUT, it also adds in request.POST and request.FILES.
  • get_form(self, form_class)—This returns a fully-instantiated form by passing the arguments retrieved from get_form_kwargs() into the class returned from get_form_class(). Given that you have control over all the arguments going into the form instance by way of get_form_kwargs(), this only makes sense for making modifications to the form after it’s been created but before it’s tested for validity.
  • form_valid(self, form)—The main workhorse, this is triggered when the form has been validated, allowing you to take appropriate action. By default, this redirects the user to the result of the get_success_url() method.
  • form_invalid(self, form)—A natural counterpart form_valid(), this is called when the form is considered invalid, and will be given the invalid form itself. By default, this simply re-renders the template with the form.
  • get_success_url(self)—This returns the URL where the user should be sent after successful validation of the form. By default, it returns the value of the success_url attribute.

As you can see, FormView gives you a chance to customize practically every aspect of the form process. Rather than having to write a separate view for every use of a form, you can control just the parts that are specific to your needs.

If you need to work with a form for a specific model, there’s another generic view that can do even more for you. Rather than having to override several of those methods to create a ModelForm instance to do what you need, Django provides a few other classes for you to work with. These all live in django.views.generic.edit, because they allow you to edit data.

  • CreateView is used to help with the creation of new objects.
  • UpdateView is used when editing existing objects.
  • DeleteView is used for deleting existing objects.

All three views work in similar ways. To get the basic functionality, all you really need is to provide a model for them to work with. The views then handle the rest, including setting up the forms, validating user input, saving data and redirecting the user to appropriate URLs.

from django.views.generic import edit
from my_app.models import MyModel
 
class CreateObject(edit.CreateView):
    model = MyModel
 
class EditObject(edit.UpdateView):
    model = MyModel
 
class DeleteObject(edit.DeleteView):
    model = MyModel
    success_url = '/'

The only surprise here is that DeleteView actually does need a success_url specified, in addition to the model. Both CreateView and UpdateView result in a valid object, with data associated with it, so their default implementations can simply call the get_absolute_url() on the modified object.

In the case of DeleteView, the object being accessed no longer exists when the view is done working, so get_absolute_url() is not an option. Since there’s no standard way to describe URLs for object lists, Django can’t make any kind of guess as to where to send users. Therefore, you’ll always need to declare a success_url in order to use DeleteView properly.

Custom Fields

While the fields included with Django are suitable for most tasks, not every application fits neatly into a list of situations somebody else expected to be common. For those applications where the existing fields aren’t enough, it’s easy to define custom fields for forms, much like how fields for models can be created. It’s even easier to create form fields than model fields, since they don’t have to interact with the database.

The main difference between model fields and form fields is that forms only have to deal with string input, which greatly simplifies the process. There’s no need to worry about supporting multiple backends, each with its own complexities, much less all the different lookup types and relationships that add to the bulk of model fields.

As mentioned, all form fields inherit from Field, living at django.forms.fields. Because forms use this fact to distinguish fields from methods or other attributes, all custom fields must be part of this inheritance chain in order to work properly. Thankfully, Field provides a number of useful features that can make it much easier to implement a specific type of field.

Like many other classes, fields define a few attributes and methods to control specific behaviors, such as what widget to use and what error messages to display, as well as how to validate and clean incoming values. Any or all of them can be overridden to customize the functionality of a specific field.

Validation

Perhaps the most important behavior of a field is how it validates and cleans user input. After all, fields exist as a bridge between dangerous incoming data and a safe Python environment, so it’s essential that this translation be done properly. The field’s clean() method is primarily responsible for this, both for raising exceptions for improper data and for returning a cleaned value if the input is valid.

The method’s signature is simply clean(self, value), accepting the field object itself and also the incoming value. Then, if the value is deemed inappropriate according to the field’s requirements, it should raise an instance of django.forms.util.ValidationError with a message indicating what went wrong. Otherwise, it should convert the value to whatever native Python data type is appropriate for the field and return it.

In addition to making sure error messages are as descriptive as possible, it’s important to keep maintenance of error messages simple, while still allowing individual instances to override them. Django facilitates by way of a pair of attributes called error_messages and default_error_messages, as well as an argument called error_messages. This may seem like a tangled nest of values, but the way it works is rather simple.

A field class defines its standard error messages in a class-level attribute called default_error_messages. This is a dictionary mapping an easily-identifiable key to the actual error message string. Since fields will often inherit from other fields, which may define their own default_error_messages attributes, Django automatically combines them all into one dictionary when the field is instantiated.

In addition to using default_error_messages, Django allows individual field instances to override some of these messages by way of the error_messages argument. Any values in that dictionary will replace the default values for the keys specified, but only for that particular field instance. All other instances of the field will remain unaffected.

That means that error messages can come from three separate places: the field class itself, the field’s parent classes, and the arguments used to instantiate the field. When looking to raise an exception as part of the clean() method, there needs to be a simple way to retrieve a specific error message, regardless of where it was actually defined. For this, Django populates an error_messages attribute of every field instance, which contains all the messages that were defined in all three ways. This way, clean() can simply look up a key in self.error_messages and use its value as the argument to ValidationError.

from django.forms import fields, util
 
class LatitudeField(fields.DecimalField):
    default_error_messages = {
        'out_of_range': u'Value must be within -90 and 90.',
    }
 
    def clean(self, value):
        value = super(LatitudeField, self).clean(value)
        if not -90 <= value <= 90:
            raise util.ValidationError(self.error_messages['out_of_range'])
        return value
 
class LongitudeField(fields.DecimalField):
    default_error_messages = {
        'out_of_range': u'Value must be within -180 and 180.',
    }
 
    def clean(self, value):
        value = super(LatitudeField, self).clean(value)
        if not -180 <= value <= 180:
            raise util.ValidationError(self.error_messages['out_of_range'])
        return value

Note the use of super() here to call the clean() method of the parent DecimalField class, which first makes sure that the value is a valid decimal before bothering to check if it’s a valid latitude or longitude. Since invalid values result in an exception being raised, if the call to DecimalField.clean() allows code to continue executing, then it is assured that the value is a valid decimal.

Controlling Widgets

Two other attributes defined on field classes specify which widgets are used to generate HTML for the field in certain situations. The first, widget, defines the default widget to be used when the field instance doesn’t specify one explicitly. This is specified as a widget class, rather than an instance, as the widget is instantiated at the same time as the field itself.

A second attribute, called hidden_widget, controls which widget is used when the field should be output into the HTML, but not shown to the user. This shouldn’t have to be overridden, as the default HiddenInput widget is sufficient for most fields. Some fields, like the MultipleChoiceField, need to specify more than one value, so a special MultipleHiddenInput is used on those cases.

In addition to specifying individual widget classes for these situations, fields can also define a widget_attrs() method to specify a set of attributes that should be added to whatever widget is used to render the field in HTML. It receives two arguments, the usual self as well as widget, a fully-instantiated widget object that any new attributes will be attached to. Rather than attaching the attributes directly, widget_attrs() should return a dictionary of all the attributes that should be assigned to the widget. This is the technique the built-in CharField uses to assign a maxlength attribute to the HTML input field.

Defining HTML Behavior

Widgets, as mentioned in the previous section, are how fields represent themselves in a Web page as HTML. While fields themselves deal more with data validation and conversion, widgets are concerned with presenting the form and accepting user input. Each field has a widget associated with it, which handles the actual interaction with the user.

Django provides a variety of widgets, from basic text inputs to checkboxes and radio buttons, even multiple-choice list boxes. Each field provided by Django has, as its widget attribute, the widget that is most appropriate for the most common use cases for that field, but some cases may find need for a different widget. These widgets can be overridden on an individual field basis by simply supplying a different class to the field’s constructor as the widget argument.

Custom Widgets

Like fields, the widgets provided with Django are useful for the most common cases, but will not fit every need. Some applications may need to provide additional information, such as a unit of measurement, to help users enter data accurately. Others may need to integrate with client-side JavaScript libraries to provide extra options, such as calendars for selecting dates. These types of added features are provided with custom widgets, which satisfy the requirements of the field they are associated with, while allowing great flexibility in HTML.

While not strictly enforced like fields, all widgets should inherit from django.forms.widgets.Widget to receive the most common functionality from the start. Then, each custom widget can override whatever attributes and methods are most appropriate for the task it needs to perform.

Rendering HTML

The most common need for a custom widget is to present a customized field display for the user, by way of HTML. For example, if an application needs a field to handle percentages, it would make it easier for users to work with that field if its widget could output a percent sign (%) after the input field. This is possible by overriding the render() method of the widget.

In addition to the normal self, the render() method receives three additional arguments: the name of the HTML element, the value currently associated with it and attrs, a dictionary of attributes that should be applied to the element. Of these, only attrs is optional, and should default to an empty dictionary if not provided.

>>> from django import forms
>>> class PriceInput(forms.TextInput):
...     def render(self, name, value, attrs=None):
...         return '$ %s' % super(PriceInput, self).render(name, value, attrs)
...
>>> class PercentInput(forms.TextInput):
...     def render(self, name, value, attrs=None):
...         return '%s %%' % super(PercentInput, self).render(name, value, attrs)
...
>>> class ProductEntry(forms.Form):
...     sku = forms.IntegerField(label='SKU')
...     description = forms.CharField(widget=forms.Textarea())
...     price = forms.DecimalField(decimal_places=2, widget=PriceInput())
...     tax = forms.IntegerField(widget=PercentInput())
...
>>> print ProductEntry()
<tr><th><label for="id_sku">SKU:</label></th><td><input type="text" name="sku" i
d="id_sku" /></td></tr>
<tr><th><label for="id_description">Description:</label></th><td><textarea id="i
d_description" rows="10" cols="40" name="description"></textarea></td></tr>
<tr><th><label for="id_price">Price:</label></th><td>$ <input type="text" name="
price" id="id_price" /></td></tr>
<tr><th><label for="id_tax">Tax:</label></th><td><input type="text" name="tax" i
d="id_tax" /> %</td></tr>

Obtaining Values from Posted Data

Since widgets are all about dealing with HTML, and values are posted to the server using a format specified by HTML, in a structure dictated by HTML elements, widgets serve the extra purpose of translating between incoming data and the fields that data maps to. This not only insulates fields from the details of how HTML inputs work, it’s also the only way to manage widgets that use multiple HTML inputs, and allows widgets to fill in defaults, like None, in situations where nothing at all was submitted by the HTML input.

The widget method responsible for this task is value_from_datadict(), which takes three arguments in addition to the standard self.

  • data—The dictionary provided to the form’s constructor, usually request.POST
  • files—The files passed to the form’s constructor, using the same format as request.FILES
  • name—The name of the widget, which is essentially just the name of the field plus any prefix that was added to the form

The method uses all of this information to retrieve the value submitted from the browser, make any necessary changes and return a value suitable for fields to use. This should always return a value, defaulting to None if no suitable value could be found. All Python functions return None by default, if they don’t return anything else, so this rule is easily followed simply by ensuring that value_from_datadict() doesn’t raise any exceptions, but for the sake of readability, it’s always best to explicitly return None.

Splitting Data Across Multiple Widgets

Since widgets are a bridge between fields and HTML, they have a great deal of control over what HTML gets used, and how it reports back to the field. So much so, in fact, that it’s possible to split up a single field across multiple HTML field controls. Because of where the render() and value_from_datadict() hooks are placed in the flow, this can even be done without the field having to know it’s happening.

Exactly how this works depends largely on what HTML inputs the widget would use, but the general idea is simple. A field passes its value to the widget’s render() method, which breaks it up into multiple HTML inputs, each containing a piece of the original value. An example of this is having a separate text box for each of the date and time components of a DateTimeField.

Then, when the widget receives the data back through its value_from_datadict() method, it assembles these pieces back together into a single value, which is then handed back to the field. At no point does the field have to deal with more than one value, regardless of what the widget does.

Unfortunately, that all requires each widget to be responsible for all the HTML markup, as well as reassembling the value when it’s received. Sometimes it’s just as useful to simply combine two or more existing fields, relying on their widgets to do the job instead. Since it’s quite handy to have a utility to help with this, Django provides one.

To be accurate, Django provides two utilities: a field, MultiValueField, and a widget, MultiWidget, which are designed to work together. By themselves, they’re not terribly useful in the real world. Instead, they provide a significant share of the necessary features, while allowing subclasses to fill in the details that are specific to a particular use case.

On the field side of things, MultiValueField takes care of the details when cleaning data, by validating it against each of the individual fields that make up the composite. The only two things it leaves to the subclass are the definition of which fields should be combined and how their values should be compressed into a single value suitable for use by other Python code. In Django itself, for example, the SplitDateTimeField combines a DateField with a TimeField and compresses their values to a single datetime object.

The process of defining which fields should be used is simple, and is handled in the __init__() method of the new field class. All it takes is to populate a tuple with the field instances that should be combined. Then, simply pass this tuple as the first argument to the __init__() method of the parent class, which handles the rest from there. This keeps the method definition on the specific field quite simple, typically only a few lines long.

Compressing the values generated by those multiple fields takes place in the compress() method. This takes a single value in addition to the usual self, a sequence of values that should be combined into a single native Python value. What happens within can be a bit more complicated, though, as there are a few situations to take into account.

First, there’s the possibility that no value was submitted at all, for any part of the field, which would mean that the incoming data would be an empty list. By default, fields are required, in which case an exception would be thrown prior to calling compress(). If a field was declared with required=False, this is a very likely scenario, and the method should return None in this case.

In addition, it’s quite possible for just part of the value to be submitted, since it’s split across multiple HTML inputs. Again, if the field is required, this is handled automatically, but if the field is optional, compress() must still do some extra work to ensure that if any of the value is submitted, all of it is submitted. This is typically handled by just checking each item in the value sequence against the standard EMPTY_VALUES tuple, also located at django.forms.fields. Any portion of the field containing an empty value should then raise an exception informing the user of which portion of the field was missing a value.

Then, if all the values were submitted and were valid, compress() does its real work, returning a value suitable for use in Python when the form is being processed. The exact nature of this return value will depend entirely on the type of field being created, and how it’s expected to be used. Consider the following example of a field to accept latitude and longitude coordinates as separate decimals, combining them into a simple tuple.

from django.forms import fields
 
class LatLonField(fields.MultiValueField):
 
    def __init__(self, *args, **kwargs):
        flds = (LatitudeField(), LongitudeField())
        super(LatLonField, self).__init__(flds, *args, **kwargs)
 
    def compress(self, data_list):
        if data_list:
            if data_list[0] in fields.EMPTY_VALUES:
                raise fields.ValidationError(u'Enter a valid latitude.')
            if data_list[1] in fields.EMPTY_VALUES:
                raise fields.ValidationError(u'Enter a valid longitude.')
            return tuple(data_list)
        return None

With the field side of things out of the way, the next step is to create a widget that captures both of these elements separately. Since the intended display is simply two text boxes, it makes sense to make the custom widget a simple composite of two TextInput widgets, which solves the first challenge of identifying the widgets to be used. The base MultiWidget does a good job of rendering output and retrieving values from the incoming data, so the only challenge left is to convert the single compressed value into a list of values to be rendered by the individual widgets.

The counterpart to the field’s compress() method is, as you might expect, the widget’s decompress() method. Its signature is quite similar, taking just a single value, but its task is to split that value into as many pieces as there are widgets to render them. Ordinarily, this would be a matter of taking bits and pieces from a single value and putting them into a sequence, such as a tuple or a list. Since the LatLonField shown previously outputs its value as a tuple directly, the only thing that’s left is to supply a tuple of empty values if none was provided.

from django.forms import fields, widgets
 
class LatLonWidget(widgets.MultiWidget):
    def __init__(self, attrs=None):
        wdgts = (widgets.TextInput(attrs), widgets.TextInput(attrs))
        super(LatLonWidget, self).__init__(wdgts, attrs)
 
    def decompress(self, value):
        return value or (None, None)
 
class LatLonField(fields.MultiValueField):
    widget = LatLonWidget
 
    # The rest of the code previously described

Customizing Form Markup

In addition to defining custom widgets, it’s also possible to customize how forms themselves are rendered as HTML. Unlike the previous examples, the following techniques are used inside Django’s template language, where it’s a bit easier to make changes that are specific to an individual form.

The most obvious thing that can be customized is the actual <form> element, because Django forms don’t even output that at all. This is primarily because there’s no way to assume whether the form should use GET or POST, and what URL it should be sent to. Any form that needs to be submitted back to the server needs to have this specified by hand, so it’s a perfect opportunity for some specialization. When using a form that includes a FileField, for example, the <form> element needs to include an attribute such as enctype="multipart/form-data".

In addition to the form’s submission behavior, one common thing to configure is the presentation of the form, using Cascading Style Sheets (CSS). There are a number of ways to reference an element with CSS, but two of the most useful are by assigning an ID or a class, both of which are often placed on the <form> element itself. Since that element has to be defined, it’s easy to add these extra attributes as well.

In addition, there are often uses for configuring how the form’s field are displayed, depending on how the overall look of a site is achieved. Different sites may use tables, lists or even simple paragraphs to present forms, so Django tries to make it as easy as possible to accommodate these different scenarios.

When outputting a form in a template, there are a few methods available to choose which of these output formats to use. The default, as_table, wraps each field in a row, suitable for use in a standard table, while as_ul() wraps the fields in list items, and as_p() wraps them in paragraphs. None of these output any kind of element around all the fields, however; that’s left to the template, so that additional attributes can be added, such as IDs and classes for CSS referencing, just like the form element.

While these three provided methods are useful for their own purposes, they’re not necessarily enough for every situation. In keeping with DRY, each of them is in fact a customized wrapper around a common method, which wraps any kind of markup around all the fields in the form. This common method, _html_output(), shouldn’t be called directly from outside the form, but is perfectly suitable for use by another custom method designed for a more specific purpose. It takes a number of arguments, each specifying a different aspect of the HTML output.

  • normal_row—HTML to be used for a standard row. It’s specified as a Python format string that will receive a dictionary, so there are a few values that can be placed here:errors, label, field and help_text. Those should be fairly self-explanatory, except that field actually contains the HTML generated by the field’s widget.
  • error_row—HTML used for a row consisting solely of an error message, primarily used for form-level errors that aren’t tied to a specific field. It’s also used for forms that are configured to show field errors on a separate row from the field itself, according to the errors_on_separate_row option described at the end of this list. It’s also a Python format string, taking a single unnamed argument, the errors to be displayed.
  • row_enderMarkup used to identify the end of a row. Rather than appending this to the rows, since the preceding rows must have their endings specified directly, this is used to insert any hidden fields into a last row, just before its ending. Therefore, always make sure that the following is true: normal_row.endswith(row_ender).
  • help_text_html—HTML to be used when writing out help text. This markup will be placed immediately after the widget, and takes the help text as a single unnamed argument to this format string.
  • errors_on_separate_row—A Boolean indicating whether field errors should be rendered using the error_row prior to rendering the field itself. This doesn’t impact what values are passed to normal_row, so if the form expects errors to be on separate rows, be sure to leave errors out of that format string. Otherwise, errors will be printed twice.

Accessing Individual Fields

In addition to being able to customize a form’s overall markup in Python, on the form itself, it’s also quite simple to specify a form’s markup directly in a template. This way, forms are as reusable as possible, while still allowing templates to have final control over the rendered markup.

Form objects are iterable, using techniques described in Chapter 2. This means that templates can simply loop over them using the for block tag, with each iteration being a field on the form, which has been bound to a value. This bound field object can then be used to display the various aspects of a field, inside whatever markup makes the most sense to a template. It has a nice selection of attributes and methods to help the process along.

  • field—The original field object, with all of its associated attributes
  • data—The current value bound to the field
  • errors—An ErrorList (as described in the next section) containing all the errors for the field
  • is_hidden—A Boolean indicating whether the default widget is a hidden input
  • label_tag()—The HTML <label> element and its contents, for use with the field
  • as_widget()—The default rendering of the field, using the widget defined for it
  • as_text()The field rendered using a basic TextInput instead of its own widget
  • as_textarea()—The field rendered using a Textarea instead of the widget defined for it
  • as_hidden()The field rendered using a hidden input instead of any visible widget

Customizing the Display of Errors

By default, the markup used to display errors is specified by a special Python class called ErrorList, which lives at django.forms.util. This behaves just like a standard Python list, except that it has some extra methods for outputting its values as HTML. In particular, it has two methods by default, as_ul() and as_text(), which output errors as an unordered list or as unadorned text, respectively.

By creating a custom error class, as a subclass of ErrorList, it’s easy to override these methods to provide custom markup when errors are displayed. This markup includes any containing elements, such as <ul>, as the entire markup will be dropped in place wherever the field’s errors are displayed, whether as part of the default markup, or by accessing the field’s errors attribute directly.

By default, the as_ul() method is used to render errors, though templates that wish to do further customizations can call whichever method makes the most sense for the template. In fact, it’s possible to add entirely new methods and even override which method is used by default by also overriding the __unicode__() method. It’s also possible for templates to simply loop through the errors in this list and wrap each one in whatever markup makes sense for the situation.

Writing a custom ErrorList subclass isn’t quite enough; it also has to be passed into the form somehow to make sure it gets used. This is also quite simple: just pass the custom class into the form’s constructor as the error_class argument.

In addition to displaying errors on individual fields, a form’s clean() method allows errors to be shown for form-wide validation failures. Displaying this in the template requires accessing the form’s non_field_errors() method.

Applied Techniques

While Django’s forms are primarily designed to handle a fairly common user input requirement, they can be made to do some complicated legwork. They can be used either individually or in groups to extend the user interface even further. Nearly any form of user input can be represented using a Django form; the following is just a sample of what’s available.

Pending and Resuming Forms

Forms are generally intended to receive input all at once, process that input and behave accordingly. This is something of a one-off cycle, where the only reason a form would have to be redisplayed would be to show validation errors, allowing the user to fix them and resubmit. If a user needs to stop working on a form for a time and come back later, that means starting over from scratch.

While this is generally the accepted approach, it can also be a burden for complex forms or those where the user might need to provide information that takes time to gather, such as tax information. In these situations, it would be much more useful to be able to save the form in a partially-filled state and return to it at a later point in time. That’s not how forms typically work, so there’s clearly some work to be done, but it’s really not that hard.

Since forms are declared as classes, and there’s no reason to violate that presumption, the class developed hereafter will be usable as a parent class, just like forms.Form. In fact, for all intents and purposes, it should be a drop-in replacement for the standard class, simply imbuing its subclasses with extra functionality. Consider the following form for making an offer on a house in a properties application, something that usually won’t be taken lightly. By allowing the form to be pended and resumed at a later time, users can take the necessary time to review an offer before committing to such an investment.

from django import forms
from django_localflavor_us import forms as us_forms
 
from pend_form.forms import PendForm
 
class Offer(PendForm):
    name = forms.CharField(max_length=255)
    phone = us_forms.USPhoneNumberField()
    price = forms.IntegerField()

Note that, aside from the switch to PendForm, this is defined like any other standard Django form. The advantages of this simple change are described in the following sections, which outline a new pend_form application.

Storing Values for Later

In order to save a form in a partially completed state, its current values must be stored in the database somehow. They’d also have to be tied to field names, so they can be used later to re-create the form. This sounds like a job for dynamic models, which can be created automatically, based on the form’s definition, to store values efficiently. However, they aren’t appropriate for this use case, for a few reasons.

For one thing, form fields don’t have directly equivalent model fields. Since the dynamic model would have to be filled with fields that can contain the same data as the form fields, there would have to be some way to determine a model field based on a form field. Model fields do define form fields that can be used with them, but not the other way around.

Technically, it would be possible to manually provide a mapping of form fields to model fields, so that such models could be created anyway. This would have its fair share of problems as well, since it wouldn’t be able to support custom form fields. Essentially, any form field that isn’t present in that mapping wouldn’t have a matching model field, and the technique would fail.

Also, storing field values in model fields that are based on the form’s field types would require converting those values into Python objects first, which would mean that they’d all have to be valid values. It should be possible to pend a form, even with invalid values, so that they can be corrected later. This wouldn’t be at all possible if the values had to be stuffed into model fields with specific data types, which included either data validation or type-checking.

Instead, we can rely on the fact that all form data, when submitted back to the server, arrive as strings. These strings must be converted to native Python objects as part of the form validation process, so the strings themselves are the last chance to get the actual raw data from the submitted form. Better yet, since they’re all strings, Django provides an easy way to store them for later use: TextField. A TextField is necessary, because different form values provide different lengths of data, some of which will likely extend beyond the 255-character limit of CharField.

With a reliable way to store values, the next step is to identify what other information must be stored in the database, in order to reconstitute the form. Obviously the names of the fields would be included, so the values could get put back in the right place. Also, since different forms could have different structures, with different numbers of fields, it would be best to give each field’s value its own row in the database. That means there would need to be a way of keeping fields together as part of a form.

The trick here is that forms don’t have a unique identifier. After all, they’re not normally expected to exist outside of a specific request/response cycle, except for validation corrections, where the entire form is resubmitted as part of the new request. There’s simply no built-in way to identify an instance of a form, so something different will have to be used.

One very common way of identifying complex structures like this is to create a hash based on the data. While hashes aren’t guaranteed to be unique, they’re close enough for most purposes, and there are some things that can be included along with a hash to get better odds of uniqueness.

In the case of a form, this hash can be taken from the complete collection of field data, so that a change in any name or value would result in a change in the hash that data would produce. Another piece of information that can be stored alongside the hash is the import path to the form, which allows for differentiation among multiple sets of data, if there are multiple forms with the same collection of fields.

Now that there are a few pieces of information to store, consider how they should relate to each other. There are essentially two levels here: the form and its values. These could be taken as two separate models, relating multiple values to a single form by way of a standard foreign key relationship. The form side would contain the form’s path as well as the hash of all its values, while the value side would contain the names and values of each field, as well as a reference back to the form it belongs with.

The models.py module of the pend_form application looks like this:

class PendedForm(models.Model):
    form_class = models.CharField(max_length=255)
    hash = models.CharField(max_length=32)
 
class PendedValue(models.Model):
    form = models.ForeignKey(PendedForm, related_name='data')
    name = models.CharField(max_length=255)
    value = models.TextField()

This simple structure is now capable of storing any amount of data for any form. It wouldn’t be very efficient if the application needed to make complex queries on the form’s data, but since it’s just being used to save and restore the contents of a form all at once, it’ll work quite well.

Now that there are models in place to contain the form’s data, there needs to be a way to actually store that data for later retrieval. Thankfully, forms are just standard Python classes, so it’s easy enough to just write an extra method that handles this task directly. Then, when the time comes to write a specific form that needs this capability, it can simply subclass the following form, rather than the usual forms.Form. This is placed in a new forms.py module in our pend_form application.

try:
    from hashlib import md5
except:
    from md5 import new as md5
 
from django import forms
 
from pend_form.models import PendedForm
 
class PendForm(forms.Form):
    @classmethod
    def get_import_path(cls):
        return '%s.%s' % (cls.__module__, cls.__name__)
 
    def hash_data(self):
        content = ','.join(%s:%s' % (n, self.data[n]) for n in self.fields.keys())
        return md5(content).hexdigest()
 
    def pend(self):
        import_path = self.get_import_path()
        form_hash = self.hash_data()
        pended_form = PendedForm.objects.get_or_create(form_class=import_path,
                                                       hash=form_hash)
        for name in self.fields:
            pended_form.data.get_or_create(name=name, value=self.data[name])
        return form_hash

Note the liberal use of get_or_create() here. If an instance of a form already exists with exactly the same values, there’s no sense saving the whole thing twice. Instead, it simply relies on the fact that the previous copy will be functionally identical, so it’ll work for both.

Reconstituting a Form

Now that forms can be placed in the database without being fully processed, or even validated, their usefulness is still limited if they can’t be retrieved later, for the user to continue working on them. The data is stored in such a way that it can be reassembled into a form, all that’s left is to actually do so.

Since the code to do this must, by definition, be called prior to having a form instance to work with, it may seem like it must be in a module-level function. Remember that methods can be declared to be used on the class, rather than the instance, if the need arises. Since the goal here is to have all of this functionality encapsulated on a subclass, without having to worry about where all the machinery itself is written, a class method will do the trick here quite well.

What actually goes on in this new class method is a bit more interesting. In order to instantiate a form, it takes a dictionary as its first argument, which is usually just request.POST, available to all views. When loading the form later, the new request has absolutely nothing to do with the form, much less does it contain the appropriate data, so that dictionary must be constructed manually, from the data previously stored in the database.

This data may be referenced by the form hash described earlier, along with the import path of the form being used. Those two pieces of information are all that’s needed to properly locate and retrieve all the field’s values from the database. Since the form already knows how to get its import path, thanks to one of the methods described previously, all that’s left is to provide the form’s hash manually. This would most likely be captured in a URL pattern, though different applications may have different ways to go about that.

Once the hash is known, the method for resuming a form should be able to accept that, combine it with its own import path, retrieve the values from the database, populate a dictionary based on those values, instantiate a new copy of the form with those values, and return that new form for other code to use. That sounds like an awful lot of work, but it’s a lot easier than it may seem.

One thing that comes to the rescue here is how Python’s own dictionaries can be instantiated. The built-in dict() can accept a variety of different argument combinations, but one of the most useful is a sequence of 2-tuples, each containing the name and value of an entry in the intended dictionary. Since QuerySets return sequences already, and tools like list comprehensions and generator expressions can easily create new sequences based on them, it’s quite easy to create something suitable.

Getting the import path and looking up the saved form is easy, and that object’s data attribute provides easy access to all of its values. Using a generator expression, the data’s name/value pairs can be easily passed into the built-in dict(), creating a dictionary that can be passed into the form object’s constructor. All is made clear by the code.

@classmethod
def resume(cls, form_hash):
    import_path = cls.get_import_path()
    form = models.PendForm.objects.get(form_class=import_path, hash=form_hash)
    data = dict((d.name, d.value) for d in form.data.all())
    return cls(data)

This simple method, when called with a form’s generated hash value, will return a fully-formed form object, ready to be validated and presented to the user for further review. In fact, validation and presentation will be the typical workflow in this case, giving the user a chance to see if there was anything to add or correct, before deciding to commit the form or pend it again for later.

A Full Workflow

As mentioned earlier, the normal workflow is fairly standard, with little variation across all the various forms that are in use in the wild. By allowing forms to be pended or resumed, there’s an optional extra step added to the workflow, which requires some added handling in the view. Adding this new piece to the puzzle, the overall workflow looks a bit like this:

  1. Display an empty form.
  2. User fills in some data.
  3. User clicks Submit.
  4. Validate data submitted by the user.
  5. Display the form with errors.
  6. User clicks Pend.
  7. Save form values in the database.
  8. Validate data retrieved from the database.
  9. Display the form with errors.
  10. Process the completed form.

In order to maintain this entire workflow, the view gets a bit more complicated. There are now four separate paths that could be taken, depending on which part of the workflow is being processed at any given time. And remember, this is all just to take the necessary steps to handle the form. It doesn’t take into account any of the business logic required for a specific application.

  • User requests a form without any data.
  • User posts data using the Pend button.
  • User requests a form using a form hash.
  • User posts data using the Submit button.

From there, the typical workflow steps still apply, such as checking the validity of the input data and taking the appropriate steps that are specific to the application’s functionality. Once this is all rolled up together in a view, it looks something like this:

from django import http
from django.shortcuts import render_to_response
from django.template.context import RequestContext
 
from properties import models, forms
 
def make_offer(request, id, template_name='', form_hash=None):
    if request.method == 'POST':
        form = forms.Offer(request.POST)
        if 'pend' in request.POST:
            form_hash = form.pend()
            return http.HttpRedirect(form_hash)
        else:
            if form.is_valid():
                # This is where actual processing would take place
    else:
        if form_hash:
            form = forms.Offer.resume(form_hash)
        else:
            form = forms.Offer()
 
 
    return render_to_response(template_name, {'form': form},
                              context_instance=RequestContext(request))

There’s a lot going on here, but very little of it has anything to do with making an offer on a house. The vast majority of that code exists solely to manage all the different states the form could be in at any given time, and would have to be repeated every time a view uses a PendForm subclass, and that’s not efficient.

Making It Generic

While it’s easy to see which aspects of the view are repetitive, and should thus be factored out into something reusable, it’s a bit trickier to decide how to do so. The main issue is that the portion of the code that’s specific to this particular view isn’t just a string or a number, like has been shown in most of the previous examples, but rather a block of code.

This is something of a problem, because previous examples had shown how generic views can be used to factor out commonalities, while allowing specific differences to be specified in a URL pattern. That works well for basic data types, such as strings, numbers, sequences and dictionaries, but code is handled differently. Instead of being able to just specify the value inline in the URL pattern, this code must be defined in a separate function, which is then passed in to the pattern.

While that’s certainly possible, it makes the URL configuration module a bit more cumbersome, given that there might be a number of top-level functions declared above each block of URL patterns. Lambda-style functions could be a way around this, but since they’re restricted to executing simple expressions, with no loops or conditions, they’d severely limit the type of code that could be used.

One alternative is a decorator, which could be applied to a standard function, providing all of the necessary functionality in a wrapper. This way, any function can be used to contain the code that will actually process the form, with the full capabilities of Python at its disposal. That code also wouldn’t have to deal with any of the boilerplate necessary to pend or resume the form, because the decorator could do all that before the view code itself even executes, simply passing in a form as an argument. Here’s how the previous view could look, if a decorator was used to remove the boilerplate.

from pend_forms.decorators import pend_form
 
@pend_form
def make_offer(request, id, form):
    # This is where actual processing would take place

Now all that’s left is to write the decorator itself, encapsulating the functionality that was removed from the previous example, wrapping it around a view that would be passed in. This would be placed in a new decorators.py module.

from django import http
from django.shortcuts import render_to_response
from django.template.context import RequestContext
 
from django.utils.functional import wraps
 
def pend_form(view):
    @wraps(view)
    def wrapper(request, form_class, template_name,
                form_hash=None, *args, **kwargs):
        if request.method == 'POST':
            form = form_class(request.POST)
            if 'pend' in request.POST:
                form_hash = form.pend()
                return http.HttpRedirect(form_hash)
            else:
                if form.is_valid():
                    return view(request, form=form, *args, **kwargs)
        else:
            if form_hash:
                form = form_class.resume(form_hash)
            else:
                form = form_class()
 
        return render_to_response(template_name, {'form': form},
                                  context_instance=RequestContext(request))
    return wrapper

Now, all that’s necessary is to set up a URL configuration that provides both a form class and a template name. This decorator will handle the rest, only calling the view when the form has been completed and submitted for processing.

A Class-Based Approach

Now that you’ve seen how this could be done with traditional, function-based views, remember that Django’s new class-based views offer a different approach to many problems, and this is no exception. Earlier in this chapter, you saw how the FormView class provides most of what you need to work with forms, and we can extend that to work with our pending functionality as well. In fact, because we can add new methods to the view class, it’s no longer necessary to provide a custom Form subclass. It can be done using any stock form you have in your code.Let’s start by retrieving a previously pended form. Some of the utility methods that were previously placed on the Form subclass can be reused here intact, but we also need a way to pass the existing values into the new form, which is a perfect task for get_form_kwargs().

from django.views.generic.edit import FormView
from pend_form.models import PendedValue
 
class PendFormView(FormView):
    form_hash_name = 'form_hash'
 
    def get_form_kwargs(self):
        """
        Returns a dictionary of arguments to pass into the form instantiation.
        If resuming a pended form, this will retrieve data from the database.
        """
        form_hash = self.kwargs.get(self.form_hash_name)
        if form_hash:
            import_path = self.get_import_path(self.get_form_class())
            return {'data': self.get_pended_data(import_path, form_hash)}
        else:
            return super(PendFormView, self).get_form_kwargs()
 
    # Utility methods
 
    def get_import_path(self, form_class):
        return '%s.%s' % (form_class.__module__, form_class.__name__)
 
    def get_pended_data(self, import_path, form_hash):
        data = PendedValue.objects.filter(import_path=import_path, form_hash=form_hash)
        return dict((d.name, d.value) for d in data)

Since the purpose of get_form_kwargs() is to supply arguments to the instantiation of the form, all we really need to do here is retrieve the appropriate values and return them instead of the default values. This will suffice to fill a populated form if a form hash is provided in the URL.

Notice also that form_hash_name is included as a class-level attribute. This allows users of this view to override what argument indicates that the form is being pended. All you need to do is supply it as a class attribute, and Django will allow it to be customized, falling back to your defined value as a default.

The next stage will allow users to actually save form values for later. As before, this will need to store the form and its values in the database, along with a hash of that information for later retrieval. In addition to some additional utilities, the bulk of the work must be done in the post() method, because that’s our entry point when the form is submitted.

The raw functionality of saving the form involves quite a few pieces, some of which can be reused from the previous steps. Here’s what’s needed for saving forms for later, so we can discuss it before showing all the code together.

from django.views.generic.edit import FormView
from pend_form.models import PendedForm, PendedValue
 
class PendFormView(FormView):
    pend_button_name = 'pend'
 
    def post(self, request, *args, **kwargs):
        """
        Handles POST requests with form data. If the form was pended, it doesn't follow
        the normal flow, but saves the values for later instead.
        """
        if self.pend_button_name in self.request.POST:
            form_class = self.get_form_class()
            form = self.get_form(form_class)
            self.form_pended(form)
        else:
            super(PendFormView, self).post(request, *args, **kwargs)
 
    # Custom methods follow
 
    def get_import_path(self, form_class):
        return '%s.%s' % (form_class.__module__, form_class.__name__)
 
    def get_form_hash(self, form):
        content = ','.join('%s:%s' % (n, form.data[n]) for n in form.fields.keys())
        return md5(content).hexdigest()
 
    def form_pended(self, form):
        import_path = self.get_import_path(self.get_form_class())
        form_hash = self.get_form_hash(form)
        pended_form = PendedForm.objects.get_or_create(form_class=import_path,
                                                       hash=form_hash)
        for name in form.fields.keys():
            pended_form.data.get_or_create(name=name, value=form.data[name])
        return form_hash

The post() method normally dispatches between the form_valid() and form_invalid() methods, but since a pended form isn’t necessarily valid or invalid, it needs to be overridden to provide a third dispatch option. That third dispatch is handled by form_pended(), named to coincide with Django’s own form validity methods. It does the work of saving the form and its associated data, reusing some utilities from Django as well as the previous iteration for displaying the pended form.

And here’s what it all looks like together:

from django.views.generic.edit import FormView
from pend_form.models import PendedForm, PendedValue
 
class PendFormView(FormView):
    form_hash_name = 'form_hash'
    pend_button_name = 'pend'
 
    def get_form_kwargs(self):
        """
        Returns a dictionary of arguments to pass into the form instantiation.
        If resuming a pended form, this will retrieve data from the database.
        """
        form_hash = self.kwargs.get(self.form_hash_name)
        if form_hash:
            import_path = self.get_import_path(self.get_form_class())
            return {'data': self.get_pended_data(import_path, form_hash)}
        else:
            return super(PendFormView, self).get_form_kwargs()
 
    def post(self, request, *args, **kwargs):
        """
        Handles POST requests with form data. If the form was pended, it doesn't follow
        the normal flow, but saves the values for later instead.
        """
        if self.pend_button_name in self.request.POST:
            form_class = self.get_form_class()
            form = self.get_form(form_class)
            self.form_pended(form)
        else:
            super(PendFormView, self).post(request, *args, **kwargs)
 
    # Custom methods follow
 
    def get_import_path(self, form_class):
        return '{0}.{1}'.format(form_class.__module__, form_class.__name__)
 
    def get_form_hash(self, form):
        content = ','.join('{0}:{1}'.format(n, form.data[n]) for n in form.fields.keys())
        return md5(content).hexdigest()
 
    def form_pended(self, form):
        import_path = self.get_import_path(self.get_form_class())
        form_hash = self.get_form_hash(form)
        pended_form = PendedForm.objects.get_or_create(form_class=import_path,
                                                       hash=form_hash)
        for name in form.fields.keys():
            pended_form.data.get_or_create(name=name, value=form.data[name])
        return form_hash
 
    def get_pended_data(self, import_path, form_hash):
        data = PendedValue.objects.filter(import_path=import_path, form_hash=form_hash)
        return dict((d.name, d.value) for d in data)

Now you can use this like you would any other class-based view. All you need is to provide it a form class, as well as override any of the default values specified here or in FormView itself. Templates, button names and URL structures are customizable by simply subclassing the PendFormView and working from there. The only thing you’ll need to do beyond that is to add a button to your template to allow your users to pend the form.

Now What?

In order to be truly useful in the real world, forms must be presented to users as part of an HTML page. Rather than trying to generate that HTML content directly inside Python code, Django provides templates as a more designer-friendly alternative.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset