Digging up Django class-based views - 3

lgiordani

Leonardo Giordani

Posted on March 18, 2020

Digging up Django class-based views - 3

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. The user browses a web page (GET)
  2. The server answers the GET request with a page containing a form
  3. The user fills the form and submits it (POST)
  4. 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)
Enter fullscreen mode Exit fullscreen mode

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.

  1. The URL dispatcher requests a page containing a form with GET.
  2. The get method of ProcessFormView finds the form class of choice through get_form_class
  3. The form class is instantiated by get_form with the values contained in the self.initial dictionary
  4. At this point a template is rendered with a context returned by get_context_data as usual. The context contains the form.
  5. When the use submits the form the URL dispatcher requests the page with a POST that contains the data
  6. The post method of ProcessFormView 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

💖 💪 🙅 🚩
lgiordani
Leonardo Giordani

Posted on March 18, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related