Django Forms

Form Handling in Django, and the Form-handling CBVs


We will need to work with the form system in Django if we want to add or update books in our app.

Django’s form system has a couple of layers to it.

We’ll make an analogy. “Models are to the Database as Forms are to request/response cycle.”

The job of a model in Django is to translate data between python and the database. The job of a form in Django is to translate data back and forth between python and the request/reponse cycle. A form takes in information that comes from the request and translates it into pythonic values.

Remember, every time you get information coming in on the request, that information is coming in as a bytestring. When you pass a primary key back and forth across a URL, that comes into your app as a bytestring. You want to turn that into an integer so you can interact with the database. The Django form is going to make that happen for you.

This can start to get weird because Models have fields, and Forms have fields. They look similar, but they are very different from each other. The model field holds the responsibility of translating a particular python value into some sort of database query. Also it takes the return value and turns it back into a python value. The form does the same thing except it does it with data from the request and response. It turns one item of information into a pythonic value.

Forms serialize and deserialize, meaning that they standardize and arrange your data for output to some other medium. They do it with a structure called a widget. The widget renders out HTML elements, visible or hidden, that then is rendered into an HTML page.

Let’s say we have something that’s a forms.CharField field. It will have by default use the forms.TextInput widget, that has a text input. When you call the widget’s render method, it will give you input[type="text"] and value = field contents (empty if None). This is the overall structure of how form libraries everywhere work.

Forms in Practice

So how do forms in Django work? A form is just a collection of fields.

Let’s start up our Django shell:

In [4]: from django import forms

In [5]: class FooForm(forms.Form):
   ...:     name = forms.CharField(label="Your Name", max_length=100)
   ...:     description = forms.CharField(label="Describe Something", widget=forms.Textarea, max_length=2048)
   ...:

In [6]: FooForm
Out[6]: __main__.FooForm

In [7]: type(FooForm)
Out[7]: django.forms.forms.DeclarativeFieldsMetaclass

In [8]:  form1 = FooForm()

In [9]: form1
Out[9]: <FooForm bound=False, valid=Unknown, fields=(name;description)>

In [10]: type(form1_form)
Out[10]: __main__.FooForm

There are a few things to think about here. Our form can be in one of two states: either bound or unbound. The difference: a bound form knows about data. It has some information that has been passed into it. An unbound form doesn’t have any data attached.

Now, we have a working form called form1. It tells us that it is unbound. It doesn’t know if it is valid yet, and it has some fields.

If we just call str() on our form1, it will render out into raw HTML:

In [11]: str(form1)
Out[11]: '<tr><th><label for="id_name">Your Name:</label></th><td><input type="text" name="name" maxlength="100" required id="id_name" /></td></tr>\n<tr><th><label for="id_description">Describe Something:</label></th><td><textarea name="description" cols="40" rows="10" maxlength="2048" required id="id_description">\n</textarea></td></tr>'

Note that the labels we specified for the fields we wanted rendered out for us as well. It also rendered out a few things that we didn’t ask for, at least not explicitly:

  • Each form field has an ID that’s a concatenation of the word id and the name of the field we gave
  • Each field’s name attribute corresponds to the actual name of the field we specified in FooForm
  • Each field is required, even though we didn’t ask for that

The form1.as_p() method will render our form with <p> tags:

In [12]: form1.as_p()
Out[12]: u'<p><label for="id_name">Your Name:</label> <input type="text" name="name" maxlength="100" required id="id_name" /></p>\n<p><label for="id_description">Describe Something:</label> <textarea name="description" cols="40" rows="10" maxlength="2048" required id="id_description">\n</textarea></p>'

There are also as_table() and as_ul() methods. as_table() is the default and the same as our str() rendering.

Notice that the <form> tag is missing. There is also no <submit> button. That’s up to you.

Bound Form

Let’s make another form. We’ll pass in some data as a dictionary, and then the form will be bound.

In [13]: form2 = FooForm({'name': 'Nick', 'description': 'Instructor for the Python 401 course at Code Fellows'})

In [14]: form2
Out[14]: <FooForm bound=True, valid=Unknown, fields=(name;description)>

Now we can use that information to fill out the form with the data that exists. Notice the value="Nick" attribute:

In [15]: form2.as_p()
Out[15]: u'<p><label for="id_name">Your Name:</label> <input type="text" name="name" value="Nick" maxlength="100" required id="id_name" /></p>\n<p><label for="id_description">Describe Something:</label> <textarea name="description" cols="40" rows="10" maxlength="2048" required id="id_description">\nInstructor for the Python 401 course at Code Fellows</textarea></p>'

