Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Use mixins with class-based views


May 14, 2021 Django


Table of contents


Use mixins in class-based views

Warning: This is an advanced topic. Before exploring these techniques, it is recommended that you have some understanding of Django's class-based views.

Django's built-in class-based view provides many features, but you may need to use some of them separately. F or example, you might want to write a view that renders a template for http response, but you can't use TemplateView; M aybe you just need to render the template POST on it, and get the GET to do exactly something else. Although you can use TempleResponse directly, this can lead to code duplication.

As a result, Django also offers a number of mixers that provide more discrete functionality. F or example, template rendering is encapsulated in TemplateResponseMixin. The Django reference documentation contains the full documentation of all mixins.

Context and template replies

Two central mixins are provided that help provide a consistent interface when using templates in class-based views.

  • TemplateResponseMixin Each built-in view that returns a templateResponse will call the provided render_to_response() method TempleResponseMixin. I n most cases, this get() method will be called for you (for example, both the TemplateView and the implemented method will call it DetailView); F or this example, see the JSONResponseMixin example. r ender_to_response () itself calls get_template_names(), template_name queries on class-based views by default, and the other two mixins (SingleObject Temple ReponseMixin and MultipleObject TempleResponseMixin) override this property. to provide more flexible default values when working with actual objects.
  • ContextMixin Every built-in view that requires contextual data, such as for rendering templates, including TemplateResponseMixin, above, should call get_context_data() to pass any data they want to make sure is in it as a keyword parameter. g et_context_data () return to the dictionary, and in ContextMixin, its keyword parameters are returned, but are usually overwritten to add more members to the dictionary. You can also use extra_context properties.

Establish a generic class-based view of Django

Let's see how Django's two class-based generic views are built by mixin, which provides discrete functionality. W e'll consider DetailView, which renders a "detailed" view of an object, and ListView, which renders a list of objects, usually from a query set, and optionally peddles them. This introduces us to four mixins that provide useful functionality when using a single Django object or multiple objects.

There are also common editorial ideas mixed in (FormView and specific models of views CreativeView, UpdateView, and DeleteView), and in a common date-based view. These are described in the mixin reference document.

DetailView: Use a single Django object

To display the details of an object, we basically need to do two things: we need to find the object, and then we need TemplateResponse to make one with the appropriate template and use the object as context.

To get the object, You need The DeltaView to rely on SingleObjectMixin, a get_object() method that provides a way to find the object based on the requested URL (it looks for the pk and slug keyword parameters based on the declaration in URLConf, and then looks for the object from the properties of the model view, or provides the queryset property). SingleObjectMixin also overrides get_context_data(), which is used in all Django's built-in class-based views to provide contextual data for template rendering.

Then TemplateResponse, which wants DetailView to use StyleObjectTemplateResponseMixin, uses , as discussed above, which extends TempleResponseMixin to cover get_template_names (). I n fact, it provides a fairly complex set of options, but the main option most people use is /_detail.html. T his _detail can be set to change template_name_suffix subcategory of things in the game. ( For example, a generic editing idea uses _form to create and update comments, and _confirm_delete to delete them.)

ListView: Use many Django objects

The object list follows much the same pattern: we need a (possibly ped) list of objects, usually QuerySet, and then we need TemplateResponse to use that list of objects to make one using the appropriate template.

To get an object, listView uses MultipleObjectMixin, which provides get_queryset () and paginate_queryset(). U nlike withth, SingleObjectMixin does not need to close part of the URL to determine the set of queries to use, so the default uses the qurysetor model property on the view class. A get_queryset overlays () here is to dynamically change objects, such as depending on the current user or excluding future posts from the blog.

MultipleObjectMixin also overrides get_context_data() to include the appropriate context variable for plying (or provide virtual variables if plying is disabled). It relies object_list the keyword parameter ListView, which is passed as a keyword parameter.

Make a TemplateResponse, ListView and then use MultipleObjectTemplateResponseMixin; A s with the method above, this method overrides, get_template_names() to provide, most commonly, and the section is again obtained from the property. ( A common date-based view uses suffixes such as , and so on, to use different templates for a variety of specialized date-based list views.) )a range of options/_list.html``_listtemplate_name_suffix_archive``_archive_year

