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

A common class-based view is built in


May 14, 2021 Django


Table of contents


A common class-based view is built in

Writing Web applications can be monotonous because we repeat certain patterns over and over again. Django tries to eliminate some of the monotony of the model and template layers, but Web developers also experience this boredom at the view level.

A common view of Django has been developed to alleviate this pain. They take some common idioms and patterns found in view development and abstract them so that you can quickly write a common view of your data without having to write too much code.

We can identify some common tasks, such as displaying a list of objects and writing code that displays a list of any object. The model under discussion can then be passed to URLconf as an additional parameter.

Django comes with a universal view to do the following:

  • Displays a list of individual objects and a detail page. I f we are creating an application to manage meetings, a TalkListView and a RegisteredUserListView will be examples of list views. A single conversation page is an example of a so-called "detailed" view.
  • Date-based objects are displayed on the year/month/day archive page, associated details, and the "Latest" page.
  • Allows users to create, update, and delete objects, with or without authorization.

Together, these views provide an interface for performing the most common tasks encountered by developers.

Extend the universal view

There is no doubt that using a universal view can greatly speed up development. H owever, in most projects, sometimes a common view is no longer sufficient. Indeed, the most common question raised by the new Django developers is how to make the general view handle a wider range of situations.

This is one of the reasons for redesigning the universal view for version 1.3 - previously they were view functions with confusing lists of options; now, rather than passing a large number of configurations in URLconf, it is recommended that you extend the general view by classifying it and overwriting its properties or methods.

That is, the universal view will be restricted. If you find it difficult to implement a view as a sub-class of a common view, you may find it more efficient to use your own class or feature-based view to write only the code you need.

Some third-party applications provide more examples of common views, or you can write your own views as needed.

A common view of the object

TemplateView is useful, of course, but Django's universal view is really great when it comes to rendering views of database content. Because this is a common task, Django comes with some built-in generic views to help generate a list of objects and detailed views.

Let's start with some examples of displaying a list of objects or individual objects.

We will use the following model:

# models.py
from django.db import models

class Publisher(models.Model):
  name = models.CharField(max_length=30)
  address = models.CharField(max_length=50)
  city = models.CharField(max_length=60)
  state_province = models.CharField(max_length=30)
  country = models.CharField(max_length=50)
  website = models.URLField()

  class Meta:
      ordering = ["-name"]

  def __str__(self):
      return self.name

class Author(models.Model):
  salutation = models.CharField(max_length=10)
  name = models.CharField(max_length=200)
  email = models.EmailField()
  headshot = models.ImageField(upload_to='author_headshots')

  def __str__(self):
      return self.name

class Book(models.Model):
  title = models.CharField(max_length=100)
  authors = models.ManyToManyField('Author')
  publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
  publication_date = models.DateField()

Now we need to define a view:

# views.py
from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
  model = Publisher

Finally, hook the view to your URL:

# urls.py
from django.urls import path
from books.views import PublisherList

urlpatterns = [
  path('publishers/', PublisherList.as_view()),
]

That's all the Python code we need to write. H owever, we still need to write a template. W e can explicitly tell the view which template to use by adding a template_name property to the view, but in the absence of an explicit template, Django infers a template from the object name. In this case, the inferred template will be the books/publisher_list.html-Book section to customize the name of the model's application, while the Publisher bit is a smaller version of the model name.

Note: Therefore, when, for example, the back-end APP_DIRS option Django Templates is set to True in TEMPLATES, the template position can be: /path/to/project/books/templates/books/publisher_list.html

This template is rendered against the context that contains a variable named object_list contains all publisher objects. The template might look like this:

{% extends "base.html" %}

{% block content %}
  <h2>Publishers</h2>
  <ul>
      {% for publisher in object_list %}
          <li>{{ publisher.name }}</li>
      {% endfor %}
  </ul>
{% endblock %}

That's all. A ll the cool features of Universal View come from changing the properties set on Universal View. T he universal view refers to the selection of all detailed common views in the document; The rest of this document considers some common ways you can customize and extend a common view.

Make a "friendly" template context

You may have noticed that our sample publisher list template stores all publishers in a variable named object_list. While this works well, it's not so "friendly" to template authors: they have to "just know" that they're dealing with publishers here.

Well, if you're working with model objects, you're done. W hen you work with objects or query sets, Django can populate the context with a small case of the model class name. In addition object_list the default entry, this entry is provided, but contains exactly the same data, publisher_list.

If you still don't match well, you can manually set the name of the context variable. context_object_name the context variable to use for properties on the general view:

# views.py
from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
  model = Publisher
  context_object_name = 'my_favorite_publishers'

It's always context_object_name good idea to provide something useful and useful. Your colleagues who designed the template will thank you.

Add an additional context

In general, you need to provide some additional information that goes beyond the information provided by the universal view. F or example, consider displaying a list of all books on each publisher details page. This DetailView universal view provides publisher-to-context, but how do we get more information in the template?

The answer is to sub-classize DetailView and provide your own get_context_data method implementation. The default implementation adds the object to be displayed to the template, but you can override it to send more:

from django.views.generic import DetailView
from books.models import Book, Publisher

