sea_d35_day28_django_forms

Day 28: Form Handling in Django, and the Form-handling CBVs


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

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

(Cris is on whiteboard here)

We’ll make an analogy. “Models are to 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 to you as a bytestring. When you pass a primary key back and forth across a URL, that comes into you 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. They do it with a structure called a widget. The widget renders out visible elements (or hidden) that renders that data into a HTML page.

Let’s say we have something that’s a forms.text field. It will have a widget that has a text input. When you call the widget’s render method, it will give you inputtype = text and value = field contents. This is the overall structure of how form libraries everywhere work.

(Back to computer)

(6:08)

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 [8]: class foo(forms.Form):
    name = forms.TextInput()
    description = forms.Textarea()
   ...:     

In [9]: foo
Out[9]: __main__.foo

In [10]: type(foo)
Out[10]: django.forms.forms.DeclarativeFieldsMetaclass

In [11]:  foo_form = foo()

In [12]: foo_form
Out[12]: <foo bound=False, valid=Unknown, fields=()>

In [13]: type(foo_form)
Out[13]: __main__.foo

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

(We go back to the Django shell, but the foo_form isn’t really working. We grab a working snippet from the Django docs:)

In [16]: %paste
from django import forms

class NameForm(forms.Form):
    your_name = forms.CharField(label='Your name', max_length=100)

## -- End pasted text --

In [17]: form1 = NameForm()

In [18]: form1
Out[18]: <NameForm bound=False, valid=Unknown, fields=(your_name)>

Now we have a working form called form1. It tells us that it’s unbound. It doesn’t know if it’s valid, and it has some fields. If we just call str() on our form1, it will render out into HTML:

In [19]: str(form1)
Out[19]: '<tr><th><label for="id_your_name">Your name:</label></th><td><input id="id_your_name" maxlength="100" name="your_name" type="text" /></td></tr>'

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

In [21]: form1.as_p()
Out[21]: u'<p><label for="id_your_name">Your name:</label> <input id="id_your_name" maxlength="100" name="your_name" type="text" /></p>'

There is also an as_table() method. This is the default and the same as our str() rendering.

Notice that the <form> tag is missing. Nor is there a <submit> button. That’s up to you.

It gives us an id="id_your_name" attribute. It’s constructed by default as the name of the field with id_ prepended to it. Notice there is no value on the input either.

(14:45) Bound Form

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

In [22]: form2 = NameForm({'your_name': u'Cris Ewing'})

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

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

In [24]: form2.as_p()
Out[24]: u'<p><label for="id_your_name">Your name:</label> <input id="id_your_name" maxlength="100" name="your_name" type="text" value="Cris Ewing" /></p>'

We can also render forms as a list with as_ul().

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 [25]: for field in form2:
   ....:     print field
   ....:     
<input id="id_your_name" maxlength="100" name="your_name" type="text" value="Cris Ewing" />

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 [31]: field.id_for_label
Out[31]: u'id_your_name'

In [32]: field.label
Out[32]: 'Your name'

In [33]: field.value()
Out[33]: u'Cris Ewing'

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

(21:21)

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

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

This allows us access to cleaned_data:

In [36]: form2.cleaned_data
Out[36]: {'your_name': u'Cris Ewing'}

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. Because: form2 also has a data attribute:

In [37]: form2.data
Out[37]: {'your_name': u'Cris Ewing'}

It looks the same in this case, but the difference is that the cleaned_data has gone through validation. What this means is that you can be sure that if someone is trying to be 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, and 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 [38]: form3 = NameForm({'your_name': u'My name is reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally loooooooong'})

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

Notice it is bound, but we don’t know yet if it is valid. When we check the validity:

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

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

This particular form will have an errors attribute now:

In [42]: form3.errors
Out[42]: {'your_name': [u'Ensure this value has at most 100 characters (it has 167).']}

You can use this to send error messages back to your forms.

In [45]: if foo.name in form3.errors:
    print("There's a mistake here: {}".format(form3.errors['your_name']))
   ....:     
There's a mistake here: <ul class="errorlist"><li>Ensure this value has at most 100 characters (it has 167).</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.

(27:44) ModelForm

There’s something else you should know about forms, and that is that this way of building forms is not the only way. Your system is filled up with models. One things you would like to do is present forms that allow you to create and edit those models. Django provides you with a construct called a ModelForm.

What a ModelForm does is it looks at your model, and it looks at the fields on the model, and it creates a form that has all the equivalent form fields that are on your model.

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

In [47]: Photo
Out[47]: imager_images.models.Photo

In [48]: class PhotoForm(forms.ModelForm):
  ....:     class Meta:
  ....:         model = Photo
  ....:         exclude = []
  ....:         

In [49]: PhotoForm
Out[49]: __main__.PhotoForm

In [50]: pf1 = PhotoForm()

