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.
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 ACreateView
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 theCreateView
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:
- The list of records is made from
all records of the model defined in the
model
option. - The template to render the list
of records uses the convention
<app_name>/<model_name>_list.html
under theTEMPLATES
directory path of a project. - The context variable passed to a
template (i.e. the one containing the records) is named
object_list
.
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:
- The model record is determined
based on the
model
option and the urlpk
parameter that delimits the query to a single record based on the model's primary key. - The template to render the record
uses the convention
<app_name>/<model_name>_detail.html
under theTEMPLATES
directory path of a project. - The context variable passed to a
template (i.e. the one containing the record) is named after the
model
of the class-based view. (e.g. ifmodel=Item
, the context variable is nameditem
).
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 AListView
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 theListView
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 AListView
class-based view also inherits its behavior from many of the same classes as theCreateView
class described earlier. Therefore, you can also use: thetemplate_name
field to specify a custom template name; thecontent_type
field to specify a MIME type and theget_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 AListView
class-based view can also use theget()
class-based view method to get full control over the view workflow. See the previous section on theCreateView
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 ADetailView
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 theDetailView
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 ADetailView
class-based view also inherits its behavior from many of the class-based view classes described earlier. Therefore, you can also use: thetemplate_name
field to specify a custom template name; thecontent_type
field to specify a MIME type; theget_context_data()
method to alter the context used by a template; as well as thecontext_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 ADetailView
class-based view can also use theget()
class-based view method to get full control over the view workflow. See the previous section on theCreateView
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:
- The model record to update is
determined based on the
model
option and a urlpk
parameter that delimits the query to a single record based on the model's primary key. - The template to render a form to
update a record uses the convention
<app_name>/<model_name>_form.html
under theTEMPLATES
directory path of a project; noting it's the same default template used byCreateView
class-based views, for which you can rely on the presence of theobject
context variable to determine if it's theUpdateView
calling the template. - The context variable passed to a
template (i.e. the one containing the record) is named
object
, albeit the form is automatically populated with this record value.
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 AnUpdateView
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 theUpdateView
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 AnUpdateView
class-based view also inherits its behavior from many of the class-based view classes described earlier. Therefore, you can also use: thetemplate_name
field to specify a custom template name; thecontent_type
field to specify a MIME type; theget_context_data()
method to alter the context used by a template; as well as thecontext_object_name
field to declare a different context variable to access a record in a template. In addition, because anUpdateView
class-based view requires obtaining a model record, it can also use: thepk_url_kwarg
field to accept a different url parameter named differently thanpk
; theslug_field
field to specify an alternative record query field; and theslug_url_kwarg
field to accept a different url parameter named differently thanslug
.Finally, anUpdateView
class-based view can also use the theget()
andpost()
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:
- The model record to delete is
determined based on the
model
option and a urlpk
parameter that delimits the query to a single record based on the model's primary key. - The template to render a form to
update a record uses the convention
<app_name>/<model_name>_confirm_delete.html
under theTEMPLATES
directory path of a project. - The context variable passed to a
template (i.e. the one containing the record to delete) is named
object.
- An HTTP GET request on a
DeleteView
class-based view presents the confirmation page from the<app_name>/<model_name>_confirm_delete.html
template. An HTTP POST request on aDeleteView
class-based view performs the actual delete procedure.
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 ADeleteView
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 theDeleteView
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 ADeleteView
class-based view also inherits its behavior from many of the class-based view classes described earlier. Therefore, you can also use: thetemplate_name
field to specify a custom template name; thecontent_type
field to specify a MIME type; theget_context_data()
method to alter the context used by a template; as well as thecontext_object_name
field to declare a different context variable to access a record in a template. In addition, because aDeleteView
class-based view requires obtaining a model record, it can also use: thepk_url_kwarg
field to accept a different url parameter named differently thanpk
; theslug_field
field to specify an alternative record query field; and theslug_url_kwarg
field to accept a different url parameter named differently thanslug
.Finally, anDeleteView
class-based view can also use theget()
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.