Django 类视图基类

66 阅读10分钟

类视图基类

除去默认的CBV之外,在此基础上,django还额外提供了更加便捷可以使用的视图基类,具备了基本的获取、添加、删除、修改等操作

基本视图

TemplateView

专注于返回一个模版页面

dispatch()
http_method_not_allowed()
get_context_data()
class TemplateView(TemplateResponseMixin, ContextMixin, View):
    """
    Render a template. Pass keyword arguments from the URLconf to the context.
    """
    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)

ContextMixin:用以传递上下文变量,只要通过内部的extra_context变量

TemplateResponseMixin:通过给定的上下文字典数据、模版配置、content-type等属性组合在一起,返回关于一个模版页面的response

class TemplateResponseMixin:
    template_name = None	# 模版的名字
    template_engine = None # 模版的引擎
    response_class = TemplateResponse	# render_to_response返回的响应类
    content_type = None # 响应的http类型
  • 基础使用方式,直接在路由处指定名字
path('',TemplateView.as_view(template_name='index.html',extra_context={'title':'测试'}))
  • 覆写ContextMixin->get_context_data函数
class TemplateViewTest(TemplateView):
    template_name = 'index.html'
    def get_context_data(self, **kwargs):
        if 'view' not in kwargs:
            kwargs['view'] = self
        print(kwargs)
        if self.extra_context is not None:
            kwargs.update(self.extra_context)
        kwargs['data'] = [1,2,3,4,5]
        return kwargs

通过重定义get_context_data函数的方式,可以在模版返回时传递自定义上下文变量


RedirectView

这个视图专注于实现重定向到指定url的功能

dispatch()
http_method_not_allowed()
get_redirect_url()
  • 这是这个视图的源码,非常简单
class RedirectView(View):
    """Provide a redirect on any GET request."""
    permanent = False # 是否为永久重定向,可由pattern_name与get_redirect_url()替代
    url = None # 要跳转的地址
    pattern_name = None # 反向解析的路由命名
    query_string = False # 是否传递get传参
def get_redirect_url(self, *args, **kwargs):
    """
    Return the URL redirect to. Keyword arguments from the URL pattern
    match generating the redirect request are provided as kwargs to this
    method.
    """
    if self.url:
    	url = self.url % kwargs
    elif self.pattern_name:
    	url = reverse(self.pattern_name, args=args, kwargs=kwargs)
    else:
    	return None
    args = self.request.META.get('QUERY_STRING', '')
    if args and self.query_string:
    	url = "%s?%s" % (url, args)
    return url
  • 一般情况下可以通过重构get_redirect_url方法,进行重定向之前的工作,比如访问记录+1等等
def get_redirect_url(self, *args, **kwargs):
	# do something
	return super().get_redirect_url(*args, **kwargs)

通用显示视图

ListView

显示一个结果集数据

dispatch()
http_method_not_allowed()
get_template_names()
get_queryset()
get_context_object_name()
get_context_data()
get()
render_to_response()
  • 这是这个视图的源码以及一些相应属性
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
    """
    Render some list of objects, set by `self.model` or `self.queryset`.
    `self.queryset` can actually be any iterable of items, not just a queryset.
    """
    model = models.User # 重写model类属性,指定模型的列表
    template_name = 'userlist.html' # 指定这个列表的模板。
    context_object_name = 'userdata' # 指定这个列表模型在模板中的参数名称。
    ordering = 'create_time'   # 以时间进行排序展示
    # -----------------------------------------------------------
    paginate_by = 10  # 每一页需要展示多少条数据
    page_kwarg = 'p'   # 指定换页面的参数 默认为page 以GET的方式传递参数
  • MultipleObjectTemplateResponseMixin:重写了get_template_names方法,改写了获取模版文件的路径名,默认为app_name/model_name_list.html

  • BaseListView:定义get方法,提供给dispatch方法

可以重写BaseListView->MultipleObjectMixin->get_context_data函数进行自定义上下文数据

可以重写BaseListView->MultipleObjectMixin->get_queryset对视图所返回的数据进行过滤定义

  • 这是一个简单的demo来举例说明两个函数的用法
class ListTest(ListView):
    model = User
   	template_name = 'index.html'
    context_object_name = 'users'
	def get_context_data(self, **kwargs): # 获取上下文的数据。并且可以添加自己的参数
    	context['username'] = 'test'
    	return context
  	def get_queryset(self):
    	if self.model is not None:
      	queryset = self.model._default_manager.all()
        ordering = self.get_ordering()
		if ordering:
			if isinstance(ordering, str):
				ordering = (ordering,)
				queryset = queryset.order_by(*ordering)
		return queryset

DetailView

返回单个对象的数据

dispatch()
http_method_not_allowed()
get_template_names()
get_slug_field()
get_queryset()
get_object()
get_context_object_name()
get_context_data()
get()
render_to_response()
  • 它里面比较重要的一个继承父类,在这个父类中,指明了当前DetailViewu所需要的一些属性,以及一个非常重要的函数get_object,这个函数将会帮助我们返回一个具体要显示的数据
