Using mixins with class-based views

    This is an advanced topic. A working knowledge of Django’s class-based views is advised before exploring these techniques.

    Django’s built-in class-based views provide a lot of functionality, but some of it you may want to use separately. For instance, you may want to write a view that renders a template to make the HTTP response, but you can’t use ; perhaps you need to render a template only on POST, with GET doing something else entirely. While you could use TemplateResponse directly, this will likely result in duplicate code.

    For this reason, Django also provides a number of mixins that provide more discrete functionality. Template rendering, for instance, is encapsulated in the . The Django reference documentation contains full documentation of all the mixins.

    Two central mixins are provided that help in providing a consistent interface to working with templates in class-based views.

    Every built in view which returns a TemplateResponse will call the method that TemplateResponseMixin provides. Most of the time this will be called for you (for instance, it is called by the get() method implemented by both TemplateView and ); similarly, it’s unlikely that you’ll need to override it, although if you want your response to return something not rendered via a Django template then you’ll want to do it. For an example of this, see the JSONResponseMixin example.

    render_to_response() itself calls , which by default will look up template_name on the class-based view; two other mixins ( and MultipleObjectTemplateResponseMixin) override this to provide more flexible defaults when dealing with actual objects.

    Every built in view which needs context data, such as for rendering a template (including TemplateResponseMixin above), should call get_context_data() passing any data they want to ensure is in there as keyword arguments. get_context_data() returns a dictionary; in ContextMixin it returns its keyword arguments, but it is common to override this to add more members to the dictionary. You can also use the attribute.

    Let’s look at how two of Django’s generic class-based views are built out of mixins providing discrete functionality. We’ll consider DetailView, which renders a “detail” view of an object, and , which will render a list of objects, typically from a queryset, and optionally paginate them. This will introduce us to four mixins which between them provide useful functionality when working with either a single Django object, or multiple objects.

    There are also mixins involved in the generic edit views (FormView, and the model-specific views , UpdateView and ), and in the date-based generic views. These are covered in the mixin reference documentation.

    To show the detail of an object, we basically need to do two things: we need to look up the object and then we need to make a with a suitable template, and that object as context.

    To get the object, DetailView relies on , which provides a get_object() method that figures out the object based on the URL of the request (it looks for pk and slug keyword arguments as declared in the URLConf, and looks the object up either from the attribute on the view, or the queryset attribute if that’s provided). SingleObjectMixin also overrides , which is used across all Django’s built in class-based views to supply context data for template renders.

    To then make a TemplateResponse, uses SingleObjectTemplateResponseMixin, which extends , overriding get_template_names() as discussed above. It actually provides a fairly sophisticated set of options, but the main one that most people are going to use is <app_label>/<model_name>_detail.html. The _detail part can be changed by setting on a subclass to something else. (For instance, the generic edit views use _form for create and update views, and _confirm_delete for delete views.)

    ListView: working with many Django objects

    Lists of objects follow roughly the same pattern: we need a (possibly paginated) list of objects, typically a QuerySet, and then we need to make a with a suitable template using that list of objects.

    To get the objects, ListView uses , which provides both get_queryset() and . Unlike with SingleObjectMixin, there’s no need to key off parts of the URL to figure out the queryset to work with, so the default uses the or model attribute on the view class. A common reason to override here would be to dynamically vary the objects, such as depending on the current user or to exclude posts in the future for a blog.

    MultipleObjectMixin also overrides to include appropriate context variables for pagination (providing dummies if pagination is disabled). It relies on object_list being passed in as a keyword argument, which ListView arranges for it.

    To make a , ListView then uses ; as with SingleObjectTemplateResponseMixin above, this overrides get_template_names() to provide , with the most commonly-used being <app_label>/<model_name>_list.html, with the _list part again being taken from the template_name_suffix attribute. (The date based generic views use suffixes such as _archive, _archive_year and so on to use different templates for the various specialized date-based list views.)

    Now we’ve seen how Django’s generic class-based views use the provided mixins, let’s look at other ways we can combine them. We’re still going to be combining them with either built-in class-based views, or other generic class-based views, but there are a range of rarer problems you can solve than are provided for by Django out of the box.

    Warning

    Not all mixins can be used together, and not all generic class based views can be used with all other mixins. Here we present a few examples that do work; if you want to bring together other functionality then you’ll have to consider interactions between attributes and methods that overlap between the different classes you’re using, and how will affect which versions of the methods will be called in what order.

    The reference documentation for Django’s class-based views and will help you in understanding which attributes and methods are likely to cause conflict between different classes and mixins.

    If in doubt, it’s often better to back off and base your work on View or , perhaps with SingleObjectMixin and . Although you will probably end up writing more code, it is more likely to be clearly understandable to someone else coming to it later, and with fewer interactions to worry about you will save yourself some thinking. (Of course, you can always dip into Django’s implementation of the generic class-based views for inspiration on how to tackle problems.)

    We’ll demonstrate this with the Author model we used in the generic class-based views introduction.

    views.py

    In practice you’d probably want to record the interest in a key-value store rather than in a relational database, so we’ve left that bit out. The only bit of the view that needs to worry about using is where we want to look up the author we’re interested in, which it does with a call to self.get_object(). Everything else is taken care of for us by the mixin.

    We can hook this into our URLs easily enough:

    urls.py

    1. from django.urls import path
    2. from books.views import RecordInterestView
    3. urlpatterns = [
    4. #...
    5. path('author/<int:pk>/interest/', RecordInterestView.as_view(), name='author-interest'),
    6. ]

    Note the pk named group, which get_object() uses to look up the Author instance. You could also use a slug, or any of the other features of .

    Using SingleObjectMixin with ListView

    provides built-in pagination, but you might want to paginate a list of objects that are all linked (by a foreign key) to another object. In our publishing example, you might want to paginate through all the books by a particular publisher.

    One way to do this is to combine ListView with , so that the queryset for the paginated list of books can hang off the publisher found as the single object. In order to do this, we need to have two different querysets:

    Book queryset for use by ListView

    Since we have access to the Publisher whose books we want to list, we override get_queryset() and use the Publisher’s .

    Publisher queryset for use in get_object()

    We’ll rely on the default implementation of get_object() to fetch the correct Publisher object. However, we need to explicitly pass a queryset argument because otherwise the default implementation of get_object() would call get_queryset() which we have overridden to return Book objects instead of Publisher ones.

    Note

    We have to think carefully about get_context_data(). Since both and ListView will put things in the context data under the value of context_object_name if it’s set, we’ll instead explicitly ensure the Publisher is in the context data. will add in the suitable page_obj and paginator for us providing we remember to call super().

    Now we can write a new PublisherDetailView:

    1. from django.views.generic import ListView
    2. from django.views.generic.detail import SingleObjectMixin
    3. from books.models import Publisher
    4. class PublisherDetailView(SingleObjectMixin, ListView):
    5. paginate_by = 2
    6. def get(self, request, *args, **kwargs):
    7. self.object = self.get_object(queryset=Publisher.objects.all())
    8. return super().get(request, *args, **kwargs)
    9. def get_context_data(self, **kwargs):
    10. context = super().get_context_data(**kwargs)
    11. context['publisher'] = self.object
    12. return context
    13. def get_queryset(self):
    14. return self.object.book_set.all()

    Notice how we set self.object within get() so we can use it again later in get_context_data() and get_queryset(). If you don’t set template_name, the template will default to the normal ListView choice, which in this case would be "books/book_list.html" because it’s a list of books; knows nothing about SingleObjectMixin, so it doesn’t have any clue this view is anything to do with a Publisher.

    The paginate_by is deliberately small in the example so you don’t have to create lots of books to see the pagination working! Here’s the template you’d want to use:

    1. {% extends "base.html" %}
    2. {% block content %}
    3. <ol>
    4. {% for book in page_obj %}
    5. <li>{{ book.title }}</li>
    6. {% endfor %}
    7. </ol>
    8. <div class="pagination">
    9. <span class="step-links">
    10. {% if page_obj.has_previous %}
    11. <a href="?page={{ page_obj.previous_page_number }}">previous</a>
    12. {% endif %}
    13. <span class="current">
    14. Page {{ page_obj.number }} of {{ paginator.num_pages }}.
    15. </span>
    16. {% if page_obj.has_next %}
    17. <a href="?page={{ page_obj.next_page_number }}">next</a>
    18. {% endif %}
    19. </span>
    20. </div>
    21. {% endblock %}

    Generally you can use and SingleObjectMixin when you need their functionality. As shown above, with a bit of care you can even combine SingleObjectMixin with . However things get increasingly complex as you try to do so, and a good rule of thumb is:

    Hint

    Each of your views should use only mixins or views from one of the groups of generic class-based views: detail, list, and date. For example it’s fine to combine TemplateView (built in view) with (generic list), but you’re likely to have problems combining SingleObjectMixin (generic detail) with MultipleObjectMixin (generic list).

    To show what happens when you try to get more sophisticated, we show an example that sacrifices readability and maintainability when there is a simpler solution. First, let’s look at a naive attempt to combine DetailView with to enable us to POST a Django Form to the same URL as we’re displaying an object using .

    Think back to our earlier example of using View and together. We were recording a user’s interest in a particular author; say now that we want to let them leave a message saying why they like them. Again, let’s assume we’re not going to store this in a relational database but instead in something more esoteric that we won’t worry about here.

    At this point it’s natural to reach for a Form to encapsulate the information sent from the user’s browser to Django. Say also that we’re heavily invested in , so we want to use the same URL for displaying the author as for capturing the message from the user. Let’s rewrite our AuthorDetailView to do that.

    Note

    We use FormMixin and implement post() ourselves rather than try to mix with FormView (which provides a suitable post() already) because both of the views implement get(), and things would get much more confusing.

    Our new AuthorDetailView looks like this:

    get_success_url() provides somewhere to redirect to, which gets used in the default implementation of form_valid(). We have to provide our own post() as noted earlier.

    A better solution

    The number of subtle interactions between FormMixin and is already testing our ability to manage things. It’s unlikely you’d want to write this kind of class yourself.

    In this case, you could write the post() method yourself, keeping DetailView as the only generic functionality, although writing handling code involves a lot of duplication.

    Alternatively, it would still be less work than the above approach to have a separate view for processing the form, which could use FormView distinct from without concerns.

    What we’re really trying to do here is to use two different class based views from the same URL. So why not do just that? We have a very clear division here: GET requests should get the DetailView (with the added to the context data), and POST requests should get the FormView. Let’s set up those views first.

    The AuthorDetailView view is almost the same as ; we have to write our own get_context_data() to make the AuthorInterestForm available to the template. We’ll skip the get_object() override from before for clarity:

    1. from django import forms
    2. from django.views.generic import DetailView
    3. class AuthorInterestForm(forms.Form):
    4. message = forms.CharField()
    5. class AuthorDetailView(DetailView):
    6. model = Author
    7. def get_context_data(self, **kwargs):
    8. context = super().get_context_data(**kwargs)
    9. context['form'] = AuthorInterestForm()
    10. return context

    Then the AuthorInterestForm is a FormView, but we have to bring in so we can find the author we’re talking about, and we have to remember to set template_name to ensure that form errors will render the same template as AuthorDetailView is using on GET:

    1. from django.http import HttpResponseForbidden
    2. from django.urls import reverse
    3. from django.views.generic import FormView
    4. from django.views.generic.detail import SingleObjectMixin
    5. class AuthorInterestFormView(SingleObjectMixin, FormView):
    6. template_name = 'books/author_detail.html'
    7. form_class = AuthorInterestForm
    8. model = Author
    9. def post(self, request, *args, **kwargs):
    10. if not request.user.is_authenticated:
    11. return HttpResponseForbidden()
    12. self.object = self.get_object()
    13. return super().post(request, *args, **kwargs)
    14. def get_success_url(self):
    15. return reverse('author-detail', kwargs={'pk': self.object.pk})

    Finally we bring this together in a new AuthorView view. We already know that calling as_view() on a class-based view gives us something that behaves exactly like a function based view, so we can do that at the point we choose between the two subviews.

    You can pass through keyword arguments to in the same way you would in your URLconf, such as if you wanted the AuthorInterestFormView behavior to also appear at another URL but using a different template:

    1. from django.views import View
    2. class AuthorView(View):
    3. def get(self, request, *args, **kwargs):
    4. view = AuthorDetailView.as_view()
    5. return view(request, *args, **kwargs)
    6. def post(self, request, *args, **kwargs):
    7. view = AuthorInterestFormView.as_view()
    8. return view(request, *args, **kwargs)

    This approach can also be used with any other generic class-based views or your own class-based views inheriting directly from View or , as it keeps the different views as separate as possible.

    Where class-based views shine is when you want to do the same thing many times. Suppose you’re writing an API, and every view should return JSON instead of rendered HTML.

    We can create a mixin class to use in all of our views, handling the conversion to JSON once.

    For example, a JSON mixin might look something like this:

    Note

    Check out the Serializing Django objects documentation for more information on how to correctly transform Django models and querysets into JSON.

    This mixin provides a render_to_json_response() method with the same signature as . To use it, we need to mix it into a TemplateView for example, and override render_to_response() to call render_to_json_response() instead:

    1. from django.views.generic import TemplateView
    2. class JSONView(JSONResponseMixin, TemplateView):
    3. def render_to_response(self, context, **response_kwargs):
    4. return self.render_to_json_response(context, **response_kwargs)

    Equally we could use our mixin with one of the generic views. We can make our own version of DetailView by mixing JSONResponseMixin with the – (the DetailView before template rendering behavior has been mixed in):

    1. from django.views.generic.detail import BaseDetailView
    2. class JSONDetailView(JSONResponseMixin, BaseDetailView):
    3. def render_to_response(self, context, **response_kwargs):
    4. return self.render_to_json_response(context, **response_kwargs)

    This view can then be deployed in the same way as any other , with exactly the same behavior – except for the format of the response.

    If you want to be really adventurous, you could even mix a DetailView subclass that is able to return both HTML and JSON content, depending on some property of the HTTP request, such as a query argument or an HTTP header. Mix in both the JSONResponseMixin and a , and override the implementation of render_to_response() to defer to the appropriate rendering method depending on the type of response that the user requested:

    1. from django.views.generic.detail import SingleObjectTemplateResponseMixin
    2. class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
    3. def render_to_response(self, context):
    4. # Look for a 'format=json' GET argument
    5. if self.request.GET.get('format') == 'json':
    6. return self.render_to_json_response(context)
    7. return super().render_to_response(context)

    Because of the way that Python resolves method overloading, the call to super().render_to_response(context) ends up calling the implementation of TemplateResponseMixin.