Class-based views with models

Back in chapter 2, you learned how class-based views allow you to create views that operate with object oriented programming (OOP) principles (e.g. encapsulation, polymorphism and inheritance) leading to greater re-usability and shorter implementation times. Now that you know how Django models work, we can address class-based views that integrate with models described in the last part of table 2-10.

Unlike standard Django views -- explored in the early sections of chapter 2 -- which allow open ended logic to process a request and generate a response, class-based views with models encapsulate the logic performed against Django models in a more modular way.

For example, the logical patterns to create, read, update and delete model instances in a standard view method, generally follow a very consistent workflow: get input data from a url or form, execute CRUD operation on the model and send the response to a template.

In the spirit of Django's DRY (Don't repeat yourself) principle, class-based views with models offer a way to cut down on the boilerplate code used in standard view methods and use class fields and methods to define the workflow used for Django model CRUD operations.

Create model records with the class-based view CreateView

As you've learned up to this point, the creation of Django model instances in real-life projects comes accompanied by a series of constructs that can include: model forms, GET/POST request processing and the use of templates, among other things.

The Django CreateView class-based view is specifically designed to cut-down on the amount of boilerplate code needed to perform the creation of a model record. Listing 9-9 illustrates a class-based view that uses the CreateView class.

Listing 9-9 Django class-based view with CreateView to create model records

# views.py
from django.views.generic.edit import CreateView
from .models import Item, ItemForm
from django.core.urlresolvers import reverse_lazy

class ItemCreation(CreateView):
    model = Item
    form_class = ItemForm
    success_url = reverse_lazy('items:index')

# models.py
from django import forms
from django.db import models

class Menu(models.Model):
    name = models.CharField(max_length=30)

class Item(models.Model):
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)

class ItemForm(forms.ModelForm): 
    class Meta: 
        model = Item
        fields = '__all__'
        widgets = {
            'description': forms.Textarea(),
        }

# urls.py 
from django.conf.urls import url
from coffeehouse.items import views as items_views

urlpatterns = [
    url(r'^new/$', items_views.ItemCreation.as_view(), name='new'),
]

# templates/items/item_form.html 
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-primary">Create</button>
    </form>

The first part in listing 9-9 illustrates the ItemCreation class-based view that inherits its behavior from the CreateView class. Notice this view lacks a request reference, processing logic or return statement, all of which were common in the standard view methods described in chapter 2. So what is the ItemCreation class-based view actually doing ?

Because you know beforehand you want to create a model record, the CreateView class -- used by the ItemCreation class-based view -- supports all the necessary boilerplate logic and requires a minimum set of code to fulfill its model record creation logic.

The ItemCreation class-based view in listing 9-9 uses the model field to tell Django to create Item model records. In addition, the form_class field specifies the ItemForm form -- also declared in isting 9-9 -- which is used to create model records. In addition, the success_url field is used to return control to the item:index url when model record creation is successful.

Why use reverse_lazy in class-based views, instead of reverse ?

Due to the simultaneous import order of models, views and urls in class-based views, using the standard reverse() method to resolve url names can result in the error: django.core.exceptions.ImproperlyConfigured: The included URLconf '------' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.

The reverse_lazy method ensures any reverse url name resolution is attempted only after all models, views and urls have been properly imported, therefore it's the common choice in the context of class-based views.

You may still be left wondering, where are the save() and is_valid() methods for the ItemCreation class-based view in listing 9-9 if its creating model records ? By default there aren't any. Because you just want to create a model record, the parent CreateView class takes care of this supporting logic.

The next part in listing 9-9 contains the urls.py file with the hook to set up the class-based view into the application. In this case, the ItemCreation class-based view is set to run on the new/ url and is declared as part of the url() method by using the class-based view as_view() method -- note this url set up technique is identical for all class-based views and was described toward the end of chapter 2 for class-based views without models.

So what happens when a user visits the new/ url ? Control is sent to the ItemCreation class-based view. And because this view's purpose is to create an Item model record, the class-based view looks for a template to render the form, under the TEMPLATES directory path of a project following the convention <app_name>/<model_name>_form.html.

In the last part of listing 9-9, you can see the template templates/items/item_form.html, where templates represents a TEMPLATES directory path value, items the app name and item the model name defined for the class-based view. In addition, notice the contents of the item_form.html template use a standard form layout, which you can adjust like any Django form.

So where is the POST form handler and error handling in listing 9-9 ? By default, there isn't any either. Once a user gets an unbound form illustrated at the bottom of listing 9-9, the form processing and validation is taken care of behind the scenes by the ItemCreation class-based view. If the form contains errors, the template is re-rendered -- like any other form -- using the same template in listing 9-9. If the form data is valid, form processing is deemed successful and the class-based view creates an Item model record -- like a model form -- redirecting control to the item:index url defined in the class-based view success_url field.

As you can see in this example, a class-based view that inherits its behavior from the CreateView class, cuts-down the boilerplate code needed to a create model record.

CreateView fields and methods

While at first glance a class-based view that inherits its behavior from the CreateView class can appear to be inflexible, it's possible to override its default behaviors like any Django construct.

As it turns out, the CreateView class inherits its behavior from many other Django class-based views, which is what gives the CreateView class -- and its implementing child classes like the class-based view in listing 9-9 -- its behind-the-scene powers. The CreateView class inherits its behavior from the following class-based view classes:

django.views.generic.detail.SingleObjectTemplateResponseMixin
django.views.generic.base.TemplateResponseMixin
django.views.generic.edit.BaseCreateView
django.views.generic.edit.ModelFormMixin
django.views.generic.edit.FormMixin
django.views.generic.detail.SingleObjectMixin
django.views.generic.edit.ProcessFormView
django.views.generic.base.View

So why are these classes even important ? Because they provide the default behavior for all CreateView class-based views. The scant fields declared in the ItemCreation class-based view in listing 9-9 are only three fields for CreateView class-based views. It's possible to declare over a dozen more fields and methods -- that belong to this past list of classes -- to provide behaviors, such as: using another template other than <app_name>/<model_name>_form.html; specifying the content type for the response (e.g. text/csv); custom methods to run when a model form is valid or invalid; as well as declaring custom methods to manually execute GET and POST workflow logic.