class SingleObjectMixin(ContextMixin):
    """
    Provide the ability to retrieve a single object for further manipulation.
    """
    model = None # 数据模型,将在视图页面展示数据
    queryset = None # queryset如果提供,则值queryset取代设置model的值
    slug_field = 'slug' # 过滤的字段
    context_object_name = None
    slug_url_kwarg = 'slug' # 过滤条件,通过链接参数给定;如 a.com/<str:slug>/
    pk_url_kwarg = 'pk' # 启用主键作为链接参数过滤条件
    query_pk_and_slug = False # 当依赖主键查询不为空时,设置该值为True,可以继续过滤字段,就是又过滤pk还过滤field
    def get_object(self, queryset=None):
      	pass
  • 如果现在想要根据用户名获取具体的某个用户数据,那么可以定义这样一个视图
class DetailTest(DetailView):
    model = User
    slug_field = 'name' # 这是我要查询的字段
    context_object_name = 'user'
    slug_url_kwarg = 'name' 
    # 这个视图会通过这里定义的这个字段中去链接中匹配查找符合的数据,用来配合slug_field做查询条件
    query_pk_and_slug = True
	# 默认数据的返回模版页面定义规则如下:appname/modelname_detail.html
  • 这样定义好一个简单的视图后,我们还需要路由匹配部分进行配合
path('<str:name>/',DetailTest.as_view()) # 这里是传递到DetailView视图用来过滤的条件值
  • 如果需要自定义捕捉链接参数指明多个链式过滤条件,可以通过重写get_object方法,比如需要通过关联外键与数据本体查找某个你想要的数据,那么可以这样
class DetailTest(DetailView): 
    model = Info
    context_object_name = 'info'
   	def get_object(self,queryset=None):
      	some_id = self.kwargs.get('some_id') # a.com/<int:some_id>/<str:other>
        other = self.kwargs.get('other') # a.com/<int:some_id>/<str:other>/
        try:
            obj = self.model._default_manager.get(some_id=some_id,other=other)
        except:
            obj = None
        return obj
   	def get_context_data(self,**kwargs):
    	# 在这里可以通过context额外设置上下文
    	pass
  • 配合的路由将会是这样的
path('<int:some_id>/<str:other>/',DetailTest.as_view()),

通用编辑视图

FormView

这是用来处理表单的视图;出错时,显示出关于表单验证的错误;如果成功,则跳转到新的url

class FormMixin(ContextMixin):
    """Provide a way to show and handle a form in a request."""
    initial = {} # 初始化默认值
    form_class = None # 某个表单类
    success_url = None # 验证成功之后跳转的地址
    prefix = None # 控制在表单页面中的name值前缀,用以区分不同表单的name属性
  • FormView有一个非常重要的父类,用来控制分析表单
class ProcessFormView(View):
    """Render a form on GET and processes it on POST."""
    def get(self, request, *args, **kwargs):
        """Handle GET requests: instantiate a blank version of the form."""
        return self.render_to_response(self.get_context_data())

    def post(self, request, *args, **kwargs):
        """
        Handle POST requests: instantiate a form instance with the passed
        POST variables and then check if it's valid.
        """
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)
class FormMixin(ContextMixin):
  	def get_form_class(self):
        """Return the form class to use."""
        return self.form_class

    def get_form(self, form_class=None):
        """Return an instance of the form to be used in this view."""
        if form_class is None:
            form_class = self.get_form_class()
        return form_class(**self.get_form_kwargs())

在这个类中定义了主要的post请求,用来分析form表单是否是正确的输入,有关的self.form_validself.form_invalid函数定义如下,还是非常简单的,出错的话,会通过get_context_data将错误返回,成功的话将会通过get_success_url重定向

class FormMixin(ContextMixin):
    def form_invalid(self, form):
        """If the form is invalid, render the invalid form."""
        return self.render_to_response(self.get_context_data(form=form))
    def form_valid(self, form):
        """If the form is valid, redirect to the supplied URL."""
        return HttpResponseRedirect(self.get_success_url())
  • 来做一个简单的测试,使用FormView进行表单页面的提供以及数据的验证入库
#forms 
class UserForm(forms.Form):
    name = forms.CharField(max_length=30)
#views
class FormViewTest(FormView):
    model = User # 添加model全局属性,以供代码整洁良好复用
    template_name = 'test_form.html' # 因为FormView并没有自动提供模版后缀,需要我们手动指定
    form_class = UserForm # 表单页面渲染的表单类
    success_url = '/' # 表单验证成功之后的重定向地址
    prefix = 'a1' # 在表单页面的name属性值前缀,一般不要重复即可

    def post(self, request, *args, **kwargs): # 重写post方法,因为我们要数据入库
        form = self.get_form()
        if form.is_valid():
            name = form.cleaned_data['name']
            self.model._default_manager.create(name=name) # 验证之后的数据入库
            return self.form_valid(form)
        else:
            return self.form_invalid(form)
  • 需要配合的表单页面就更加简单了
<!-- test_form.html -->
<form action="" method="POST">
    {% csrf_token %}
    {{ form }}
    <button type="submit">提交</button>
</form>
  • 注意:如果在继承类中使用success_url属性,进行反向解析路由命名,要使用reverse_lazy而非reverse,因为success_url属性为类属性,而Django的urlconf加载在所有类加载之后,而reverse_lazy可以让success_url的加载在路由被访问时。

CreateView

快速创建某个模型数据

因为继承父类太多了,简单一些,直接列举了他一些设置属性

class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
    """
    View for creating a new object, with a response rendered by a template.
    """
    template_name_suffix = '_form'
    # 这个视图默认的表单页地址是:appname/modelname_from.html
    #---ModelFormMixin---
    fields = [] # 模型类中需要表单提供的字段值
    #---FormMixin---
    initial = {} # 初始化默认值
    form_class = None # 某个表单类
    success_url = None # 验证成功之后跳转的地址
    prefix = None # 控制在表单页面中的name值前缀,用以区分不同表单的name属性
    #---SingleObjectMixin---
    model = None # 需要表单创建的模型类
  • 在ModelFormMixin这个类中重写了form_valid函数,不光进行了数据校验,还进行了数据入库
class ModelFormMixin(FormMixin, SingleObjectMixin):
    def form_valid(self, form):
    	"""If the form is valid, save the associated model."""
        self.object = form.save()
    	return super().form_valid(form)

在ModelFormMixin类中还使用了modelform_factory函数,这是为什么我们不需要在这个视图类中进行表单类的定义

可以单独使用 modelform_factory() 函数来直接从 Model 创建表单。在不需要很多自定义的情况下是更方便的,比如

UserForm = modelform_factory(User, fields=("name",))
  • 列举一个使用示例
class CreateTest(CreateView):
    model = Info
    fields = ['user','info']
    success_url = '/' # 数据创建成功之后的跳转地址
<form action="" method="post">
    {% csrf_token %}
    {{ form }}
    <button type="submit">提交</button>
</form>

字段中的多对一,多对多关系会自动成为下拉菜单或复选框的样式供你使用,非常的简单!


UpdateView

更新某个数据的视图类,更新通过链接传递的pk值,或者对应的字段过滤条件,参见DetailView

class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):
    """View for updating an object, with a response rendered by a template."""
    template_name_suffix = '_form'
   	# 默认的表单页面后缀,appname/modelname_form.html
    #---FormMixin---
    initial = {} # 初始化默认值
    form_class = None # 某个表单类
    success_url = None # 验证成功之后跳转的地址
    prefix = None # 控制在表单页面中的name值前缀,用以区分不同表单的name属性
    #---SingleObjectMixin---
    model = None # 数据模型,将在视图页面展示数据
    queryset = None # queryset如果提供,则值queryset取代设置model的值
    slug_field = 'slug' # 过滤的字段
    context_object_name = None
    slug_url_kwarg = 'slug' # 过滤条件,通过链接参数给定;如 a.com/<str:slug>/
    pk_url_kwarg = 'pk' # 启用主键作为链接参数过滤条件
    query_pk_and_slug = False # 当依赖主键查询不为空时,设置该值为True,可以继续过滤字段,就是又过滤pk还过滤field

使用UpdateView,首先要通过路由传递对应的pk或者指明的slug_url_kwarg字段确定要更新的数据是哪个,然后进行更新,更新完毕会跳转到由success_url定义的路由之中。

  • 看个demo,比如我想更新张三
class UpdateTest(UpdateView):
    model = Info
    fields = ['id','info','user']
    success_url = '/'
    slug_field = 'user__name' 
    # 外键关联关系可以通过链式查询给定
    slug_url_kwarg = 'user_name'
    # 在链接中的给定传递给user__name的形参名
path('update/<str:user_name>/',UpdateTest.as_view())
# 对应上方的user_name

DeleteView

删除self.get_object()获取到的数据对象

class DeleteView(SingleObjectTemplateResponseMixin, BaseDeleteView):
    """
    View for deleting an object retrieved with self.get_object(), with a
    response rendered by a template.
    """
    template_name_suffix = '_confirm_delete'
    # 默认模版的路径是appname/modelname_confirm_delete.html
    #---SingleObjectMixin---
    model = None # 数据模型,将在视图页面展示数据
    queryset = None # queryset如果提供,则值queryset取代设置model的值
    slug_field = 'slug' # 过滤的字段
    context_object_name = None
    slug_url_kwarg = 'slug' # 过滤条件,通过链接参数给定;如 a.com/<str:slug>/
    pk_url_kwarg = 'pk' # 启用主键作为链接参数过滤条件
    query_pk_and_slug = False # 当依赖主键查询不为空时,设置该值为True,可以继续过滤字段,就是又过滤pk还过滤field
  • get方式访问时,会先返回符合匹配条件的数据对象,默认的模版上下文名为object

  • post、delete方式访问时,会将所匹配的数据对象删除

class DeleteTest(DeleteView):
    model = Info
    slug_url_kwarg = 'user_name'
    slug_field = 'user__name' # user为外键,根据外键的链式查询进行筛选
    success_url = '/'
    context_object_name = 'info' # 当get访问时,模版页面渲染时的上下文变量名
path('delete/<str:user_name>/',DeleteTest.as_view()),