Mix in with Django's class-based view

Now that we've seen how Django's class-based generic view uses the provided mixins, let's look at other ways to put them together. Of course, we still combine them with built-in class-based views or other generic class-based views, but you can solve many rare problems that are less out-of-the-box than Django.

Warning: Not all mixin can be used together, and not all views based on common classes can be used with all other mixins. H ere, we provide some possible examples. If you want to merge other features, you must consider the interaction between properties and methods that overlap between the different classes you use, and how the method resolution order affects which versions of the method are called in which order.

Django's class-based view and class-based view mixin reference documentation will help you understand which properties and methods can cause conflicts between different classes and mixin.

If in doubt, it's usually best to give up View or TemplateView as a base, perhaps using SingleObjectMixin and MultipleObjectMixin. A lthough you may end up writing more code, others may understand it more easily later, and less interactivity worries you and saves you some time. ( Of course, you can always gain insight into Django's implementation of a generic class-based view for inspiration on how to solve problems.)

SingleObjectMixin is used with View

If we are going to write a class-based view that responds only to the response, we will create a subPOST class View and post() write a method in that sub-class. However, if we want our processing to be able to handle specific objects identified from URLs, we will need to provide the functionality of SingleObjectMixin.

We'll demonstrate this by using the model that Author uses in the general class-based view introduction.

the views.py

from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author

class RecordInterest(SingleObjectMixin, View):
  """Records the current user's interest in an author."""
  model = Author

  def post(self, request, *args, **kwargs):
      if not request.user.is_authenticated:
          return HttpResponseForbidden()

      # Look up the author we're interested in.
      self.object = self.get_object()
      # Actually record interest somehow here!

      return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))

In fact, you might want to record interest in key-value storage rather than in a relationship database, so we've omitted that. T he only view we need to worry about using, SingleObjectMixin, is where we look for authors of interest, and it does this by calling self.get_object(). Mixin handles all other transactions for us.

We can easily hook it up to our URL:

the urls.py

from django.urls import path
from books.views import RecordInterest

urlpatterns = [
  #...
  path('author/<int:pk>/interest/', RecordInterest.as_view(), name='author-interest'),
]

Note the pk naming group, which get_object () to find Theuthor instance. You can also use slug or any other feature, SingleObjectMixin.

SingleObjectMixin with the use of ListView

ListView provides built-in pedding capabilities, but you may want to peddle a list of all objects that are linked to another object through foreign keys. In our publishing example, you might want to peddle all the books from a particular publisher.

One implementation is to use SingleObjectMixin in conjunction with ListView so that the query set of the peddle book list can be detached from the publisher found as a single object. To do this, we need to have two different sets of queries:

  • Book by... The query set ListView is used because we can access the books that Publisser wants to list, so we can overwrite get_queryset() and use Publisser's reverse foreign key manager.
  • Publisher queryset is used get_object () the default implementation we will rely get_object () to get the correct Publicisher object. However, we need to explicitly pass a quryset parameter, because otherwise the default implementation get_object() will call get_queryset() we have rewritten to return the Book object instead of the object's object Publisser.

Note: We must carefully consider get_context_data (). B ecause both StyleObjectMixin and ListView place things below the values in context data (context_object_name if set), we'll explicitly make sure that things are in publisher context data. ListView will add page_obj and paginator we provide us remember to call super().

Now we can write a new Publisser Detail:

from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher

class PublisherDetail(SingleObjectMixin, ListView):
  paginate_by = 2
  template_name = "books/publisher_detail.html"

  def get(self, request, *args, **kwargs):
      self.object = self.get_object(queryset=Publisher.objects.all())
      return super().get(request, *args, **kwargs)

  def get_context_data(self, **kwargs):
      context = super().get_context_data(**kwargs)
      context['publisher'] = self.object
      return context

  def get_queryset(self):
      return self.object.book_set.all()

Notice how we set self.object, get() so that we can use it get_context_data later in the property () and get_queryset (). If you don't set template_name, the template defaults to the normal ListView selection, in which case it's "books/book_list.html" because it's a list of books;

The paginate_by so you don't have to create a lot of books to see that plying work is intentionally small for example! Here's the template you want to use:

{% extends "base.html" %}

{% block content %}
  <h2>Publisher {{ publisher.name }}</h2>

  <ol>
    {% for book in page_obj %}
      <li>{{ book.title }}</li>
    {% endfor %}
  </ol>

  <div class="pagination">
      <span class="step-links">
          {% if page_obj.has_previous %}
              <a href="?page={{ page_obj.previous_page_number }}">previous</a>
          {% endif %}

          <span class="current">
              Page {{ page_obj.number }} of {{ paginator.num_pages }}.
          </span>

          {% if page_obj.has_next %}
              <a href="?page={{ page_obj.next_page_number }}">next</a>
          {% endif %}
      </span>
  </div>
{% endblock %}

Avoid anything more complicated

In general, you can use TemplateResponseMixin and SingleObjectMixin when you need their features. A s shown above, with a little attention, you can even use ListView with StyleObjectMixin. However, as you try to do so, things become more complex, and a good lesson is:

Hint: Each of your views should use only mixins or a set of generic class-based views: details, lists, edits, and dates. For example, combining TempleView (built into view) with MultipleObjectMixin (Universal List) is a good choice, but combining StyleObjectMixin (Universal Details) with MultipleObjectMixin (Universal List) can be a problem.

To show what happens when you try to become more complex, we show an example that sacrifices readability and maintainability when there is a simpler solution. First, let's take a look at the naive attempt to combine DetailView with FormMixin so that we can POST bring Django Form to the same URL DetailView that we use to display objects.

FormMixin with Use DetailView

Think back to our previous example of using View and SingleObjectMixin together. W e're recording users' interest in specific authors; now we're going to ask them to leave a message about why they like them. Again, let's assume that we don't store it in a relationship database, but in something more esoterical that we no longer worry about.

At this point, it is natural for Form to encapsulate the information sent to Django from the user's browser. A lso, we invested heavily in REST, so we wanted to display the author using the same URL that displays messages from the user. Let's rewrite our code AutoDetailView.

Although a must be added to the context data in order to render it in the template, we will retain the processing from get. W e also want to introduce form processing from it and write some code so that it can be called correctly on the form. DetailViewFormFormMixinPOST

Note: Instead of trying DetailView to blend FormView (this post), we use FormMixin and implement post() ourselves, because both views implement get() and things get more confusing.

Our new AutoDetail looks like this:

# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
# run into when combining different generic class-based view
# functionality that is not designed to be used together.

from django import forms
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
from books.models import Author

class AuthorInterestForm(forms.Form):
  message = forms.CharField()

class AuthorDetail(FormMixin, DetailView):
  model = Author
  form_class = AuthorInterestForm

  def get_success_url(self):
      return reverse('author-detail', kwargs={'pk': self.object.pk})

  def post(self, request, *args, **kwargs):
      if not request.user.is_authenticated:
          return HttpResponseForbidden()
      self.object = self.get_object()
      form = self.get_form()
      if form.is_valid():
          return self.form_valid(form)
      else:
          return self.form_invalid(form)

  def form_valid(self, form):
      # Here, we would record the user's interest using the message
      # passed in form.cleaned_data['message']
      return super().form_valid(form)

get_success_url () provides a redirected location that can be used for the default implementation form_valid(). Post () As mentioned earlier, we must provide our own.

Better solutions

The number of subtle interactions between FormMixin and DetailView has tested our ability to manage things. You are less likely to want to write this class yourself.

In this case, although writing processing code involves a lot of repetition, you can post () write your own methods and keep DetailView's only universal feature Form.

Alternatively, it will still work less than the method above in a form that has for processing, and it can use a separate view FormView from different DetailViews without concern.

Another, better solution

What we're actually trying to do here is use two different class-based views from the same URL. S o why not? W e have a very clear division here: GET requests should get DetailView (Form added to contextual data), and POST requests should get FormView. Let's set up these views first.

The AutoDisplay view is almost identical to the one we first introduced when AutoDetail was introduced; we're writing our own get_context_data() to make authorInterestForm available. get_object () For clarity, we will skip the previous substitution:

from django import forms
from django.views.generic import DetailView
from books.models import Author

