Django 类视图学习笔记(三) 内建类视图概览


简介

编写Web程序可能是很单调的,因为视图层,模板层,模型层都可能会有大量逻辑类似的代码,但是我们却需要不断的重复编写它们,这肯定让人受不了。为此,Django在视图层,对经常要编写的,逻辑类似的事务代码进行了抽象,并封装在Django中,这就是内建类视图,之后我们就可以利用内建类视图来进行高效开发。

这些通用的内建类视图可以处理以下事务逻辑:

  • 表示某个对象的列表页面和详情页面。比如博客网站的博文的列表页面和就某一篇博文的详情页面。
  • 基于时期或其它数据对某一类对象进行排序并呈现出来。
  • 给予用户创建、更新、删除某类对象的权限。

以上这些事务基本上是开发人员所遇到的最常见的几类视图。

另外要注意的是,Django的内建类视图虽然很有用,但并不能为我们解决所有问题,使用它们的最常见的一种方法是用我们的类来继承这些内建类视图,然后根据具体问题决定如何去拓展。


使用ListView

以Django内置的ListView为例,讲解如何继承并使用它。

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()

views.py:

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

class PublisherList(ListView):
    model = Publisher

urls.py:

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

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

以上是使用ListView的一个简单示例,我们没有直接使用它,而是使用它的子类PublisherList。ListView主要用于显示数据库中的内容,因此它具有一个很显而易见的属性:model,model用于指定该视图所基于的模型。

template_name

可以看到视图层部分的代码十分简单,ListView还有一个属性:template_name,表示用于呈现前端界面的模板,但在这里没有指定。需要注意的是,如果我们没有指定template_name,那么Django将会自动推断出一个template_name出来,在本例中,它将会推断出template_name为 books/publisher_list.html,其中books为该app的名字,publisher为模型名的小写。

另外,让我们想一想,如果我们使用函数视图来实现这一步功能的话,我们还需要传递一个参数字典过去,但是ListView怎么完成这一步呢?
默认情况下,ListVIew会传递一个object_list到模板层,因此这个模板可能是这样子的:

{% extends "base.html" %}

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

context_object_name

看起来挺好的,但是对于开发前后端分离的项目来说,你向前端传递了一个object_list,前端怎么知道这个object_list到底是哪个object?因此你最好在类视图中定义如下属性:context_object_name,该属性会指定object_list的新名字。

可能如下所示:

views.py:

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

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

DetailView

好了,现在又有新的需求来了。

如果我们想在每一个Publisher的详情页面展示一个书籍的列表,那么该怎么做呢?

还是先思考一下传统思路。如果用视图函数来完成这个功能的话,我们只需要在调用detail.html页面时把book_list传进去就好了,如果用内建视图的话,这需要用到DetailView

如下:

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

class PublisherDetail(DetailView):

    model = Publisher

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['book_list'] = Book.objects.all()
        return context

默认情况下,get_context_data()需要一个self.object参数,但是你可以在你的子类中重写该方法以让它携带更多的数据。在本例中,self.object自然是publisher,但是我们还需要书籍列表,因此重写了它以使它携带了book_list这一参数。


queryset

在上面的例子中,我们用model参数来指定视图将要处理的数据模型,该参数可使用于需要处理单一对象或对象集合的所有视图。但是,除了model参数之外,其实还有类似的参数提供和model等同的功能,即queryset

示例:

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

class PublisherDetail(DetailView):

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

事实上,指定model = Publisher其实就是指定了queryset = Publisher.objects.all(),但是通过使用queryset,我们可以对objects进行进一步的筛选,比如:

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'

动态过滤

你可能注意到,上面的代码有一个很明显的问题,publisher__name被硬指定为ACME Publishing,但其实我们想要的是对于任意的publisher,我们都能得到正确的book_list,这又该怎么做呢?

所幸ListView提供有get_queryset()方法,它返回queryset,我们可以覆写它以便在get_queryset()中添加更多的逻辑。

使用此方法的关键是需要知道,从urlconf来的所有参数,包括request,传入类视图中后都会被存储在self中,比如self.request、self.args、self.kwargs。

示例如下:

urls.py

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

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

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)

如果我们愿意,我们还可以将publisher的信息添加到上下文中,如下:

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)
        
    def get_context_data(self, **kwargs):
	    context = super().get_context_data(**kwargs)
	    context['publisher'] = self.publisher
	    return context

关于内建类视图,这里先说这么多,事实上,Django的内建类视图提供有丰富的功能,关于它们更详尽的介绍将在之后给出。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章