Note A CreateView class-based view inherits many fields and methods from its parent classes. The following options are the most common ones, for an exhaustive list consult each of the CreateView parent classes.

Basic CreateView options: model, form_class and success_url fields

As you've already seen in listing 9-9, the essential logic fulfilled by a CreateView class-based view is to create a model record using a form, which in turn requires a basic set of parameters. First, the model field is basic to the whole operation, because a CreateView class-based view must know beforehand which type of model record to create. Second, the form_class is also a basic parameter, because a user must be presented with a form to capture the data to create the model record.

Finally, because successfully creating a model record entails notifying a user about the action and moving away from the form page, the success_url field is also a basic part of a CreateView class-based view to indicate where to redirect a user after a model record is created.

Customize template name, MIME type and context : template_name and content_type fields & get_context_data() method

Sometimes relying on the CreateView class-based view template naming convention <app_name>/<model_name>_form.html is unfeasible, either because you have a pre-existing template to reuse or simply because you don't like the default convention. You can declare the template_name field as part of a CreateView class-based view to override this convention. Similarly, it's also possible to override the default MIME type used by a class-based view response -- if the template contains something other than text/html (e.g. text/csv) -- using the content_type field as part of a CreateView class-based view.

In addition, you can alter the context data passed to a class-based view template by defining an implementation of the get_context_data() method. This last process is common when you need to pass additional data to a template -- besides the form reference -- or change the actual form reference to another name.

Listing 9-10 illustrates a CreateView class-based view that makes use of the template_name and content_type fields, as well as the get_context_data() method.

Listing 9-10 Django class-based view with CreateView with template_name, content_type and get_context_data()

# views.py
from django.views.generic.edit import CreateView
from .models import Item, ItemForm, Menu

class ItemCreation(CreateView):
    template_name = "items/item_form.html"
    context_type = "text/html"
    model = Item
    form_class = ItemForm
    success_url = reverse_lazy('items:index')
    def get_context_data(self,**kwargs):
        kwargs['special_context_variable'] = 'My special context variable!!!'
        context = super(ItemCreation, self).get_context_data(**kwargs)
        return context        

You can see in listing 9-10 the template_name and content_type fields are declared as part of the class-based view. In this particular case, both fields values are assigned their default values -- making them redundant -- for simplicity, but you can adjust accordingly to your needs.

The get_context_data() method in listing 9-10 first adds the custom special_context_variable key to make it available to the class-based view template (i.e. items/item_form.html). Next, a call is made to the parent class's get_context_data() method (i.e. CreateView) to run its context set up logic, which consists of setting up the form reference which is also used in the template. Finally, the context reference with all the template context values is returned by the get_context_data() method for use inside the template.

As you can see in listing 9-10, by simply adding fields and overriding methods in a CreateView class-based view, you can easily start changing its default behavior.

Customize form initialization and validation: initial field, get_initial(), get_form(), form_valid() and form_invalid() methods

The initial field on a CreateView class-based view works just like a standard form's initial argument to specify default values for an unbound form. For example, declaring the field initial = {'size':'L'} on a CreateView class-based view sets its form's size field to L.

Form initialization can sometimes require more complex requirements than a single line statement, in which a CreateView class-based view also offers the get_initial() method. The get_initial() method functions like the __init__ method used in standard forms -- in that you can introduce open-ended logic to set up default values -- but is intended solely to set up initial values and to return a dictionary of values -- unlike the __init__ method where you can introduce other actions besides default form values (e.g. change widgets, add validation).

Listing 9-11 illustrates the use of the initial argument and get_initial() method on a CreateView class-based view.

Listing 9-11 Django class-based view with CreateView with initial and get_initial()

# views.py
from django.views.generic.edit import CreateView
from .models import Item, ItemForm, Menu

class ItemCreation(CreateView):
    initial = {'size':'L'}
    model = Item
    form_class = ItemForm
    success_url = reverse_lazy('items:index')
    def get_initial(self):
        initial_base = super(ItemCreation, self).get_initial()
        initial_base['menu'] = Menu.objects.get(id=1)
        return initial_base

The first step in listing 9-11 set the initial field to set the class-based view's form size field to L. Next, the get_initial() method is declared to add another default value to a class-based view form.

Inside the the get_initial() method, the first step is to call the parent class's get_initial() method (i.e. CreateView) to run its initial set up logic, this ensures the class's initial field value (i.e.{'size':'L'}) is taken into account to as part of the initial value. Next, the initial_base reference is updated to set the form's menu field to the Menu record with id=1. Finally, the get_initial() method in listing 9-11 returns a dictionary containing both the initial field value set by the class-based view and the custom form value set in its body.

The get_form() method of a CreateView class-based view is designed to tap into the full initialization process of a form and not just on setting its default values like the initial field and get_initial() method. This makes get_form() method suited to perform broader form initialization tasks like setting form widgets and validation initialization, like its done in the __init__ method in standard forms. Inclusively, it's possible to use the get_form() method to specify default form values and forgo the use of initial and get_initial() altogether. Listing 9-12 illustrates the use of the get_form() method in a CreateView class-based view.

Listing 9-12 Django class-based view with CreateView with get_form()

# views.py
from django.views.generic.edit import CreateView
from .models import Item, ItemForm, Menu

class ItemCreation(CreateView):
    initial = {'size':'L'}
    model = Item
    form_class = ItemForm
    success_url = reverse_lazy('items:index')
    def get_form(self):
        form = super(ItemCreation, self).get_form()
        initial_base = self.get_initial() 
        initial_base['menu'] = Menu.objects.get(id=1)
        form.initial = initial_base
        form.fields['name'].widget = forms.widgets.Textarea()
        return form

The first step in the get_form() method in listing 9-12 is to call the parent class's get_form() method (i.e. CreateView) to get the base form. Next, a call is made to the class-based view's get_initial() method to get its initial form value, as well as set up a default value for the form's menu field using a model query. In this case, the form initialization form dictionary has the same values as those in listing 9-11.