class AuthorInterestForm(forms.Form):
  message = forms.CharField()

class AuthorDisplay(DetailView):
  model = Author

  def get_context_data(self, **kwargs):
      context = super().get_context_data(**kwargs)
      context['form'] = AuthorInterestForm()
      return context

Then AutoInterest is a FormView, but we have to bring in SingleObjectMixin so that we can find the author we're talking about, and we have to remember to set it up template_name to make sure that the form error will render the same template GET as AutoDisplayon:

from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin

class AuthorInterest(SingleObjectMixin, FormView):
  template_name = 'books/author_detail.html'
  form_class = AuthorInterestForm
  model = Author

  def post(self, request, *args, **kwargs):
      if not request.user.is_authenticated:
          return HttpResponseForbidden()
      self.object = self.get_object()
      return super().post(request, *args, **kwargs)

  def get_success_url(self):
      return reverse('author-detail', kwargs={'pk': self.object.pk})

Finally, we put them together with a new Author Detail perspective. As we already know, calling as_view() class-based views makes us behave exactly the same as function-based views, so we can choose between two sub-views.

Of course, you can pass the keyword parameter as_view()in the same way as you would in URLconf, for example, if you want the AutoInterest behavior to appear on another URL, but using a different template:

from django.views import View

class AuthorDetail(View):

  def get(self, request, *args, **kwargs):
      view = AuthorDisplay.as_view()
      return view(request, *args, **kwargs)

  def post(self, request, *args, **kwargs):
      view = AuthorInterest.as_view()
      return view(request, *args, **kwargs)

This method can also use TemplateView with any other general class-based view that is directly inherited from View or inherited, or your own class-based view, because it keeps the different views as separate as possible.

Class-based views shine when you want to do the same thing multiple times. Suppose you are writing an API, and each view should return JSON instead of rendered HTML.

We can create a mixin class to use in all views and process the conversion to JSON at once.

For example, the JSON blend might look like this:

from django.http import JsonResponse

class JSONResponseMixin:
    """
    A mixin that can be used to render a JSON response.
    """
    def render_to_json_response(self, context, **response_kwargs):
        """
        Returns a JSON response, transforming 'context' to make the payload.
        """
        return JsonResponse(
            self.get_data(context),
            **response_kwargs
        )

    def get_data(self, context):
        """
        Returns an object that will be serialized as JSON by json.dumps().
        """
        # Note: This is *EXTREMELY* naive; in reality, you'll need
        # to do much more complex handling to ensure that arbitrary
        # objects -- such as Django model instances or querysets
        # -- can be serialized as JSON.
        return context

Note: Check the serialized Django object documentation for more information on how to correctly convert the Django model and query set to JSON correctly.

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

from django.views.generic import TemplateView

class JSONView(JSONResponseMixin, TemplateView):
    def render_to_response(self, context, **response_kwargs):
        return self.render_to_json_response(context, **response_kwargs)

Similarly, we can use our mixin with one of the common views. We can make our own version with JSONResponseMixin and django.views.generic.baseDetailView -hybrid (mixed with the behavior before the DetailView template renders):

from django.views.generic.detail import BaseDetailView

class JSONDetailView(JSONResponseMixin, BaseDetailView):
    def render_to_response(self, context, **response_kwargs):
        return self.render_to_json_response(context, **response_kwargs)

You can then deploy this view DetailView in the same way as any other view, and behave exactly the same - except in the format of the response.

If you want to be truly adventurous, you can even mix a DetailView sub-class that can return two HTML and JSON content, depending on the HTTP request, for certain properties such as query parameters or HTTP headers. Mix JSONResponseMixin and SingleObjectTemplateResponseMixin and render_to_response() override the implementation based on the type of response requested by the user to take the appropriate rendering method:

from django.views.generic.detail import SingleObjectTemplateResponseMixin

class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
    def render_to_response(self, context):
        # Look for a 'format=json' GET argument
        if self.request.GET.get('format') == 'json':
            return self.render_to_json_response(context)
        else:
            return super().render_to_response(context)

Due to the way the Python workaround overloads, the render_to_response() of the final call to super() .render_to_response (context) implements TempleResponseMixin.

For more information: https://docs.djangoproject.com/en/3.0/