Forms are also iterators. These objects come out in the order that you specify the attributes. If you want to change the ordering of a form, change it in the Django form definition.

In [16]: for field in form2:
   ....:     print(field)
   ....:
<input type="text" name="name" value="Nick" maxlength="100" required id="id_name" />
<textarea name="description" cols="40" rows="10" maxlength="2048" required id="id_description">
Instructor for the Python 401 course at Code Fellows</textarea>

If you aren’t happy with the way Django is rendering <p> forms or whatever, you can address the field properties directly and lay them out how you like:

In [17]: field.id_for_label
Out[17]: u'id_description'

In [18]: field.label
Out[18]: u'Describe Something'

In [19]: field.value()
Out[19]: u'Instructor for the Python 401 course at Code Fellows'

It’s probably best to let Django render the fields for you.

The valid value of a form is also important. When a form is bound, you can do validation checks on it.

In [20]:  form2.is_valid()
Out[20]: True

This allows us access to cleaned_data:

In [21]: form2.cleaned_data
Out[21]: 
{'description': 'Instructor for the Python 401 course at Code Fellows',
 'name': 'Nick'}

cleaned_data is a dictionary of key-value pairs that correspond to the field names and the values those fields contain. It is super important. Why? Because: form2 also has a data attribute:

In [22]: form2.data
Out[22]:
{'description': 'Instructor for the Python 401 course at Code Fellows',
 'name': 'Nick'}

It looks the same in this case, but the difference is that cleaned_data has gone through validation. Thus you can be sure that if someone is trying to inject malicious data, Django has already sterilized it and it is safe for python to use.

Whenever you’re reading data from a form, always validate the data first. Then use the cleaned_data attribute of your form to get access to the information.

Let’s build one that might not be validated.

In [23]: form3 = FooForm({'name': 'My name is reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally loooooooong', 'description': ''})

In [24]: form3
Out[24]: <NameForm bound=True, valid=Unknown, fields=(your_name)>

Notice it is bound, but we don’t know yet if it is valid. That valid=Unknown flag will only get decided after a manual check for validity. When we check the validity:

In [23]: form3.is_valid()
Out[23]: False

In [24]: form3.cleaned_data
Out[24]: {}

This particular form will have an errors attribute now:

In [25]: form3.errors
Out[25]: 
{'description': ['This field is required.'],
 'name': ['Ensure this value has at most 100 characters (it has 113).']}

Notice how even though we specified the “description” field, Django interprets the data as empty because we passed it an empty string as a value.

You can use the errors attribute to send error messages back to your forms.

In [26]: for error_field in form3.errors:
    print("There's a mistake in field '{field}': {problem}".format(
        field=error_field,
        problem=form3.errors[error_field]
    ))
   ....:
There's a mistake in field 'name': <ul class="errorlist"><li>Ensure this value has at most 100 characters (it has 113).</li></ul>
There's a mistake in field 'description': <ul class="errorlist"><li>This field is required.</li></ul>

Django makes some nice default error messages for us with HTML markup too. There is ample API documentation for forms, like how to substitute css classes and things like that. Dig in when you get the chance!

ModelForm

There’s something else you should know about forms: the way we built forms above is not the only way.

Your app is filled up model objects. One thing you would like to do is present forms that allow you to create and edit those models directly. For this purpose, Django provides a construct called a ModelForm.

What a ModelForm does is inspect your model and its fields. It then creates a form that has all the equivalent form fields to your model’s fields.

To put a ModelForm to use:

  • Create a class that subclasses ModelForm.
  • Provide a class Meta
    • This informs the form object about some options that you can set for it.
In [27]: from lender_books.models import Book

In [28]: Book
Out[28]: lender_books.models.Book

In [29]: class BookForm(forms.ModelForm):
  ....:     class Meta:
  ....:         model = Book
  ....:         exclude = []
  ....:

In [30]: BookForm
Out[30]: __main__.BookForm

In [31]: bkfm1 = BookForm()

In [32]: bkfm1
Out[32]: <BookForm bound=False, valid=Unknown, fields=(title;author;cover_image;status)>

This looks familiar. Our fields automatically include all of the fields on the Book model.

Let’s grab ourselves an instance of the Book object:

In [33]: Book.objects.all()
Out[33]: <QuerySet [<Book: Book object>]>

In [34]: bk1 = _[0]

In [35]: bk1
Out[35]: <Book: Book object>

When we create a BookForm, if we want to bind it to an instance, we can use the instance keyword argument to instantiate our new BookForm:

In [36]: lovely_form = BookForm(instance=bk1)

In [37]: lovely_form
Out[37]: <BookForm bound=False, valid=Unknown, fields=(title;author;cover_image;status)>

In [38]: lovely_form.as_p()
Out[38]: '<p><label for="id_title">Title:</label> <input id="id_title" maxlength="255" name="title" type="text" value="Catcher in the Rye" required /></p>\n<p><label for="id_author">Author:</label> <input id="id_author" maxlength="255" name="author" type="text" value="J.D. Salinger" required /></p>\n<p><label for="id_status">Status:</label> <select id="id_status" name="status" required>\n<option value="available" selected="selected">Available</option>\n<option value="checked out">Checked Out</option>\n</select></p>'
...'

Django gives us all sorts of options here that allows us to manipulate our model.

Notice that lovely_form is not bound:

In [39]: lovely_form
Out[39]: <BookForm bound=False, valid=Unknown, fields=(title;author;cover_image;status)>

Why do we suppose that is? Because we haven’t passed in any data. We use model forms to take data from somewhere else and either create a new model instance or edit an existing model form. Simply providing an instance doesn’t bind the form to anything.

If we make a change, the form will become bound, and the selected attribute will now be 'bob' (our user 3 in this case):

In [40]: changed_form = BookForm({'title': 'Malcolm X'}, instance=bk1)

In [41]: changed_form
Out[41]: <BookForm bound=True, valid=Unknown, fields=(title;author;cover_image;status)>

In [42]: str(changed_form)
Out[42]: '<tr><th><label for="id_title">Title:</label></th><td><input id="id_title" maxlength="255" name="title" type="text" value="Malcolm X" required /></td></tr>\n<tr><th><label for="id_author">Author:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input id="id_author" maxlength="255" name="author" type="text" required /></td></tr>\n<tr><th><label for="id_cover_image">Cover image:</label></th>......

Let’s look at a bit of this:

<input id="id_title" maxlength="255" name="title" type="text" value="Malcolm X" required />

We see that our provided dictionary’s data takes precedence. The base instance that was attached to the form will still provide its data to the form, but it doesn’t take priority.

Is the form valid?

In [43]: changed_form.is_valid()
Out[43]: False

In [44]: changed_form.errors
Out[44]: {'author': ['This field is required.'], 'status': ['This field is required.']}

So let’s fix our errors and inspect our book:

In [45]: changed_form = BookForm({"author": "Alex Haley", "status": "available", "title": "Malcolm X"}, instance=bk1)

In [46]: changed_form.is_valid()
Out[46]: True

In [47]: bk1.title
Out[47]: 'Catcher in the Rye'

Even though the data provided to the form was valid, nothing changed about the book object itself because the form wasn’t saved. Let’s remedy that.

In [48]: changed_form.save()
Out[48]: <Book: Book object>

In [49]: bk1.title
Out[49]: 'Malcolm X'

Our instance has already been updated by the fact that we bound some data to a form. Once we save the form, it will be updated in the database too.

Forms and the WRRC

We saw how we passed in an instance of a form and some data. Where does the data come from when we’re not working in terminal?

In Django you have a request object available within inside any view you create. Amongst others, request will have 2 attributes of importance attached. These attributes will contain any form data that got sent to you from the browser. Depending on the type of request, data will either be in request.post or request.get. Using forms you’ll probably want to use post.

Most of this process will be similar to what you have done already with create and update views.

request.post is a dictionary, and you can pass it in as part of the data that comes to you. Provided that everything coming from the request is what you need to create a new model instance or update one, you can pass it directly.

updated_book = BookForm(request.post)

If your data needs to be massaged at all, you’ll need to do that work and then pass the resulting data into the BookForm.

Forms and Files

You are going to want to upload books with images of their covers. This might be a bit odd. That image data (and any other uploaded data) will be coming to you not in request.post or get. They come in a completely different place called request.files.

There’s one more trick to this. In order to get image data, you have to do something to your HTML <form> tag. There’s an attribute to forms called the enctype. In order to upload images, you need to set the enctype="multipart/form-data" attribute

On the other end, you need to make sure you bind your form with not only the first dictionary, but also a second containing the file information:

In [50]: changed_form = BookForm({"author": "Alex Haley", "status": "available", "title": "Malcolm X"}}, {'cover_image': <file upload object>}, instance=bk1)

Django is responsible for dealing with all of this. All you need to do is pass in request.post, request.files, instance=the_original_instance. If you’re creating a new instance, just don’t pass in an instance and Django will build a new one for you when you call form.save()