Next, the form initialization dictionary is assigned to the base form using the standard initial form reference. Finally, before returning an instance of the form class, a custom widget is set on the form's name field, overriding the default widget of the form name field.

In addition to initialization tasks, the form validation process for a CreateView class-based view can also be customized. The form_valid() and form_invalid() methods are used to access the points at which a class-based view form is deemed successful or erroneous, respectively. Listing 9-13 illustrates an example that uses the form_valid() and form_invalid() methods in a CreateView class-based view.

Listing 9-13 Django class-based view with CreateView with form_valid() and form_invalid()

# views.py
from django.views.generic.edit import CreateView
from django.http import HttpResponseRedirect
from django.contrib import messages
from .models import Item, ItemForm, Menu

class ItemCreation(CreateView):
    initial = {'size':'L'}
    model = Item
    form_class = ItemForm
    success_url = reverse_lazy('items:index')
    def form_valid(self,form):
        super(ItemCreation,self).form_valid(form)
        # Add action to valid form phase
        messages.success(self.request, 'Item created successfully!')        
        return HttpResponseRedirect(self.get_success_url())        
    def form_invalid(self,form):
        # Add action to invalid form phase
        return self.render_to_response(self.get_context_data(form=form))

As you can see in listing 9-13, the form_valid() method gains access to the form instance via its form input argument, which in turn allows you to perform actions on the form when it passes its validation phase. In this case, no action is taken on the form itself to simplify things, but an additional piece of logic is added to illustrate the customization process.

The first step in the form_valid() in listing 9-13 is a call to the parent class's form_valid() method (i.e. CreateView) to run its form validation set up logic, this ensures the base class's form validation is run first to verify any form rule violations (e.g. add errors to the form). If a call to the parent's form_valid() method detects a rule violation, then the class's form_valid() method (i.e. the one listing 9-13) short-circuits and falls back to the form_invalid() method. If the parent's form_valid() method passes, the logic In listing 9-13 continues to add a Django message framework success message to present to a user.

Tip It's also possible to add Django message framework success messages to a class-based view form validation process through a mixin. Mixins for class-based views are described in the last section of this chapter.

Finally, because you're handling a valid form workflow, the form_valid() method in listing 9-13 must explicitly redirect a user to a location to finish its work. In this case, a standard Django HttpResponseRedirect method is used with the class-based view's get_success_url() method, the last of which gets the class-based view success_url field value.

The form_invalid() method in listing 9-13 does nothing in particular to handle an invalid form. It simply does the minimum amount work -- by means of class-based view methods -- which is to return control to the same template location and add the context that contains the form with errors to present to a user.

As you can see, the benefit of the form_valid() and form_invalid() methods in a CreateView class-based view is they allow you to customize the form validation workflow, without the need to modify other parts of a CreateView workflow (e.g. saving the form data to the database).

Customize view method workflow: get() and post() methods

Beside the previous customization options for a CreateView class-based view, it's also possible to get absolute control over the workflow done by a class-based view with either the get() or post() methods. The get() method is used to tap into the HTTP GET workflow associated with a class-based view, where as the post() method is used to tap into the HTTP POST workflow associated with a class-based view.

Although the use of the get() or post() methods can offer some of the greatest flexibility to a class-based view, they also require to explicitly declare the initialization, validation and redirect sequences of a class-based view. This means class-based views that use either the get() or post() methods, can resemble more the standard open-ended Django views -- described in Chapter 2 -- than the succinct class-based view presented earlier in listing 9-9.

Still, sometimes the appeal of class-based views is so great, the use of the get() or post() methods is warranted. Listing 9-14 illustrates an example that uses the get() and post() methods in a CreateView class-based view.

Listing 9-14 Django class-based view with CreateView with get() and post()

# views.py
from django.views.generic.edit import CreateView
from django.shortcuts import render
from django.contrib import messages

class ItemCreation(CreateView):
    initial = {'size':'L'}
    model = Item
    form_class = ItemForm
    success_url = reverse_lazy('items:index')
    template_name = "items/item_form.html"
    def get(self,request, *args, **kwargs):
        form = super(ItemCreation, self).get_form()
        # Set initial values and custom widget
        initial_base = self.get_initial() 
        initial_base['menu'] = Menu.objects.get(id=1)
        form.initial = initial_base
        form.fields['name'].widget = forms.widgets.Textarea()
        # return response using standard render() method
        return render(request,self.template_name,
                      {'form':form,
                       'special_context_variable':'My special context variable!!!'})
 
    def post(self,request,*args, **kwargs):
        form = self.get_form()
        # Verify form is valid
        if form.is_valid():
            # Call parent form_valid to create model record object
            super(ItemCreation,self).form_valid(form)
            # Add custom success message
            messages.success(request, 'Item created successfully!')    
            # Redirect to success page    
            return HttpResponseRedirect(self.get_success_url())
        # Form is invalid
        # Set object to None, since class-based view expects model record object
        self.object = None
        # Return class-based view form_invalid to generate form with errors
        return self.form_invalid(form)