In [51]: pf1
Out[51]: <PhotoForm bound=False, valid=Unknown, fields=(user;image;title;description;date_published;published;location)>

This looks similar. Our fields include all of the fields on the Photo model automatically now.

Let’s look at a photo object:

In [53]: Photo.objects.all()
Out[53]: [<Photo: PuPPy>]

In [54]: p1 = _[0]

In [55]: p1
Out[55]: <Photo: PuPPy>

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

In [56]: lovely_form = PhotoForm(instance=p1)

In [57]: lovely_form
Out[57]: <PhotoForm bound=False, valid=Unknown, fields=(user;image;title;description;date_published;published;location)>

In [58]: lovely_form.as_p()
Out[58]: u'<p><label for="id_user">User:</label> <select id="id_user" name="user">\n<option value="">---------</option>\n<option value="2" selected="selected">joel</option>\n</select></p>\n<p><label for="id_image">Image:</label> Currently: <a href="/media/photo_files/2016-01-20/puppy_tshirt_1.jpg">photo_files/2016-01-20/puppy_tshirt_1.jpg</a> <br />Change: <input id="id_image" name="image" type="file" /></p>\n<p><label for="id_title">Title:</label> <input id="id_title" maxlength="256" name="title" type="text" value="PuPPy" /></p>\n<p><label for="id_description">Description:</label> <textarea cols="40" id="id_description" name="description" rows="10">\r\nPuPPy logo</textarea></p>
...'

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

Notice the lovely_form is not bound:

In [60]: lovely_form
Out[60]: <PhotoForm bound=False, valid=False, fields=(user;image;title;description;date_published;published;location)>

Why do we suppose that is? Because we haven’t passed in any data. Just giving it an instance doesn’t bind it 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 [73]: changed_form = PhotoForm({'user': 3}, instance=p1)

In [74]: changed_form
Out[74]: <PhotoForm bound=True, valid=Unknown, fields=(user;image;title;description;date_published;published;location)>

In [75]: str(changed_form)
Out[75]: '<tr><th><label for="id_user">User:</label></th><td><select id="id_user" name="user">\n<option value="">---------</option>\n<option value="2">joel</option>\n<option value="3" selected="selected">bob</option>\n</select></td></tr>\n<tr><th><label for="id_image">Image:</label></th>......

<option value="3" selected="selected">bob</option>

What this means is the data you send in that the form is bound to, will override and take precedence over the data that’s attached to the object that represents the base instance of that form.

Is the form valid?

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

In [79]: changed_form.errors
Out[79]:
{'published': [u'This field is required.'],
 'title': [u'This field is required.']}

So let’s fix our errors:

In [88]: changed_form = PhotoForm({'user': 3, 'title': 'PuPPy Logo', 'published': 'public'}, instance=p1)

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

In [90]: p1
Out[90]: <Photo: PuPPy Logo>

In [91]: p1.user
Out[91]: <User: bob>

Our user 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.

(41:14)

We saw how we passed in an instance of a form and some data. Where does the data come from? In Django you have a request object inside any view you create.

(Cris is at whiteboard here)

request will have 2 attributes on it. These attributes will contain any form data that got sent to you from the browser. request.post, request.get. Using forms you’ll probably want to use post.

Most of this process will be similar to what you have done already on edit views and create views. request.post is a dictionary, and you can pass it in as part of the data that comes to you.

You are going to want to upload images. This might be a bit odd. Those images will be coming to you not in request.post or get. The 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 <form> tag. There’s another attribute to forms called the enctype. In order to upload images, you need to set enctype="multipart/form-data"

(Back to the computer)

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

In [92]: changed_form = PhotoForm({'user': 3, 'title': 'PuPPy Logo', 'published': 'public'}, {'file': <file upload object>}, instance=p1)

Django is responsible for dealing with all of this. All you need to is pass in request.post, request.files, instance=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()

(48:23) Authenticating Users

One last thing. Think about authentication. How will you deal with testing your users and whether or not they will have access to things. You’ve already seen one decorator you can use in a view, or put in your urlconf, and that’s login_required. If you’re using class-based views, you must put the decorator in the urlconf.

Beyond the idea of login_required, there are other ones you can use as well:

permission_required. Django has some default permissions. Every new model you make has permissions associated with it: create, edit, and delete. The user has a has_perm attribute.

This decorator allows you at the view level, you can say they must have a particular permission. So you could say inactive users don’t have permission to change files. Be aware the permissions are table level permissions. You would also need to make sure the user is the owner of the object in question.

Both login_required and permission_required are special instances of a more generalized decorator that’s available called user_passes_test. That takes a function, the function takes the user object, and returns either True or False. When this decorator is applied to a view, if the logged in user does not pass that test, they are sent off to the login page.

So Django gives us with specific instances of the user_passes_test, but you can come up with your own if you need to.