6

理解Django中基于类的视图-创建CBV

 3 years ago
source link: https://www.mindg.cn/?p=2782
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

理解Django中基于类的视图-创建CBV

在本文中,我们将研究如何创建一个基于类的视图,方法是首先实现一个最小的CBV,然后对其进行扩展以突出如何设计CBV以便重用。 本系列的第一篇文章探讨了如何初始化基于类的视图,以及如何使用View类作为基础来设置视图并初步路由逻辑。 由于View本身不能用作独立的CBV,因此我们需要定义其他逻辑以正确处理请求并向用户提供正确的响应。

先决条件
阅读了上一篇文章-View Base Class
基本了解Python中的类继承
最小的CBV
让我们看一下基于类的视图的最小实现。 我们将通过首先导入View基类在views.py文件中创建CBV。 我们的视图BasicView将是一个从View继承的类。

# app-level views.py
from django.views.generic import View
class BasicView(View):

该视图没有任何作用,因此我们需要为其添加功能,以处理至少一种类型的HTTP请求。 让我们添加一个方法来处理GET请求。

from django.shortcuts import render # New
from django.views.generic import View
class BasicView(View):
    def get(self, request, *args, **kwargs):
        return render(request, "basic.html")

我们在类get()上定义了一个新方法,该方法仅返回对新导入的render函数的调用。至此,我们制作了一个功能齐全的视图,该视图将向用户呈现一个模板basic.html。如果我们只需要渲染一个静态页面,该页面已经在模板中包含了所有必要的信息,那么此视图将是完整的。

尽管此视图很少,几乎没有逻辑,但它演示了CBV工作所需的基本组件。 CBV必须至少具有一种与请求中期望的HTTP动词匹配的方法。在此示例中,我们仅期待一个GET请求,该请求导致调用get()方法。如果您不记得如何调用get(),请看一下上一篇文章,特别是dispatch()方法。该视图还需要返回HTTP响应。我们的示例视图使用了渲染快捷方式以在响应中返回模板,但是可以使用HttpResponse或其子类之一。

让我们扩展视图以处理GET和POST请求。

from django.shortcuts import render
from django.views.generic import View
class BasicView(View):
    def get(self, request, *args, **kwargs):
        return render(request, "basic.html")
    def post(self, request, *args, **kwargs):
        # Do something with POST data
        return render(request, "basic_post.html")

现在,当发出POST请求时,将调用post()方法,该方法将在处理POST数据后呈现不同的模板。 我们可以通过在视图中添加适当命名的HTTP请求方法来添加处理任何其他类型请求的逻辑。

到目前为止,对于每种请求类型,我们只使用了一种方法。 调用get()或post()方法,该方法直接返回响应。 但是,这不是必需的,我们可以构造视图,以便通过调用另一个方法然后返回响应来使get()和post()返回。

from django.shortcuts import render
from django.views.generic import View
class BasicView(View):
    def get(self, request, *args, **kwargs):
        return self.return_response(request)
    def post(self, request, *args, **kwargs):
        # Do something with POST data
        return self.return_response(request)
    def return_response(self, request):
        return render(request, "basic.html")

在这里,get()和post()都通过调用return_response()返回,它将返回带有模板的最终响应。如果我们想分离出不同的逻辑,我们还可以在初次调用get()或post()和return_response()之间添加其他中间方法调用。这突显了如何创建基于类的视图的重要部分:CBV的基本组成是调用其他方法直到方法返回响应的方法。

使CBV可重用
为了了解CBV如何用多种方法组成并可以用于多种目的,让我们构建一个稍微复杂的视图。我们将创建Django的基本版本,该版本是基于通用类的ListView内置的(在下一篇文章中将介绍基于通用类的视图)。该视图从数据库中获取对象列表,并将其返回到上下文字典中以在给定模板中显示。假设我们有一个模型Country(乡村),它存储有关一个国家的信息,另一个模型City(城市),它​​包含有关一个城市的信息。

提供所有Country对象列表的CBV看起来像这样:

from django.shortcuts import render
from django.views.generic import View
from .models import Country
class BasicListView(View):
    def get(self, request, *args, **kwargs):
        countries = Country.objects.all()
        context = {"country_list": countries}
        return render(request, "list.html", context)

通过从数据库中检索所有Country对象,将它们存储在上下文中,然后返回带有上下文字典的模板,此视图仅处理GET请求。 我们以一种将视图与“国家/地区”模型紧密联系的方式创建了视图。 在Country.objects.all()中明确声明了Country来获取所有对象实例,我们将该查询集存储在一个名为countrys的变量中,在上下文字典中,我们将该查询集的关键字命名为country_list。 所有这些意味着,如果我们想使用视图来显示其他信息的列表(例如City对象的列表),则该视图不可重用。

使用类属性
通过将对模型的显式引用移出get()方法并进行一些重命名,可以使视图更通用。

