Leonardo Giordani
Posted on March 18, 2020
This post was originally posted on The Digital Cat
In the first two issues of this short series we discussed the basic concepts of class-based views in Django, and started understanding and using two of the basic generic views Django makes available to you: ListView
and DetailView
. Both are views that read some data from the database and show them on a rendered template. We also briefly reviewed the base views that allow us to build heavily customised views, and date-based views.
This third issue will introduce the reader to the class-based version of Django forms. This post is not meant to be a full introduction to the Django form library; rather, I want to show how class-based generic views implement the CUD part of the CRUD operations (Create, Read, Update, Delete), the Read one being implemented by "standard" generic views.
A very basic example
To start working with CBFs (class-based forms) let's consider a simple example. We have a StickyNote
class which represents a simple text note with a date:
class StickyNote(models.Model):
timestamp = models.DateTimeField()
text = models.TextField(blank=True, null=True)
One of the first things we usually want to do is to build a form that allows the user to create a new entry in the database, in this case a new sticky note. We can create a page that allows us to input data for a new StickyNote
simply creating the following view
class NoteAdd(CreateView):
model = StickyNote
It is no surprise that the class is mostly empty. Thanks to inheritance, as happened in the first two posts with standard views, the class contains a bunch of code that lives somewhere in the class hierarchy and works behind the scenes. Our mission is now to uncover that code to figure out how exactly CBFs work and how we can change them to perform what we need.
To make the post easier to follow, please always remember that "class-based form" is a short name for "class-based form view". That is, CBFs are views, so their job is to process incoming HTTP requests and return an HTTP response. Form views do this in a slightly different way than the standard ones, mostly due to the different nature of POST requests compared with GET ones. Let us take a look at this concept before moving on.
HTTP requests: GET and POST
Please note that this is a broad subject and that the present section wants only to be a very quick review of the main concepts that are related to Django CBFs
HTTP requests come in different forms, depending on the method they carry. Those methods are called HTTP verbs and the two most used ones are GET and POST. The GET method tells the server that the client wants to retrieve a resource (the one connected with the relative URL) and shall have no side effects (such as changing the resource). The POST method is used to send some data to the server, the given URL being the resource that shall handle the data.
As you can see, the definition of POST is very broad: the server accepts the incoming data and is allowed to perform any type of action with it, such as creating a new entity, editing or deleting one or more of them, and so on.
Keep in mind that forms are not the same thing as POST request. As a matter of fact, they are connected just incidentally: a form is a way to collect data from a user browsing a HTML page, while POST requests are the way that data is transmitted to the server. You do not need to have a form to make a POST request, you just need some data to send. HTML forms are just a useful way to send POST requests, but not the only one.
Form views
Why are form views different from standard views? The answer can be found looking at the flow of a typical data submission on a Web site:
- The user browses a web page (GET)
- The server answers the GET request with a page containing a form
- The user fills the form and submits it (POST)
- The server receives and processes data
As you can see the procedure involves a double interaction with the server: the first request GETs the page, the second POSTs the data. So you need to build a view that answers the GET request and a view that answers the POST one.
Since most of the time the URL we use to POST data is the same URL we used to GET the page, we need to build a view that accepts both methods. It is time to dig into the class-based forms that Django provides to understand how they deal with this double interaction.
Let us start with the CreateView
class we used in our simple example (CODE. It is an almost empty class that inherits from SingleObjectTemplateResponseMixin
and BaseCreateView
. The first class deals with the template selected to render the response and we can leave it aside for the moment. The second class (CODE), on the other hand, is the one we are interested in now, as it implements two methods which names are self explaining, get
and post
.
Processing GET and POST requests
We already met the get
method in the previous article when we talked about the dispatch
method of the View
class. A quick recap of its purpose: this method is uses to process an incoming HTTP request, and is called when the HTTP method is GET. Unsurprisingly, the post
method is called when the incoming request is a POST one. The two methods are already defined by an ancestor of the BaseCreateView
class, namely ProcessFormView
(CODE), so it is useful to have a look at the source code of this last class:
class ProcessFormView(View):
"""Render a form on GET and processes it on POST."""
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
As you can see the two methods are pretty straightforward, but it's clear that a lot is going on under the hood.
The form workflow
Let's start with get
, which apparently doesn't do much. It just calls render_to_response
passing the result of get_context_data
, so we need to track the latter to see what the template will get. ProcessFormView
or its ancestors don't provide any method called get_context_data
; instead, the BaseCreateView
class receives it from ModelFormMixin
, which in turn receives it from FormMixin
(CODE).
The class hierarchy is pretty complex, but don't be scared, the important part is that the method get_context_data
provided by FormMixin
injects a 'form'
value into the context (CODE), and the form is provided by the get_form
method defined in the same class (CODE), and this eventually uses the form_class
attribute to instantiate the form (CODE). As you can see there are plenty of steps, which means plenty of chances to customise the behaviour, if we should need to provide a personalised solution.
It is interesting to have a even more in-depth look at the form creating mechanism, though, as this is the crucial point of the whole GET/POST difference. Once the method get_form
retrieved the form class, it instantiates it to create the form itself, and the parameters passed to the class are provided by get_form_kwargs
(CODE). When the HTTP method is GET, get_form_kwargs
returns a dictionary with the initial
and prefix
keys, which are taken from the attributes with the same names. I don't want to dig too much into forms now, as they are out of the scope of the post, but if you read the definition of BaseForm
(CODE) you will notice that its __init__
method accepts the same two attributes inital
and prefix
. Pay attention that this is a simplification of the whole process, as the ModelFormMixin
class injects a slightly more complicated version of both got_form_class
and get_form_kwargs
to provide naming conventions related to the Django model in use.
Back to ProcessFormView
, the post
method does not directly render the template since it has to process incoming data before doing that last step. The method, thus, calls get_form
directly and then runs the validation process on it, calling then either form_valid
or form_invalid
, depending on the result of the test. See the official documentation for more information about form validation.
This time, get_form_kwargs
adds two keys to the form when it is instantiated, namely data
and files
. These come directly from the POST
and FILES
attributes of the request, and contain the data the user is sending to the server.
Last, let's have a look at form_valid
and form_invalid
. Both methods are provided by FormMixin
(CODE), but the former is augmented by ModelFormMixin
(CODE). The base version of form_invalid
calls render_to_response
passing the context data initialised with the form itself. This way it is possible to fill the template with the form values and error messages for the wrong ones, while form_valid
, in its base form, just returns an HttpResponseRedirect
to the success_url
. As I said, form_valid
is overridden by ModelFormMixin
, which first saves the form, and then calls the base version of the method.
Let's recap the process until here.
- The URL dispatcher requests a page containing a form with GET.
- The
get
method ofProcessFormView
finds the form class of choice throughget_form_class
- The form class is instantiated by
get_form
with the values contained in theself.initial
dictionary - At this point a template is rendered with a context returned by
get_context_data
as usual. The context contains the form. - When the use submits the form the URL dispatcher requests the page with a POST that contains the data
- The
post
method ofProcessFormView
validates the form and acts accordingly, rendering the page again if the data is invalid or processing it and rendering a success template with the newly created object.
Update and Delete operations
This rather rich code tour unveiled the inner mechanism of the CreateView
class, which can be used to create a new object in the database. The UpdateView
and DeleteView
classes follow a similar path, with minor changes to perform the different action they are implementing.
UpdateView
wants to show the form already filled with values, so it instantiates an object before processing the request (CODE). This makes the object available in the keywords dictionary under the instance
key (CODE), which is used by model forms to initialize the data (CODE). The save
method of BaseModelForm
is smart enough to understand if the object has been created or just changed (CODE so the post
method of UpdateView
works just like the one of CreateView
.
DeleteView
is a bit different from CreateView
and UpdateView
. As the official documentation states, if called with a GET method it shows a confirmation page that POSTs to the same URL. So, as for the GET requests, DeleteView
just uses the get
method defined by its ancestor BaseDetailView
(CODE), which renders the template putting the object in the context. When called with a POST request, the view uses the post
method defined by DeletionMixin
(CODE, which just calls the delete
method of the same class (CODE). This performs the deletion on the database and redirects to the success URL.
Final words
As you can see, the structure behind the current implementation of Django class-based form views is rather complex. This allows the user to achieve complex behaviours like the CUD operations just by defining a couple of classes as I did in the simple example at the beginning of the post. Most of the time, however, such a simplification makes it difficult for the programmer to understand how to achieve the desired changes to the class behaviour. So, the purpose of this big tour I made inside the Django source code was to give an insight of what methods are called in the life cycle of your HTTP request so that you can better identify what methods you need to override.
When performing special actions that fall outside the standard CUD operations you better inherit from FormView
(CODE). The first thing to do is to check if and how you need to customize the get
and post
methods; remember that you either need to implement the full behaviour of those methods or make you changes and call the parent implementation. If this is not enough for your application consider overriding one of the more dedicated methods, such as get_form_kwargs
or form_valid
.
Read more posts like this on The Digital Cat
Posted on March 18, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.