You can see in listing 9-14, both the get() and post() methods have access to a request input -- an HttpRequest instance -- like standard view methods. This request reference allows class-based views access to request data, such as HTTP meta data (e.g. user's IP address), which is a topic described in detail in Chapter 2.

The first step in the get() method in listing 9-14 is to create an unbound form using the parent class's get_form() method (i.e. CreateView). Because the get() method gives you full control over the workflow, it would be equally valid to create an unbound form using standard form syntax (e.g. form = ItemForm()), but since it's a class-based view, the example leverages a class-based view construct. Once the unbound form is created, a series of initial values and a custom widget is set on the form, just like it's done in the previous class-based view examples.

Once the unbound form is ready, notice the return statement of the get() method uses the standard render() method used in regular view methods. In this case, the render() method redirects control to the class-based view template and sets the template context with the unbound form and an additional special_context_variable variable to use in the template.

Next, the post() method in listing 9-14 is tasked with processing the form with user data. The first step in the post() method is to get a bound form instance using the class-based view get_form() class. Similarly to the get() method, it would be equally valid to create a bound form using standard form syntax (e.g. form = ItemForm(request.POST)).

With a bound form instance, a check is made to verify if the user provided form data is valid, just like it's done with standard forms (e.g. form.is_valid()). If the form data is valid, a call is made to the parent class's is_valid() method (i.e. CreateView), which ensures the core logic of a class-based view is executed when a form is valid (e.g. saving the form data to a database). Here it's possible once again to use any standard model construct, but calling the parent class's is_valid() method is easier to execute this routine logic to create a model object record. Once the routine validation logic is complete, a success message is added to present to an end user and a redirect is made to the class-based view's success url. If the form data is invalid, the class-based view's object field is set to None -- since class-based views expect to handle an object record instance in POST processing -- and control is returned using the class-based form_invalid() method which takes care of the underlying details vs. using the render() method to create a standard view method response.

As you can now understand from this example in listing 9-14, a CreateView class-based view can be just as flexible as a standard view method. It's simply a matter of knowing and understanding the different fields and methods supported by a CreateView class-based view. Of course, if you feel the custom logic for a CreateView class-based view becomes to unwieldy, you can always fall back to a standard view method presented back in Chapter 2.

Read model records with the class-based views ListView and DetailView

Similar to the process of creating model records, the process to read model records also follows a near identical process for all models: create a query to get model record(s) and then use a template to display the model record(s). The Django ListView and DetailView class-based views are specifically designed to cut-down on the amount of boilerplate code needed to display a list of Django model records and a single Django model record, respectively.

The ListView class-based view can quickly set up a query for a list of model records and display them in a template. Listing 9-15 illustrates a class-based view that uses the ListView class-based view.

Listing 9-15 Django class-based view with ListView to read list of records

# views.py
from django.views.generic.list import ListView
from .models import Item

class ItemList(ListView):
    model = Item

# urls.py 
from django.conf.urls import url
from coffeehouse.items import views as items_views

urlpatterns = [
    url(r'^$',items_views.ItemList.as_view(),name="index"),
]

# templates/items/item_list.html 
  {% regroup object_list by menu as item_menu_list %}
  {% for menu_section in item_menu_list %}
   <li>{{ menu_section.grouper }}
    <ul>
        {% for item in menu_section.list %}
        <li>{{item.name|title}}</li>    
        {% endfor %}
    </ul>
    </li>
{% endfor %}

The first definition in listing 9-15 is the ItemList class which inherits its behavior from the ListView class-based view class. The model field in the ItemList class set to Item, tells Django to generate a list of all Item model records (e.g. Item.objects.all())

Next, the ListView class-based view is hooked up to a root url regular exression -- r'^$' -- using the as_view() method, the last of which is available on all class-based views and was also used in the past section to set up a a CreateView class-based view.

Finally, the last part in listing 9-15 illustrates the template item_list.html which generates a loop over the object_list reference, the last of which is the default context variable used by a ListView class-based view that contains the model record list.

Recapping, the most important default behaviors of a ListView class-based view are:

As you can see in this example, a ListView class-based view cuts-down the boilerplate code needed to present a list of model records to one field.

The DetailView class-based view is another record reading construct, designed to quickly set up a single record query and present the results in a template. Listing 9-16 illustrates a class-based view that uses the DetailView class.

Listing 9-16 Django class-based view with DetailView to read model record

# views.py
from django.views.generic. import DetailView
from .models import Item

class ItemDetail(DetailView):
    model = Item

# urls.py 
from django.conf.urls import url
from coffeehouse.items import views as items_views

urlpatterns = [
    url(r'^(?P<pk>\d+)/$',items_views.ItemDetail.as_view(),name="detail"),
]

# templates/items/item_detail.html 
<h4> {{item.name|title}}</h4>
<p>{{item.description}}</p>
<p>${{item.price}}</p>
<p>For {{item.get_size_display}} size: Only {{item.calories}} calories
{% if item.drink %}
and {{item.drink.caffeine}} mg of caffeine.</p>
{% endif %}
</p>

The first definition in listing 9-16 is the ItemDetail class which inherits its behavior from the DetailView class-based view class. The model field in the ItemDetail class is set to Item, which tells Django to get an Item model record. Unlike the ListView class-based view class in listing 9-15 that reads all model records, a DetailView class-based view class must always limit its model query to a single record, which is where the class-based view url definition comes into play.

The DetailView class-based view in listing 9-16 is hooked up to a root url regular exression -- r'^$' -- using the as_view() method -- just like other class-based views -- but notice the url definition contains the (?P<pk>\d+) url parameter, which gets passed to the class-based view to delimit the model query to a single record.

For example, if a request is made on the url /items/1/, the 1 is assigned to the pk parameter, which is passed to the classed-based view, to build the model query Item.objects.get(pk=1) that gets the Item model record with pk=1 -- note the pk field stands for primary key, which is generally equivalent to the id field.

In this manner, as the url requests made on the DetailView class-based view change (e.g. /items/2/, /items/3/), so does the backing query made for a model a record, and with it the record returned to be displayed in the template.

Finally, the last part in listing 9-16 illustrates the template item_detail.html which outputs various fields of the item reference that represents the model record. In this case, the item reference is used because the default context variable used by a DetailView class-based view for a model record is the name of the model itself.

Recapping, the most important default behaviors of a DetailView class-based view are:

As you can see in this example, a class-based view that inherits its behavior from the DetailView class, cuts-down the boilerplate code needed to present a single model record.

ListView fields and methods

Similar to the CreateView class-based view presented earlier, it's possible to override many of default behaviors of a ListView class-based view.

As it turns out, the ListView class-based view inherits its behavior from many other Django class-based views, presented in the following list:

django.views.generic.list.MultipleObjectTemplateResponseMixin
django.views.generic.base.TemplateResponseMixin
django.views.generic.list.BaseListView
django.views.generic.list.MultipleObjectMixin
django.views.generic.base.View
Note A ListView class-based view inherits many fields and methods from its parent classes. The following options are the most common ones, for an exhaustive list consult each of the ListView parent classes.

Basic ListView option: model field

As you saw in listing 9-15, the essential logic fulfilled by a ListView class-based view is to create a list of records from a model. Therefore the model field option is essential to indicate on which model to create a record list.

The next sections describe how to customize a ListView class-based view, including how to delimit and generate multiple pages for a list of records, as well as how to override other default behaviors.

Customize template context reference name: context_object_name

In listing 9-15, you can see the template of the ListView class-based view uses the rather unfriendly context variable named object_list. This is the default behavior, but to help template editors, it's possible to use a different context variable to contain the list of records.

The context_object_name field is used to define a custom context variable name to manipulate the list of records inside a template (e.g. context_object_name = 'item_list').

Tip A ListView class-based view also inherits its behavior from many of the same classes as the CreateView class described earlier. Therefore, you can also use: the template_name field to specify a custom template name; the content_type field to specify a MIME type and the get_context_data() method to alter the context used by a template.

Customize record list: queryset and ordering fields & pagination behavior

By default, a ListView class-based view generates a list for all records that belong to a model. While this behavior is reasonable, it can also be necessary to create a ListView class-based view that returns a more limited criteria, in which case it's necessary to delimit the backing model query. The queryset field is used to define a custom query to generate a list of records.

Another helpful option to customize a record list is to specify the field order in which to generate the record list. Similar to the standard order_by() method used in model queries, a ListView class-based view can specify the ordering field to define the sort order of a record list.

Listing 9-17 illustrates the use of the queryset and ordering fields in a ListView class-based view.

Listing 9-17 Django class-based view with ListView to reduce record list with queryset

# views.py
from django.views.generic.list import ListView
from .models import Item

class ItemList(ListView):
    model = Item
    queryset = Item.objects.filter(menu__id=1)
    ordering = ['name']

As you can see in listing 9-17, the queryset field is assigned a standard model query to produce Item records with a menu id=1. In this manner, the resulting record list passed by ListView the class-based view to the template only contains Item records that match this criteria. In addition, listing 9-17 also makes use of the ordering = ['name'] field, which in this case ensures the query to generate the record list is sorted by the Item model's name field.

Although the queryset option is helpful to delimit the size of the record list presented by a ListView class-based view, sometimes it's necessary to deal with a large record list and still be able to delimit the amount of results displayed in a template. For such scenarios, you can use pagination to split out a large record list over multiple pages.

Since pagination by definition depends on the use of multiple pages, it forces you to adjust not only a ListView class-based view definition, but also the url structure and template used by the class-based view to support multiple pages. Listing 9-18 illustrates a pagination example for a a ListView class-based view, which is based on the example from listing 9-15.

Listing 9-18 Django class-based view with ListView to read list of records with pagination

# views.py
from django.views.generic.list import ListView
from .models import Item

class ItemList(ListView):
    model = Item
    paginate_by = 5

# urls.py 
from django.conf.urls import url
from coffeehouse.items import views as items_views

urlpatterns = [
    url(r'^$',items_views.ItemList.as_view(),name="index"),
    url(r'^page/(?P<page>\d+)/$',items_views.ItemList.as_view(),name="page"),
]

# templates/items/item_list.html 
  {% regroup object_list by menu as item_menu_list %}
  {% for menu_section in item_menu_list %}
   <li>{{ menu_section.grouper }}
    <ul>
        {% for item in menu_section.list %}
        <li>{{item.name|title}}</li>    
        {% endfor %}
    </ul>
    </li>
  {% endfor %}
 {% if is_paginated %}
    {{page_obj}}
 {% endif %}

The relevant pagination logic in listing 9-18 is in bold. First off, the paginate_by = 5 is added to the class-based view definition, which tells Django to limit the record list to five records per page. Since the ListView class-based view will require a page number to display records beyond the first five records of query -- since pages are limited 5 -- the natural place to obtain a page number is through a url (e.g. /page/1/ to create a list with the first five records of a query, /page/2/ to create a list with the records six through ten, etc).

Next in listing 9-18, you can see a new url definition with the (?P<page>\d+) parameter hooked up the ListView class-based view. This url definition allows a request that matches the regular expression pattern (e.g. /page/1/,/page/2/) to pass the url page parameter value to the class-based view. When a ListView class-based view detects a url page parameter value with the presence of the paginate_by option, it adjusts the query for the record list to return the appropriate set of records to a template based on the page (e.g. /page/1/ generates a list of records from one to five of the overall query, /page/2/ generates a list of records from six to ten of the overall query, etc).

Finally, the last section in listing 9-18 represents the backing template for a ListView class-based view that uses pagination. The {% is_paginated %} tag and {{page_obj}} context reference are used let users know on which page of the overall record list they're on.

Tip A ListView class-based view can also use the get() class-based view method to get full control over the view workflow. See the previous section on the CreateView class-based view which describes how to use it and its implications.

DetailView fields and methods

Like other class-based views, a DetailView class-based view can also be created with custom fields and methods to override their default behaviors.

As it turns out, the DetailView class inherits its behavior from many other Django class-based views, which are described in the following list:

django.views.generic.detail.SingleObjectTemplateResponseMixin
django.views.generic.base.TemplateResponseMixin
django.views.generic.detail.BaseDetailView
django.views.generic.detail.SingleObjectMixin
django.views.generic.base.View
Note A DetailView class-based view inherits many fields and methods from its parent classes. The following options are the most common ones, for an exhaustive list consult each of the DetailView parent classes.

Basic DetailView options: model field and url with pk parameter

As you saw in listing 9-16, the essential logic fulfilled by a DetailView class-based view is to get a model record and display it in a template. Therefore the model field is one of the essential pieces to this type of class-based view and must always by provided.

In order to get a specific model record, a DetailView class-based view must also define a url parameter which helps it select a model record. By default, this url parameter must be named pk and its value is used to perform a query on the model value using the pk field, which is generally equivalent to a model's id field.

Tip A DetailView class-based view also inherits its behavior from many of the class-based view classes described earlier. Therefore, you can also use: the template_name field to specify a custom template name; the content_type field to specify a MIME type; the get_context_data() method to alter the context used by a template; as well as the context_object_name field to declare a different context variable to access a record in a template.

Customize url and query parameters: pk_url_kwarg, slug_field & slug_url_kwarg

By default, a DetailView class-based view expects a url argument named pk to determine on which model record to make a query by its primary key. However, if you already have a pre-existing url or simply don't like this inexpressive url parameter name, you can assign a custom url parameter with the pk_url_kwarg field.

For example, setting pk_url_kwarg='item_id' on a DetailView class-based view, allows a url to be defined as url(r'^(?P<item_id>\d+)/$',items_views.ItemDetail.as_view()) vs. the default url(r'^(?P<pk>\d+)/$',items_views.ItemDetail.as_view()).

Even though performing the record query of a DetailView class-based view with the pk field is a common practice -- given the pk field is generally an integer field with a database index to increase lookup performance -- sometimes in can be necessary to perform the record query of a DetailView class-based with another model field (e.g. to create more user/SEO-friendly urls like /item/capuccino/, instead of /item/1/).

Listing 9-19 illustrates a DetailView class-based view that uses the slug_field option to allow a model record query to be made against a model field other than pk.

Listing 9-19 Django class-based view with DetailView and slug_field option

# views.py
from django.views.generic import DetailView
from .models import Item

class ItemDetail(DetailView):
    model = Item
    slug_field = 'name__iexact'

# urls.py 
from django.conf.urls import url
from coffeehouse.items import views as items_views
urlpatterns = [
    url(r'^(?P<slug>\w+)/$',items_views.ItemDetail.as_view(),name="detail"),
]

# Template identical to listing to template in listing 9-16

The first important aspect in listing 9-19 is the url definition has a url parameter named slug that matches any word pattern due to the w+ regular expression. This means urls such as /items/espresso/ and /items/latte/ match this url, unlike the prior DetailView class-based url definition that uses the pk parameter to match numeric values with d+ regular expression.

When a DetailView class-based view receives a url parameter named slug, it signals the class-based view that the model record query should done on a model field other thank pk. Because this slug value can be ambiguous (e.g. it can represent one of several model field values, such as name, cost or description), it's necessary to qualify which model field the slug value represents, which is the purpose of slug_field field.

In the case of listing 9-19, the slug_field field is set to name__iexact, which tells the DetailView class-based view to perform a case-insensitive model query with the slug value on the Item model name field. The case insensitive query is provided by the __iexact lookup and is important in case the database record values are mixed-case (e.g. the url /items/capuccino/ would match an Item name record capuccino, Capuccino or CAPUCCINO. Without case inventivenesses, the Item name record would need to be exactly capuccino to match the url).

Although not presented in the sample in listing 9-19, another option available for a DetailView class-based view is the slug_url_kwarg option, which has a similar to the pk_url_kwarg option described earlier.

By default, a DetailView class-based view that uses a slug query (i.e. non pk query) expects a url argument named slug to determine on which model field to make a query to obtain a record. However, if you already have a pre-existing url or simply don't like this inexpressive url parameter name, you can assign a custom url parameter with the slug_url_kwarg field.

For example, setting slug_url_kwarg='item_name' on a DetailView class-based view, allows a url to be defined as url(r'^(?P<item_name>\w+)/$',items_views.ItemDetail.as_view()) vs. the default url(r'^(?P<slug>\w+)/$',items_views.ItemDetail.as_view()).

Tip A DetailView class-based view can also use the get() class-based view method to get full control over the view workflow. See the previous section on the CreateView class-based view which describes how to use it and its implications.

Update model records with the class-based view UpateView

When you update a Django model record, the typical process involves: fetching the model record you want to update from a database, presenting the model data in a form so a user can make changes and finally saving the changes back to the database. The Django UpdateView class-based view is specifically designed to cut-down on the amount of boilerplate code needed to update a Django model record.

Due to its functionality, the UpdateView class-based view operates like a combination of the CreateView -- which creates a model record through a form -- and the DetailView -- which displays a model record. Listing 9-20 illustrates an example of an UpdateView class-based view.

Listing 9-20 Django class-based view with UpdateView to edit a record

# views.py
from django.views.generic import UpdateView
from .models import Item

class ItemUpdate(UpdateView):
    model = Item
    form_class = ItemForm
    success_url = reverse_lazy('items:index')

# urls.py 
from django.conf.urls import url
from coffeehouse.items import views as items_views

urlpatterns = [
     url(r'^edit/(?P<pk>\d+)/$', items_views.ItemUpdate.as_view(), name='edit'),
]

# templates/items/item_form.html 
<form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-primary">
          {% if object == None%}Create{% else %}Update{% endif %}
        </button>
</form>

The first definition in listing 9-20 is the ItemUpdate class which inherits its behavior from the UpdateView class-based view class. The model field in the ItemUpate class sets a value to Item, which tells Django to update Item model records. In addition, because you're dealing with an update operation, the Item model record must be presented in a form -- which is the purpose of the from_class option -- as well as define a success url to redirect a user if an update is successful, which is the purpose of the success_url option.

Next in listing 9-20 is the url definition that hooks up the UpdateView class-based view class. Because an UpdateView class-based view must present a specific model record to edit, it relies on a url parameter to limit a query to a single record. In this case, notice the url contains the pk url parameter that gets passed to the class-based view -- just like it's done with a DetailView class-based view.

For example, if a request is made for the url /items/edit/1/, 1 is passed as the pk argument to the UpdateView class-based view, which in turn performs the query Item.objects.get(pk=1) to retrieve the record and present it in a form for editing.

Finally, the last part in listing 9-20 illustrates the template item_form.html which displays the record inside a form to edit. One important aspect of the default UpdateView class-based view template, is that it's the same default template used by a CreateView class-based view. This is because both class-based views share the same parent class-based view class for this functionality, not to mention the purpose of the form is the same (e.g. the form in a CreateView class-based view is sent empty for a user to fill in new record data, where as the form in a UpdateView class-based view is sent pre-filled with a record to update the record data).

For this reason, the template in listing 9-20 is almost identical to the template in listing 9-9 used in a CreateView class-based view. The minor difference is that if the template detects the presence the object variable in its context, it means the UpdateView class-based view is passing a record for editing and the form button is set to Update, otherwise, if no object variable is present, it means the CreateView class-based view is calling the template and the form button is set to Create.

Recapping, the most important default behaviors of an UpdateView class-based view:

As you can see in this example, a class-based view that inherits its behavior from the UpdateView class, cuts-down the boilerplate code needed to edit a model record.

UpdateView fields and methods

Like other class-based views, UpdateView class-based views can also be created with custom fields and methods to override their default behaviors.

As it turns out, the UpdateView class inherits its behavior from many other Django class-based views, presented in the following list:

django.views.generic.detail.SingleObjectTemplateResponseMixin
django.views.generic.base.TemplateResponseMixin
django.views.generic.edit.BaseUpdateView
django.views.generic.edit.ModelFormMixin
django.views.generic.edit.FormMixin
django.views.generic.detail.SingleObjectMixin
django.views.generic.edit.ProcessFormView
django.views.generic.base.View
Note An UpdateView class-based view inherits many fields and methods from its parent classes. The following options are the most common ones, for an exhaustive list consult each of the UpdateView parent classes.

Basic UpdateView options: model, form_class and success_url fields & url with pk parameter

As you saw in listing 9-20, the essential logic fulfilled by an UpdateView class-based view is to get a model record and display it in a form for editing. Therefore the model field is one of the essential pieces for this type of class-based view and must always by provided. In addition, because a record is updated via form, it's also necessary to specify a form through the form_class option, as well as a success url through the success_url option for when an update is successful.

In order to get a specific model record, an UpdateView class-based view must also define a url parameter which helps it select a model record to edit. By default, this url parameter must be named pk and its value is used to perform a query on the model value using the pk field, which is generally equivalent to a model's id field.

Tip An UpdateView class-based view also inherits its behavior from many of the class-based view classes described earlier. Therefore, you can also use: the template_name field to specify a custom template name; the content_type field to specify a MIME type; the get_context_data() method to alter the context used by a template; as well as the context_object_name field to declare a different context variable to access a record in a template. In addition, because an UpdateView class-based view requires obtaining a model record, it can also use: the pk_url_kwarg field to accept a different url parameter named differently than pk; the slug_field field to specify an alternative record query field; and the slug_url_kwarg field to accept a different url parameter named differently than slug.Finally, an UpdateView class-based view can also use the the get() and post() class-based view methods to get full control over the view workflow. The previous class-based view sections which describes how use all these class-based view fields and methods.

Delete records with the class-bases view DeleteView

When you delete a Django model record, besides executing the actual delete operation, it's generally a good practice to present a confirmation page to a user. The Django DeleteView class-based view is specifically designed to cut-down on the amount of boilerplate code needed to delete a model record, while presenting a confirmation page. Listing 9-21 illustrates a DeleteView class-based view example.

Listing 9-21 Django class-based view with DeleteView to delete record

# views.py
from django.views.generic.edit import DeleteView
from .models import Item

class ItemDelete(UpdateView):
    model = Item
    success_url = reverse_lazy('items:index')

# urls.py 
from django.conf.urls import url
from coffeehouse.items import views as items_views

urlpatterns = [
    url(r'^delete/(?P<pk>\d+)/$', items_views.ItemDelete.as_view(), name='delete'),  
]

# templates/items/item_confirm_delete.html 
  <form method="post">
        {% csrf_token %}
        Do you really want to delete "{{ object }}"?
        <button class="btn btn-primary" type="submit">Yes, remove it!</button>
  </form>

The first definition in listing 9-21 is the ItemDelete class which inherits its behavior from the DeleteView class-based view class. The model field in the ItemDelete class sets a value to Item, which tells Django to delete Item model records. In addition, because you'll want users to confirm the delete operation, the success_url option must also be declared to specify where to take users after the delete operation is successful.

Next in listing 9-21 is the url definition that hooks up the DeleteView class-based view class. Because a DeleteView class-based view must be told which model record to delete, it relies on a url parameter to provide this query delimiter. In this case, notice the url contains the pk url parameter that gets passed to the class-based view -- just like it's done with a DetailView class-based view.

For example, if a request is made for the url /items/delete/1/, 1 is passed as the pk argument to the DeleteView class-based view, which in turn performs the query Item.objects.get(pk=1) to prepare the record for deletion.

Finally, the last part in listing 9-21 illustrates the item_confirm_delete.html template which is used for the user-facing deletion sequence. Notice this template contains a pseudo-form (i.e no form fields) with a question and submit button. The reason for this pseudo-form is the item_confirm_delete.html template has a dual function.

If an HTTP GET request is made on a DeleteView class-based view, a page with this pseudo-form is returned to the user presenting him with the question Do you really want to delete "{{ object }}"? , where object is the model record to be deleted. If the user clicks on this pseudo-form submit button, it issues an HTTP POST request -- notice the <form method="post"> tag -- to the same DeleteView class-based view, which invokes the actual delete procedure. In this manner, this template with a pseudo-form allows a user to confirm if he really wants to delete the record after hitting a url delete link (e.g. /items/delete/1/), instead of immediately deleting a record when hitting a url delete link.

Recapping, the most important default behaviors of a DeleteView class-based view are:

As you can see in this example, a class-based view that inherits its behavior from the DeleteView class, cuts-down the boilerplate code needed to delete a model record.

DeleteView fields and methods

Like other class-based views, a DeleteView class-based view can also be created with custom fields and methods to override their default behaviors.

As it turns out, the DeleteView class inherits its behavior from many other Django class-based views, presented in the following list:

django.views.generic.detail.SingleObjectTemplateResponseMixin
django.views.generic.base.TemplateResponseMixin
django.views.generic.edit.BaseDeleteView
django.views.generic.edit.DeletionMixin
django.views.generic.detail.BaseDetailView
django.views.generic.detail.SingleObjectMixin
django.views.generic.base.View
Note A DeleteView class-based view inherits many fields and methods from its parent classes. The following options are the most common ones, for an exhaustive list consult each of the DeleteView parent classes.

Basic DeleteView options: model and success_url fields & url with pk parameter

As you saw in listing 9-21, the essential logic fulfilled by a DeleteView class-based view is to get a model record and delete it while presenting a confirmation page. Therefore the model field is one of the essential pieces to this type of class-based view and must always by provided. In addition, because a record is set to be deleted, it's also necessary to specify a success url through the success_url option to re-direct a user after the record is deleted.

In order to get a specific model record, a DeleteView class-based view must also define a url parameter which helps it select a model record to delete. By default, this url parameter must be named pk and its value is used to perform a query on the model value using the pk field, which is generally equivalent to a model's id field.

Tip A DeleteView class-based view also inherits its behavior from many of the class-based view classes described earlier. Therefore, you can also use: the template_name field to specify a custom template name; the content_type field to specify a MIME type; the get_context_data() method to alter the context used by a template; as well as the context_object_name field to declare a different context variable to access a record in a template. In addition, because a DeleteView class-based view requires obtaining a model record, it can also use: the pk_url_kwarg field to accept a different url parameter named differently than pk; the slug_field field to specify an alternative record query field; and the slug_url_kwarg field to accept a different url parameter named differently than slug.Finally, an DeleteView class-based view can also use the get() and post() class-based view methods to get full control over the view workflow. The previous class-based view sections describe how to use all these class-based view fields and methods.

Class-based views with mixins

Although all class-based views that operate on models typically follow the same workflow to create, read, update and delete model records, it's fair to say that after seeing the previous class-based view sections, you'll often find yourself adjusting the default behavior of class-based views.

While it can be perfectly reasonable to adjust the methods and fields of a class-based view a couple of times, it can get tiresome if you need to do it over and over to obtain the same functionality across dozens or hundreds of class-based views.

Nowhere is this more evident than when you're forced to define a get() or post() method in a class-based view, to include some functionality that's not supported in a class-based view (e.g. CreateView, ListView, DetailView, UpdateView, DeleteView), which requires typing in a a long winded workflow which can turn out to be repetitive if done multiple times. To cut down on repetitive customizations in the context of class-based views, you can use mixins.

For starters, you've already used mixins in the context of class-based views, even if you didn't realize it. If you looked closely at the class-based view classes that provide the behaviors to the model class-based views described in previous sections, you may have noticed many include the term mixin in their name (e.g. ModelFormMixin, FormMixin, SingleObjectMixin).

A software mixin is a construct that allows inheritance-like behavior between classes, noting the emphasis on inheritance-like. When you use class inheritance, a parent-child class relationship is often described with the 'is a' term (e.g. if a Drink class inherits its behavior from an Item class, a Drink is an Item). A mixin class on the other hand, allows a class to adopt the behaviors of a mixin class without the 'is a' behavior.

In other words, a mixin class is a way to add functionality to classes, with the mixin class serving as a re-usable component. For example, you can have a mixin Checkout class used by a Store class and OnlineStore class, which allows the functionality of the mixin class to be reused in any other class. Notice that for mixin classes, the inheritance 'is a' behavior doesn't apply, you can't say a Store is a Checkout or an OnlineStore is a Checkout, it's more of 'uses a' behavior. Therefore although mixin classes -- semantically speaking -- are used to inherit behaviors, technically speaking they don't use inheritance as it's commonly known in software engineering, hence the use of the term inheritance-like.

So why are mixins important to class-based views ? It turns out, all of the base class-based view classes you learned about in the previous section are built on class-based views mixins. This not only means you can mix and match mixins to create custom class-based views, but you can also create or re-use other mixins to enhance the functionality of class-based views.

Earlier when you learned about the CreateView() class-based view, you may recall the examples in listing 9-13 and listing 9-14 added a Django framework success message to inform a user when a record was created. In the case of listing 9-13, this required tapping into the form_valid() class-based view method to add this message, while in the case of listing 9-14, this required tapping into the post() class-based view method. Although both approaches are perfectly valid, declaring either of these methods in a class-based view for the sole purpose of adding a Django framework success message is a lot of work.

By using a mixin on a class-based view, you can simplify the addition of Django framework success messages in a class-based view to a single field, without the need to customize more elaborate methods and/or add logic to a class-based view.

Listing 9-22 illustrates a CreateView class-based view that uses a mixin to support this functionality to add a Django framework success message to a class-based view response.

Listing 9-22 Django class-based view with CreateView and mixin class

# views.py
from django.views.generic.edit import CreateView
from django.contrib.messages.views import SuccessMessageMixin
from .models import Item, ItemForm

class ItemCreation(SuccessMessageMixin,CreateView):
    model = Item
    form_class = ItemForm
    success_url = reverse_lazy('items:index')
    success_message = "Item %(name)s created successfully"
Caution Mixin classes should always be declared first to take precedence over coarser grained class-based views classes in the context of multiple inheritance class-based views (e.g. class ItemCreation(SuccessMessageMixin,CreateView) ).

The first important aspect of listing 9-22 is the import statement for the SuccessMessageMixin mixin class, which is what gives any class-based view the ability to easily add success messages. Next, the SuccessMessageMixin mixin class is added to the ItemCreation class, along with the CreateView class-based view to give the class-based view its core functionality. Notice the mixin class is added to the class-based view using standard Python inheritance syntax.

Once the ItemCreation class-based view gains access to the SuccessMessageMixin mixin class behavior, all that's needed to generate a success message is define the actual message in the success_message field. In this manner, when a success operation occurs in the context of the class-based view (e.g. the success_url is triggered), the class-based view automatically adds the success_message value to the request to display to an end user.

As you can see in listing 9-22, the process to add a Django framework success message to a class-based view is greatly simplified and made reusable by means of a mixin class. This is particularly true, when you compare it to the approaches taken in listing 9-13 and listing 9-14, consisting of overriding class-based view methods that require a lot of typing -- many repetitive -- to fulfill a simple task.