from django.shortcuts import render
from django.views.generic import View
from .models import Country
class BasicListView(View):
    model = Country
    def get(self, request, *args, **kwargs):
        objects = self.model.objects.all()
        context = {"object_list": objects}
        return render(request, "list.html", context)

我们定义了一个类属性,模型,并为其分配了国家/地区。 现在,在get()方法中,我们可以通过self.model访问此属性。 通过将queryset和context字典键重命名为更通用的名称,我们使get()方法适用于我们可能希望显示其对象列表的任何模型。

在这一点上,我们重构了视图以使其更通用,但是我们仍然仅使用它来显示Country对象的列表。 那为什么要经历所有这些工作呢?

基于类的视图的主要功能之一是可重用性。 由于类可以从其他类继承,就像我们通过从View类继承来使用BasicListView一样,因此我们可以在其他类中重用一个类的功能。 让我们的观点更加通用

from django.shortcuts import render
from django.views.generic import View
from .models import Country
class BasicListView(View):
    model = None # Remove connection to specific model
    def get(self, request, *args, **kwargs):
        objects = self.model.objects.all()
        context = {"object_list": objects}
        return render(request, "list.html", context)

我们所做的只是将model属性设置为None。 现在,该类不再具有单独的功能,因为它没有从中获取对象列表的模型。 但是我们可以将此视图视为其他视图可以使用的扩展基类。 由于我们已经在此视图中定义了get()逻辑,因此任何从其继承的CBV只需定义一个模型即可正常运行。

from django.shortcuts import render
from django.views.generic import View
from .models import Country, City
class BasicListView(View):
    model = None
    def get(self, request, *args, **kwargs):
        objects = self.model.objects.all()
        context = {"object_list": objects}
        return render(request, "list.html", context)
# View to display a list of countries
class CountryListView(BasicListView):
    model = Country
# View to display a list of cities
class CityListView(BasicListView):
    model = City

我们创建了CountryListView和CityListView,它们都从BasicListView继承(后者又从View继承),并且在其定义中仅包含一行以指示要使用的模型。 当用户请求这些视图时,现在将在BasicListView的get()方法中使用正确的模型。

由于CountryListView和CityListView仅需要定义所使用的模型,因此实际上存在另一种方式,我们可以获取这些视图提供的相同功能,而无需单独定义它们。 如果回顾一下View类,我们知道可以在URLconf的.as_view()方法调用中使用关键字参数。 这些参数然后用于在视图上设置类属性。 由于model是类属性,并且是CountryListView和CityListView定义的唯一特定逻辑,因此我们甚至不需要定义这两个视图,而是可以将BasicListView与model一起用作关键字参数。