class PublisherDetail(DetailView):

  model = Publisher

  def get_context_data(self, **kwargs):
      # Call the base implementation first to get a context
      context = super().get_context_data(**kwargs)
      # Add in a QuerySet of all the books
      context['book_list'] = Book.objects.all()
      return context

Note: Typically, get_context_data the context data of all parent classes with the context data of the current class. T o preserve this behavior in your own class to change the context, it is important to ensure that the get_context_data called superseed. T his provides the expected results when no two classes attempt to define the same key. H owever, if any class tries to override the key after the parent class has set the key (after calling the super), all children of the class also need to explicitly set the key after the super to ensure that all parent keys are overwritten. If you're having trouble, check the method resolution order for the view.

Another consideration is that contextual data based on a common view of the class overrides the data provided by the context processor. See get_context_data () example.

View a subset of objects

Now, let's take a closer look at the parameters we've been using in model. T he model parameter specifies a database model that will operate on the view, which can be used for all common views that operate on a single object or collection of objects. However, model parameters are not the only way to specify the object that the view will manipulate - you can also specify a list of objects using the quryset parameter:

from django.views.generic import DetailView
from books.models import Publisher

class PublisherDetail(DetailView):

  context_object_name = 'publisher'
  queryset = Publisher.objects.all()

The designation is short. H owever, by using a filtered list of defined objects, you can learn more about the objects that appear in the view (for more information about objects, see Query for complete details, see Class-based view references). model = Publisher``queryset = Publisher.objects.all()``querysetQuerySet

For example, we might want to order a list of books by publication date, whichever is latest:

from django.views.generic import ListView
from books.models import Book

class BookList(ListView):
  queryset = Book.objects.order_by('-publication_date')
  context_object_name = 'book_list'

This is a very small example, but it's a good illustration of the idea. O f course, usually you don't just need to reorder objects, you need to do more. If you want to display a list of books for a specific publisher, you can use the same technique:

from django.views.generic import ListView
from books.models import Book

class AcmeBookList(ListView):

    context_object_name = 'book_list'
    queryset = Book.objects.filter(publisher__name='ACME Publishing')
    template_name = 'books/acme_list.html'

Note that in addition to the filter, we also use custom template names. If we don't, Universal View will use the same template as the Vanilla object list, which may not be what we want.

Also note that this is not a very elegant way to make publisher-specific books. I f we're adding another publisher page, we need to add a few lines to URLconf, and more than a few publishers become unreasonable. We'll solve this problem in the next section.

Note: If you receive 404 at the time of request, please/books/acme/check to make sure that you actually have a publisher with the name 'ACME Publishing'. The common allow_empty has a parameter for this situation.

Dynamic filtering

Another common requirement is to filter a given object through a key in the URL. We used to hardcode the publisher's name in URLconf, but what if we wanted to write a view to show all the books of any publisher?

Conveniently, our ListView has a method get_queryset () can be overwritten. By default, it returns the value of the quryset property, but we can use it to add more logic.

The key part of doing this is that when you call a class-based view, all kinds of useful things are stored in self, and the request (self.request) includes self.args captured based on URLconf and name-based (self.kwargs) parameters.

Here we have a URLconf that contains a captured group:

# urls.py
from django.urls import path
from books.views import PublisherBookList

urlpatterns = [
    path('books/<publisher>/', PublisherBookList.as_view()),
]

Next, we'll write the PublicbookList view itself:

# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher

class PublisherBookList(ListView):

    template_name = 'books/books_by_publisher.html'

    def get_queryset(self):
        self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher'])
        return Book.objects.filter(publisher=self.publisher)

Using get_queryset to add logic to the query set selection is convenient and powerful. For example, if needed, we can filter using the current user of self.request.user or other more complex logic.

We can also add publishers to the context at the same time, so we can use it in templates:

# ...

def get_context_data(self, **kwargs):
    # Call the base implementation first to get a context
    context = super().get_context_data(**kwargs)
    # Add in the publisher
    context['publisher'] = self.publisher
    return context

Do extra work

The last common pattern we'll see involves doing some extra work before or after calling the universal view.

Imagine if we had last_accessed field on the Auto model to track when anyone last viewed the author:

# models.py
from django.db import models

class Author(models.Model):
    salutation = models.CharField(max_length=10)
    name = models.CharField(max_length=200)
    email = models.EmailField()
    headshot = models.ImageField(upload_to='author_headshots')
    last_accessed = models.DateTimeField()

Of course, generic classes don't know anything about this field, but we can easily write a custom view again to keep the field up to date.

First, we need to add the author detail bit in URLconf to point to the custom view:

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

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

We'll then write a new view - get_object is the method for retrieving the object - so we override it and wrap the call:

from django.utils import timezone
from django.views.generic import DetailView
from books.models import Author

class AuthorDetailView(DetailView):

    queryset = Author.objects.all()

    def get_object(self):
        obj = super().get_object()
        # Record the last accessed date
        obj.last_accessed = timezone.now()
        obj.save()
        return obj

Note: URLconf uses the named group pk here - the name is the default name, and DetailView is used to find the value of the primary key used to filter the query set.

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