Django 中类视图详解

在写视图的时候,Django除了使用函数作为视图,也可以使用类作为视图。使用类视图可以使用类的一些特性,比如继承等。

View视图:

django.views.generic.base.View是主要的类视图,所有的类视图都是继承自他。如果我们写自己的类视图,也可以继承自他。然后再根据当前请求的method,来实现不同的方法。

例如:这个视图只能使用get的方式来请求,那么就可以在这个类中定义get(self,request,*args,**kwargs)方法。
在views.py中新建一个类视图

from django.http import HttpResponse
from django.views.generic import View

class BookListView(View):
    def get(self,request,*args,**kwargs):
        return HttpResponse('book list view')

这样就只能使用get方法来访问这个类视图了。
要想访问我们的类视图,我们还需要配置url,添加映射,而类视图不能像我们访问视图函数那样进行映射,还需要使用as_view()方法才能进行映射。

path('get',views.BookListView.as_view()),

将上面代码添加至urls中就能成功的进行映射了。

以此类推,如果只需要实现post方法,那么就只需要在类中实现post(self,request,*args,**kwargs)。

from django.http import HttpResponse
from django.views.generic import View

class BookListView(View):
    def post(self,request,*args,**kwargs):
        return HttpResponse('book list view')

接下来我们实现一个需求:我们需要获取到前端传入的值,如果用户是使用GET方法访问的我们定义的视图函数,那么我们就返回一个html页面让他输入值,如果是使用POST方法,我们就对获取的值进行处理。

首先在templates下面新建一个add_book.html的文件,写入代码:

<form action="" method="post">
    <table>
        <tbody>
            <tr>
                <td>图书名字:</td>
                <td><input type="text" name="name"></td>
            </tr>
            <tr>
                <td>作者:</td>
                <td><input type="text" name="author"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="提交"></td>
            </tr>
        </tbody>
    </table>
</form>

然后在views中新建一个类视图接收数据:

class AddBookView(View):
    def get(self,request,*args,**kwargs):
        return render(request,'add_book.html')

    def post(self,request,*args,**kwargs):
        book_name = request.POST.get('name')
        book_author = request.POST.get('author')
        print('name:{},author:{}'.format(book_name,book_author))
        return HttpResponse('success')

在urls中添加映射

path('add',views.AddBookView.as_view()),

然后在浏览器中输入网址,写入信息,就能在控制台打印出获取的信息了。

除了getpost方法,View还支持以下方法['get','post','put','patch','delete','head','options','trace']。

如果用户访问了View中没有定义的方法。比如你的类视图只支持get方法,而出现了post方法,那么就会把这个请求转发给http_method_not_allowed(request,*args,**kwargs)

示例:
新建一个BookDetaial的类:

class BookDetaialView(View):
    def post(self,request,book_id):
        return HttpResponse('图书id是:{}'.format(book_id))
    def http_method_not_allowed(self, request, *args, **kwargs):
        return HttpResponse('你使用的是%s请求,但是不支持POST以外的其他请求!'%request.method)

然后在urls中添加映射:

path('detaial/<int:book_id>',views.BookDetaialView.as_view()),

当我们输入网址进行访问的时候,就会返回一个字符窜:你使用的是GET请求,但是不支持POST以外的其他请求!

其实不管是get请求还是post请求,都会走django.views.generic.base.Viewdispatch(request,*args,**kwargs)方法,所以如果实现这个方法,将能够对所有请求都处理到。

TemplateView视图:

django.views.generic.base.TemplateView,这个类视图是专门用来返回模版的。在这个类中,有两个属性是经常需要用到的,一个是template_name,这个属性是用来存储模版的路径,TemplateView会自动的渲染这个变量指向的模版。另外一个是get_context_data,这个方法是用来返回上下文数据的,也就是在给模版传的参数的。

需求:在一个网站中,有一些页面不需要我们从数据库中提取数据到前端页面中,例如网址中的“关于我们” 这个页面一般都是在html中写死的数据,不需要进行改动,这个时候我们就可以直接在urls中直接渲染html文件,而不用视图函数或者视图类来进行渲染。

新建一个about.html的文件,

<body>
    这是关于我们的页面
</body>

然后直接在urls中渲染这个模板:

from django.views.generic import TemplateView

# 如果渲染的模板不需要传递任何参数,那么建议在urls中使用TemplateView直接进行渲染
path('about/',TemplateView.as_view(template_name='about.html')),

而我们又想使用这个TemplateView进行渲染模板,又想传递少许参数,这个时候我们就可以定义一个类,继承至TemplateView

示例:

在views中新建一个类继承至TemplateView

from django.views.generic import View,TemplateView
class AboutView(TemplateView):
	# 指定模板的路径
    template_name = 'about.html'
    
	# 传入的参数,数据
    def get_context_data(self,**kwargs):
        context  ={'phone':'xxx-xxxxxxx'}
        return context

我们也需要在about.html文件中接收传入的数据

<body>
    这是关于我们的页面{{ phone }}
</body>

然后将上面的映射注释掉,添加新的映射

    path('about/',views.AboutView.as_view()),

这里我们就传递了一个参数phone过去。

ListView:

在网站开发中,经常会出现需要列出某个表中的一些数据作为列表展示出来。比如文章列表,图书列表等等。在Django中可以使用ListView来帮我们快速实现这种需求。

因为要对表中的数据进行操作,那么我们首先的创建一个模型,然后映射到数据库中去。
models.py中写入代码:

from django.db import models

# Create your models here.

class Article(models.Model):
    title = models.CharField(max_length=100);
    content = models.TextField()
    create_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'listView_article'

如果你的models在某个app下面,那么先将app添加至settings.py中,然后配置数据库连接MySQL数据库,当然不配置也可以,使用默认的SQLite数据库也可以。
如果models.py没有在某个app下面,那么就不用进行添加app这一步骤了,直接配置数据库就行了。

然后执行makenigrations和migrate。将模型映射到数据库中。为了方便我们进行测试,首先我们先来进行批量添加数据:
在views中新家一个函数视图:用来批量添加数据。

# 添加测试数据
from . import models

def addInfo(request):
    articles = []
    for x in range(0,101):
        article = models.Article(title='标题:%s'%x,content='内容:%s'%x)
        articles.append(article)

    models.Article.objects.bulk_create(articles)  #批量添加数据
    return HttpResponse('success')

添加映射

path('addInfo/',views.addInfo),

输入网址,执行视图函数,我们就添加了101个数据到数据库中了。

然后我们新建一个article_list.html的文件,写入代码:

<ul>
    {% for article in articles %}
        <li>{{ article.title }}</li>
    {% endfor %}
</ul>

接下来我们新建一个类视图:

class ArticleListView(ListView):
    model = models.Article # 重写model类属性,指定模型的列表
    template_name = 'article_list.html' # 指定这个列表的模板。
    context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
    paginate_by = 10  # 每一页需要展示多少条数据
    ordering = 'create_time'   # 以时间进行排序展示

然后添加映射

path('list/',views.ArticleListView.as_view()),

输入网址,就能查看效果了,并且当前页面只有10条数据。
而如果我们向访问后面的页面,我们只需要在输入的网址后面以GET的方式添加一个page=x的参数就行了

例如:我的网址是

http://127.0.0.1:8000/class/list/

那么我想访问第二页,我就应该这样输入网址

http://127.0.0.1:8000/class/list/?page=2

以此类推,后面的也是一样的了,因为我们只添加了101条数据,所以只有11页,而我们如果让page=12的话,就会找不到页面。如果我们在网址后面没有传递page参数,默认返回的就是第一页。

而如果我们不想使用page作为参数,而是换一个p作为参数,那么就可以使用page_kwarg这个属性了
示例:将默认参数page改为p

class ArticleListView(ListView):
    model = models.Article # 重写model类属性,指定模型的列表
    template_name = 'article_list.html' # 指定这个列表的模板。
    context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
    paginate_by = 10  # 每一页需要展示多少条数据
    ordering = 'create_time'   # 以时间进行排序展示
    page_kwarg = 'p'   # 指定换页面的参数 默认为page 以GET的方式传递参数

这样,我们就不能使用page作为参数了,而是只能使用p作为参数了。

上面演示的是常用属性的用法,而我们还有两个比较常用的方法。
接下来我们就将演示这两种方法。

  1. get_context_data:获取上下文的数据。并且可以添加自己的参数,例如下面的username
    在上面定义的类中添加这个方法
class ArticleListView(ListView):
    model = models.Article # 重写model类属性,指定模型的列表
    template_name = 'article_list.html' # 指定这个列表的模板。
    context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
    paginate_by = 10  # 每一页需要展示多少条数据
    ordering = 'create_time'   # 以时间进行排序展示
    page_kwarg = 'p'   # 指定换页面的参数 默认为page 以GET的方式传递参数

    # 重写父类的get_context_data方法,添加自己的参数
    def get_context_data(self,**kwargs):
    	# 我们需要先继承至父模板的get_context_data方法,否则我们有很多方法将不能使用
        context = super(ArticleListView,self).get_context_data(**kwargs)
        # 然后添加自定义的参数
        context['username'] = 'zhiliao'
        print(context)

我们就能在控制台看到打印出来的东西了。
里面包含了很多东西,有我们常用的paginator类、page_obj类和我们自定义的一些参数等。
(paginator类、page_obj类下面将会进行讲解)

  1. get_queryset:如果你提取数据的时候,并不是要把所有数据都返回,那么你可以重写这个方法。将一些不需要展示的数据给过滤掉。
    def get_context_data(self,**kwargs):
        # 我们需要先继承至父模板的get_context_data方法,否则我们有很多方法将不能使用
        context = super(ArticleListView,self).get_context_data(**kwargs)
        # 然后添加自定义的参数
        context['username'] = 'zhiliao'
        print(context)
        return context

    # 设置需要返回的数据
    def get_queryset(self):
        # 没有重写方法默认返回所有的数据
        # return models.Article.objects.all()
        return models.Article.objects.filter(id__lte=93)

注意: 在这里我们将id>93的数据过滤掉了,所以现在的页面只有10页了。所以传入的参数page不能大于10。

上面类中的属性和方法说明:

  1. model:重写model类属性,指定这个列表是给哪个模型的。
  2. template_name:指定这个列表的模板。
  3. paginate_by:指定这个列表一页中展示多少条数据。
  4. context_object_name:指定这个列表模型在模板中的参数名称。
  5. ordering:指定这个列表的排序方式。
  6. page_kwarg:获取第几页的数据的参数名称。默认是page。
  7. get_context_data:获取上下文的数据。
  8. get_queryset:如果你提取数据的时候,并不是要把所有数据都返回,那么你可以重写这个方法。将一些不需要展示的数据给过滤掉。

Paginator和Page类:

PaginatorPage类都是用来做分页的。他们在Django中的路径为django.core.paginator.Paginatordjango.core.paginator.Page

Paginator常用属性和方法:

  1. count:总共有多少条数据。
  2. num_pages:总共有多少页。
  3. page_range:页面的区间。比如有三页,那么就range(1,4)。

示例代码:修改上面定义的类的代码:

class ArticleListView(ListView):
    model = models.Article # 重写model类属性,指定模型的列表
    template_name = 'article_list.html' # 指定这个列表的模板。
    context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
    paginate_by = 10  # 每一页需要展示多少条数据
    ordering = 'create_time'   # 以时间进行排序展示
    page_kwarg = 'p'   # 指定换页面的参数 默认为page 以GET的方式传递参数

    # 重写父类的get_context_data方法,添加自己的参数
    def get_context_data(self,**kwargs):
        # 我们需要先继承至父模板的get_context_data方法,否则我们有很多方法将不能使用
        context = super(ArticleListView,self).get_context_data(**kwargs)
        # 然后添加自定义的参数
        context['username'] = 'zhiliao'
        # print(context)
        paginator = context.get('paginator')  # paginator类

        print(paginator.count)  # 获取数据的个数
        # print(paginator.num_pages)  # 获取页数
        # print(paginator.page_range) #或缺页数的范围,要前不要后

        return context

    # 设置需要返回的数据
    # def get_queryset(self):
    #     # 没有重写方法默认返回所有的数据
    #     # return models.Article.objects.all()
    #     return models.Article.objects.filter(id__lte=89)

然后可自行修改代码查看对应效果。

Page类:

page类的常用属性和方法:

  1. has_next:是否还有下一页。
  2. has_previous:是否还有上一页。
  3. next_page_number:下一页的页码。
  4. previous_page_number:上一页的页码。
  5. number:当前页。 只有这一个是属性,其他的都是方法
  6. start_index:当前这一页的第一条数据的索引值。
  7. end_index:当前这一页的最后一条数据的索引值。

示例代码:

class ArticleListView(ListView):
    model = models.Article # 重写model类属性,指定模型的列表
    template_name = 'article_list.html' # 指定这个列表的模板。
    context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
    paginate_by = 10  # 每一页需要展示多少条数据
    ordering = 'create_time'   # 以时间进行排序展示
    page_kwarg = 'p'   # 指定换页面的参数 默认为page 以GET的方式传递参数

    # 重写父类的get_context_data方法,添加自己的参数
    def get_context_data(self,**kwargs):
        # 我们需要先继承至父模板的get_context_data方法,否则我们有很多方法将不能使用
        context = super(ArticleListView,self).get_context_data(**kwargs)
        # 然后添加自定义的参数
        context['username'] = 'zhiliao'
        # print(context)
        paginator = context.get('paginator')  # paginator类
        page_obj = context.get('page_obj')   # page_obj类

        # print(paginator.count)  # 获取数据的个数
        # print(paginator.num_pages)  # 获取页数
        # print(paginator.page_range) #或缺页数的范围,要前不要后

        print(page_obj.number)
        print(page_obj.start_index())
        print(page_obj.end_index())
        print(page_obj.has_next())
        print(page_obj.has_previous())
        print(page_obj.next_page_number())
        print(page_obj.previous_page_number())
        return context

    # 设置需要返回的数据
    # def get_queryset(self):
    #     # 没有重写方法默认返回所有的数据
    #     # return models.Article.objects.all()
    #     return models.Article.objects.filter(id__lte=89)

给类视图添加装饰器:

在开发中,有时候需要给一些视图添加装饰器。如果用函数视图那么非常简单,只要在函数的上面写上装饰器就可以了。但是如果想要给类添加装饰器,那么可以通过以下两种方式来实现:

需求:在访问个人中心页面的时候,如果没有登录,我们就让它跳转到登录页面,登录之后才能访问个人中心。

这里我们就是用get请求来模拟是否登录了,即如果我们在访问网址的时候使用get方法传递了一个username的参数,我们就认为已经登录, 否则的话就没有登录。

1. 装饰类视图中的dispatch方法:

首先在view中定义个人中心的类视图和登录页面的函数视图:

# 个人中心的类视图
class Profileview(View):
    # 只能通过GET请求来访问
    def get(self,request):
        return HttpResponse('个人中心页面')
        
    # 因为所有的请求方法在类视图中最终都会通过diapatch这个方法,
    # 所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。
    def dispatch(self, request, *args, **kwargs):
        return super(Profileview,self).dispatch(request,*args,**kwargs)

# 登录的函数视图
def login(request):
    return HttpResponse('login')

在上面我们已经说到了所有的请求方法在类视图中最终都会通过diapatch这个方法,所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。然后再使用装饰器将dispatch装饰,就达到了对类视图进行装饰的目地了,所以我们需要先写一个装饰器:

def login_required(func):
    def wrapper(request,*args,**kwargs):
        username = request.GET.get('username')
        # 判断是否得到username这个值,如果得到了,就认为已经登录成功了,否则就没有登录
        if username:
            return func(request,*args,**kwargs)
        else:
        	# 没有登录 重定向到登录页面
            return redirect(reverse('class:login'))

    return wrapper

这里的reverse中的class是我在这个view的app下面的urls中的app_name,login是我的给path起的名字。

app_name = 'class'
urlpatterns = [
    path('login/',views.login,name='login'),
    path('prfile/',views.Profileview.as_view(),name='profile'),
    ]

然后我们就将我们写的装饰器对dispatch进行装饰,这里我们需要使用到django中对类视图装饰的方法了:

from django.utils.decorators import method_decorator

对类视图进行装饰我们一般都使用这个方法。

然后我们使用这个方法对dispatch进行装饰,在dispatch函数的上面一行写入:

@method_decorator(login_required)

装饰完之后Profileview这个类视图的完整代码为:

# 个人中心的类视图
class Profileview(View):
    # 只能通过GET请求来访问
    def get(self,request):
        return HttpResponse('个人中心页面')

    # 因为所有的请求方法在类视图中最终都会通过diapatch这个方法,
    # 所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。
    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super(Profileview,self).dispatch(request,*args,**kwargs)

怎样我们就实现了第一种方法对类视图进行装饰了。
就可以输入url进行测试了,例如我的个人中心网址是:

http://127.0.0.1:8000/class/prfile/

当我进行访问的时候,他就会给我们跳转至登录页面,因为我们没有传入一个值username进去,就认为我们没有登录,所以我们想要访问个人中心就需要传入username进去。

http://127.0.0.1:8000/class/prfile/?username=xxx

这样,我们就能访问到我们的个人中心了,说明我们的装饰器也起到了相应的效果了。

2. 直接装饰在整个类上:

但是上面的方法并不太好,因为我们在写类视图的时候,绝大多数就不会去重写dispatch方法,比如说我使用get请求,那么我就重写get方法就行了,使用post请求,重写post方法,而不用去重写dispatch方法。

那么怎样直接装饰在整个类上面呢,非常简单,只需要在类视图上面添加语句代码就行了

# 个人中心的类视图
@method_decorator(login_required,name='dispatch')
class Profileview(View):
    # 只能通过GET请求来访问
    def get(self,request):
        return HttpResponse('个人中心页面')

    # 因为所有的请求方法在类视图中最终都会通过diapatch这个方法,
    # 所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。
    # @method_decorator(login_required)
    # def dispatch(self, request, *args, **kwargs):
    #     return super(Profileview,self).dispatch(request,*args,**kwargs)

这样,我们就不用重写dispatch方法了,只需要在后面添加一个参数name指定装饰的类中的哪一个方法既可以了。

当然,如果我们有多个装饰器,我们还可以指定一个列表,

@method_decorator([login_required,xx_required],name='dispatch')

但是我们这里只定义了login_required,而没有定义xx_required,所以肯定会报错的,这里只是想说一下当有多个装饰器的用法。

这样,我们就成功的对类视图进行了装饰了,推介大家使用第二种方法,不用重写dispatch方法。

想深入学习django的可以看一下这个视频:超详细讲解Django打造大型企业官网

猜你喜欢

转载自blog.csdn.net/xujin0/article/details/84038778