# urls.py
from django.urls import path
from .views import BasicListView
from .models import Country, City
urlpatterns = [
    path("country-list/", BasicListView.as_view(model=Country), name="country_list"),
    path("city-list/", BasicListView.as_view(model=City), name="city_list")

通过将模型作为关键字参数传递,我们现在具有相同的功能,而无需为要显示为列表的每个模型定义特定的视图。 这是使用CBV并利用其可重用性和灵活性的有效方法,但是在使用.as_view()和关键字参数创建新视图之前,也应考虑代码的可读性。 如果以后需要重新访问代码,我们是否还记得我们使用关键字参数而不是显式定义视图? 也许更重要的是,如果我们想向基类中不可用的视图之一添加更具体的逻辑该怎么办?

添加更多方法
第二个问题使我们着眼于如何创建CBV,这种方式允许我们添加特定逻辑而无需重写整个视图。 让我们回顾一下BasicListView。

from django.shortcuts import render
from django.views.generic import View
from .models import Country
class BasicListView(View):
    model = None
    def get(self, request, *args, **kwargs):
        objects = self.model.objects.all()
        context = {"object_list": objects}
        return render(request, "list.html", context)

除了能够定义从其继承的新视图并定义要使用的模型之外,我们除了显示所有对象的列表之外还不能做其他事情。 如果只想显示对象子集的列表或对所有对象进行某种转换,然后再将它们添加到上下文,则必须覆盖get()方法,这意味着我们基本上是在创建一个全新的视图。

为了避免仅为了添加某些功能而完全重写视图,我们可以将视图逻辑分解为单独的方法。 然后,我们只需要覆盖我们要更改的方法。 这是我们可以更改BasicListView以允许我们更改将哪些数据添加到上下文的方法。

from django.shortcuts import render
from django.views.generic import View
from .models import Country, City
class BasicListView(View):
    model = None
    template_name = "list.html" # Moved the template to an attribute
    def get(self, request, *args, **kwargs):
        objects = self.get_object_list() # Objects now come from another method  
        context = {"object_list": objects}
        return render(request, self.template_name, context)
    # Method to get objects
    def get_object_list(self):
        objects = self.model.objects.all()
        return objects

我们在这里做了几件事。 首先,为了使视图更加通用,我们从呈现函数中显式命名的模板中删除了该模板。 这意味着如果要使用其他模板,我们可以更轻松地覆盖它。 然后,我们创建了一个新方法get_object_list(),并将逻辑移到了先前在get()中的位置以检索对象列表。 与之前一样,这些对象将被返回,然后分配给上下文并在渲染中使用。

可以通过多种方式使用BasicListView的此新实现。 我们可以更改模型,并在模板中提供所有模型实例的不同列表,或者可以定义完全不同的查询集,并使我们选择从数据库中提取的任何对象列表可用。

例如,如果我们希望有一个视图来列出所有国家,但有一个单独的视图仅列出以“镇”结尾的城市,则我们的视图应如下所示:

from django.shortcuts import render
from django.views.generic import View
from .models import Country, City
class BasicListView(View):
    model = None
    template_name = "list.html"
    def get(self, request, *args, **kwargs):
        objects = self.get_object_list()
        context = {"object_list": objects}
        return render(request, self.template_name, context)
    def get_object_list(self):
        objects = self.model.objects.all()
        return objects
class CountryListView(BasicListView):
    model = Country
class CityListView(BasicListView):
    model = City
    def get_object_list(self):
        objects = self.model.objects.filter(name__endswith="town")
        return objects

在CityListView中,要获取特定的对象列表,我们要做的就是覆盖get_object_list()进行所需的特定更改,而不必完全覆盖所有的get()和重复很多代码。

我们可以通过创建定义查询集以获取对象的属性,而不是定义单独的方法,来实现BasicListView的不同方式。 然后,我们只需要定义属性,而不用覆盖方法。

from django.shortcuts import render
from django.views.generic import View
from .models import Country, City
class BasicListView(View):
    model = None
    template_name = "list.html"
    objects = None  # Objects in now an attribute
    def get(self, request, *args, **kwargs):
        context = {"object_list": self.objects}
        return render(request, self.template_name, context)
class CountryListView(BasicListView):
    model = Country
    objects = model.objects.all()
class CityListView(BasicListView):
    model = City
    objects = model.objects.filter(name__endswith="town")

这带来了一个折衷,即在将对象列表添加到上下文字典之前我们将无法对其进行任何额外的处理,现在我们需要为每个子类定义对象。

另外,我们可以将视图分解为更多的方法以允许更具体的自定义。

from django.shortcuts import render
from django.views.generic import View
from .models import Country, City
class BasicListView(View):
    model = None
    template_name = "list.html"
    def get(self, request, *args, **kwargs):
        objects = self.get_object_list()
        context = self.get_context_data(objects)
        return render(request, self.template_name, context)
    def get_object_list(self):
        objects = self.model.objects.all()
        return objects
    def get_context_data(self, objects):
        return {"object_list": objects}
class CountryListView(BasicListView):
    model = Country
class CityListView(BasicListView):
    model = City
    def get_object_list(self):
        objects = self.model.objects.filter(name__endswith="town")
        return objects
    def get_context_data(self, objects):
        context = super().get_context_data(objects)
        context.update({"extra_context": "Some extra data"})
        return context

我们添加了方法get_context_data(),该方法获取对象列表并将其返回到字典中。最初,此方法似乎没有做任何需要将其分开的重要事情。但是通过突破它,我们可以包含其他逻辑,以便在需要时向上下文字典添加更多数据。在CityListView中,我们使用了get_context_data()提供的挂钩将数据添加到上下文中。我们首先使用super()来调用BasicListView中定义的get_context_data()的原始版本。这将返回上下文的原始数据。然后,我们可以更新上下文以包含更多数据,然后再将其返回给get()。

至此,我们开始使BasicListView的初始版本更加复杂。可以添加更多的属性或方法,以使视图更加可重用并添加不同的功能,例如分页。幸运的是,Django随附了一些内置的基于类的通用视图,这些视图为我们实现了这种逻辑。这些视图具有可以被重写以自定义视图的属性和方法,就像我们在本文的示例视图中所做的一样。此外,即使这些视图执行不同的功能,mixin也可以用于在视图之间共享逻辑。在本系列的后续文章中,我们将介绍基于类的通用视图和混合模块。

概要
基于类的视图根据请求类型执行操作,根据可重用性的需要,它可以根据需要简单或复杂。

CBV需要从View基类继承
CBV的基本组成是调用其他方法直到方法返回响应的方法
CBV需要定义一些方法来处理用于访问视图的预期HTTP请求方法
通过定义类属性或将视图逻辑分解为可以覆盖的单独方法,可以使CBV更具可重用性

原文:https://www.brennantymrak.com/articles/comprehending-class-based-views-creating-cbvs

本条目发布于2020-10-20。属于Django分类,被贴了 Django 标签。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK