百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

「Django框架」-类视图函数CBV

suiw9 2024-12-01 04:01 52 浏览 0 评论

本文来源于公众号【Python野路子】

函数视图


前面我们学习中视图都是通过定义函数来实现的,例如:

# views.py
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')

    elif request.method == 'POST':
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        if username and pwd:
            res['user'] = username
            return JsonResponse(res, json_dumps_params={"ensure_ascii": False})

        res['msg'] = '用户名或密码错误'
        return JsonResponse(res, json_dumps_params={"ensure_ascii": False})

# *************************    
# urls.py
from .views import login

urlpatterns = [
 path('login/', login, name='login')
]

类视图

类视图引入

上面是通过函数的形式定义的视图,也就是视图函数,这种通过函数实现的便于理解,但是如果一个视图函数对应的URL支持多种HTTP请求方式GET/POST/DELETE/PUT,那可能要在一个函数中写不同的业务逻辑代码,这样会导致可读性和复用性低,我们可以使用类视图class-based view来解决。

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

Django提供了下面几个 class-based view。

View视图

django.views.generic.base.View是主要的类视图,所有的类视图都是继承自它,它实现了基于HTTP方法的分发dispatch逻辑,比如GET请求会调用对应的get方法,POST请求会调用对应的post方法,但它自己没有实现具体的get或者post方法。

如果我们自定义类视图,也可以继承自它,然后再根据当前请求方式method,来实现不同的方法,可以看下其源代码了解下。

类视图,不同的请求方式,有不同的业务处理逻辑,定义不同的方法,方法直接采用HTTP的请求方式名字,作为方法名,例如:

from django.views.generic import View

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

    def post(self, request, *args, **kwargs):
        username = request.POST.get('username')
        pwd = request.POST.get('password')
        print(username, pwd)
        res = {}
        if username and pwd:
            res['user'] = username
            return JsonResponse(res, json_dumps_params={"ensure_ascii": False})

        res['msg'] = '用户名或密码错误'
        return JsonResponse(res, json_dumps_params={"ensure_ascii": False})

3)类视图定义完后,要想访问这个类视图,还应该在urls.py中进行映射,而类视图不能像我们访问视图函数那样进行映射,还需要调用View的类方法as_view()方法才能进行映射。

from .views import LoginView

urlpatterns = [
    # 类视图:注册  as_view()可以将类视图转换成视图,并决定如何分发请求
 path('login/', LoginView.as_view(), name='login')
]


通过上面源码可知,最终还是调用一个函数。

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


如果类视图里面只出现了post方法,这样就只能使用POST方法来访问这个类视图了,如:

class LoginView(View):
    def post(self, request, *args, **kwargs):
        return HttpResponse("登录成功!")
    
    def http_method_not_allowed(self, request, *args, **kwargs):
        return HttpResponse("您当前请求的方式method是:{},本视图只支持使用POST请求!".format(request.method))

而用户访问的页面使用的是get方法,那么就会把这个请求转发给http_method_not_allowed(request,*args,**kwargs)

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


通过上面的function viewclass-based view,明显的好处就是,解耦了GET、POST 请求以及其他请求 。

View无论是函数形式还是类形式,都是用来处理HTTP请求的 。因此,对于同一个URL需要处理多种请求的情况,class-based view显然更加合适,因为可以避免写很多分支语句,代码可读性好。

类视图相对于函数视图有更高的复用性,如果其他地方需要使用到某个类的某个特定方法,直接继承该类的视图就可以了。

使用类视图是django推荐的做法,熟悉了类视图的使用方法后,能够减少视图函数的重复代码,节省开发时间。

高级玩法

我们一般在逛一些论坛或者博客,都会发现,首页都是展示一系列的文章列表之类。处理过程非常类似,首先从数据库取出文章列表,然后将这些数据传递给模板并渲染模板。于是,Django把这些相同的逻辑代码抽取了出来,写成了一系列的通用视图函数,即基于类的通用视图(Generic Class Based View)。

TemplateView

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

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

about.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    这是【关于我们】的页面
</body>
</html>

既可以用来返回某个模板, 也可以直接写到 URL 上:

from django.views.generic import TemplateView

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

这只是简单用法,用来返回静态页面,如果我们想使用TemplateView进行渲染模板,又想传递参数,我可以定义一个类,继承 TemplateView,然后实现它的get_context_data 方法来将要展示的数据传递到模板中 。

about.html

<body>
    这是【关于我们】的页面
    <p>
        姓名:{{ username }}
    </p>
</body>

views.py:

from django.views.generic import TemplateView

class AboutView(TemplateView):
    # 指定模板路径
    template_name = 'about.html'

    # 传入的数据
    def get_context_data(self, **kwargs):
        # 调用父类的get_context_data方法,否则很多方法,上下文数据将不能使用
        context = super().get_context_data(**kwargs)
        # 然后添加自定义的参数
        context.update({'username': 'admin666'})
        return context

urls.py:

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

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

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


DetailView

我们要查看某一篇文章详情,就是从数据库中获取这篇文章的记录然后渲染模板。一般我们可以这样实现:

# views.py
class ArticleView(View):
    def get(self, request, article_id):
        article = Article.objects.get(pk=article_id)
        print(article)

        return render(request, 'article_detail.html', {'article': article})

# urls.py
path('<int:article_id>/', views.ArticleView.as_view(), name='article_detail'),
<!-- article_detail.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ article.title }}</title>
</head>
<body>
    <h1>{{ article.title }}</h1>
    <div>
        <span>分类:{{ article.category.name }}</span>
        <span>阅读量:{{ article.read_num }}</span>
    </div>
    <hr/>
    <p>
        {{ article.content }}
    </p>

</body>
</html>


对于这种需求,对于这种类型的需求Django ,提供了一个 DetailView 类视图。

from django.views.generic import View, DetailView

class ArticleView(DetailView):
    model = Article    # 指定需要获取哪个Model的数据。
    template_name = 'article_detail.html'   # 渲染的模板
    context_object_name = 'article'     # 指定获取的模型列表数据保存的变量名,这个变量会被传递给模板
    pk_url_kwarg = 'article_id'     # 默认为pk,需要与url的命名组一致。否则报错
    #slug_url_kwarg = 'slug'


    def get(self, request, *args, **kwargs):
        '''
            覆写 get 方法的目的是因为每当文章被访问一次,就得将文章阅读量 +1
            get 方法返回的是一个 HttpResponse 实例
            之所以需要先调用父类的 get 方法,是因为只有当 get 方法被调用后,
            才有 self.object 属性,其值为 Article 模型实例,即被访问的文章 article
        '''

        response = super().get(request, *args, **kwargs)

        # 将文章阅读量 +1
        # 注意self.object的值就是被访问的文章article
        self.object.read_num += 1
        self.object.save(update_fields=['read_num'])

        # 视图必须返回一个 HttpResponse 对象
        return response

    # def get_queryset(self):
    #     '''
    #     用来获取数据 。
    #     如果设定了 queryset , 则会直接返回 query set 。
    #     '''
    #     pass

    # def get_object(self, queryset=None):
    #     '''
    #     get_objet()方法会取得url中传递进来的参数来获取某个object。比如,article/2,则get_object()会通过Article模型中
    #     pk =2
    #     根据URL参数,从 queryset 上获取到对应的实例 。
    #     '''
    #
    #     article_id = self.kwargs.get('article_id')
    #
    #     article = super().get_object(queryset=Article.objects.get(pk=article_id))
    #
    #     return article

    # def get_context_data(self, **kwargs):
    #     '''
    #     获取憧染到模板中的所有上下文,
    #     如果有新增数据需要传递到模板中,可以重写该方法来完成。
    #     '''
    #     pass


# urls.py
path('<int:article_id>/', views.ArticleView.as_view(), name='article_detail'),

ListView

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

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

为了方便我们进行测试,这里以Tag模型类举例学习(因为这个定义时字段比较少,哈哈~~),

models.py:

class Tag(models.Model):
    name = models.CharField(verbose_name='标签名称', max_length=20, unique=True)

    class Meta:
        db_table = 'tb_tag'

    def __str__(self):
        return self.name

为了方便测试,首先我们先来进行批量添加数据,在views中新建一个函数视图:用来批量添加数据。

# views.py

from .models import Tag

def addTag(request):
    tags = []

    for i in range(1, 50):
        tag = Tag(name='标签名称:%s'%i)

    Tag.objects.bulk_create(tags)  # 批量添加数据
    return HttpResponse('success')

# urls.py
path('addTag/', views.addTag)

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

然后我们新建一个tag_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表</title>
</head>
<body>
    <ul>
        {% for tag in tags %}
            <li>{{ tag.name }}</li>
        {% endfor %}
    </ul>
</body>
</html>

我们可以这样实现:

class TagListView(View):
    def get(self, request):
        tags = Tag.objects.all()

        return render(request, 'tag_list.html', context={'tags': tags})

首先通过Tag.objects.all()从数据库中获取Tag全部数据,并将其保存到 tags 变量中,前端模板获取渲染即可。

针对这种从数据库中获取某个模型列表数据(比如这里的 Tag列表)的视图,Django专门提供了一个 ListView 类视图。

class TagListView(ListView):
    model = Tag   # 指定需要获取哪个Model的数据。
    template_name = 'tag_list.html'  # 视图渲染的模板
    context_object_name = 'tags'    # 指定获取的模型列表数据保存的变量名,这个变量会被传递给模板。默认是object_list变量
    paginate_by = 10    # 每一页需要展示多少条数据
    ordering = 'id'     #  排序展示
    page_kwarg = 'p'    # 指定换页面参数,默认page,以GET方式传递参数

输入网址,就能查看效果了,并且当前页面只有10条数据。


而如果我们向访问后面的页面,我们只需要在输入的网址后面以GET的方式添加一个p=x的参数就行了,例如:


上面是常用属性的用法,而我们还有两个比较常用的方法。

get_context_data:获取上下文的数据,并且可以添加自己的参数,并最终将其传入模板。

get_queryset:如果提取数据的时候,并不需要将所有数据都返回,那么可以重写这个方法,将一些不需要展示的数据给过滤掉。

例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表</title>
</head>
<body>
    <ul>
        {% for tag in tags %}
            <li>{{ tag.name }}-{{ username }}</li>
        {% endfor %}
    </ul>
</body>
</html>
#views.py

class TagListView(ListView):
    model = Tag   # 指定需要获取哪个Model的数据。
    template_name = 'tag_list.html'  # 视图渲染的模板
    context_object_name = 'tags'    # 指定获取的模型列表数据保存的变量名,这个变量会被传递给模板。默认是object_list变量
    paginate_by = 10    # 每一页需要展示多少条数据
    ordering = 'id'     #  排序展示
    page_kwarg = 'page'    # 指定换页面参数,默认page,以GET方式传递参数

    # 重写父类的get_context_data方法,添加自己的参数
    def get_context_data(self, *, object_list=None, **kwargs):
        # 调用父类的get_context_data方法,否则很多方法,上下文数据将不能使用
        context = super().get_context_data(**kwargs)
        # 然后添加自定义的参数
        context['username'] = 'qmpython'
        print(context)
        return context

    def get_queryset(self):
        # 重写get_queryset,根据需要进行过滤,若没有重写方法默认返回所有的数据
        queryset = super().get_queryset()

        tag_id = self.kwargs.get('tag_id')

        return queryset.filter(id__gt=tag_id)  # id大于2的
    

# urls.py
path('tagList/<int:tag_id>/', TagListView.as_view())

在类视图ListView中,从URL捕获的路径参数值保存在实例的kwargs属性(是一个字典)里,非路径参数值保存在实例的 args 属性(是一个列表)里。所以使用self.kwargs.get('tag_id') 来获取从 URL 捕获的tag_id 值。然后查询将id>tag_id的数据过滤出来了。


通过上面打印,我们可以看出里面包含了很多东西:

{'paginator': <django.core.paginator.Paginator object at 0x7fd8e89f9d30>, 'page_obj': <Page 1 of 6>, 'is_paginated': True, 'object_list': <QuerySet [<Tag: MySQL>, <Tag: 快速入门>, <Tag: 摆地摊>, <Tag: 朋友圈>, <Tag: 标签名称:1>, <Tag: 标签名称:2>, <Tag: 标签名称:3>, <Tag: 标签名称:4>, <Tag: 标签名称:5>, <Tag: 标签名称:6>]>, 'tags': <QuerySet [<Tag: MySQL>, <Tag: 快速入门>, <Tag: 摆地摊>, <Tag: 朋友圈>, <Tag: 标签名称:1>, <Tag: 标签名称:2>, <Tag: 标签名称:3>, <Tag: 标签名称:4>, <Tag: 标签名称:5>, <Tag: 标签名称:6>]>, 'view': <django_project.views.TagListView object at 0x7fd8e8a78c88>, 'username': 'qmpython'}

有我们常用的paginator类、page_obj类和我们自定义的一些参数等。

paginator类、page_obj类我们接下来进行学习。

我们来具体看一下 ListView 的流程,其他的 View 大同小异 。(1)请求到达之后,首先会调用 dispatch 进行分发 。(2)接着会调用 get 方法 。①在 GET 请求中,首先会调用 get_queryset 方法,拿到数据源 。②接着调用 get_context_data 方法 , 拿到需要渲染到模板中的数据 。1)在 get _ context data 中,首先会调用 get_paginate_by 拿到每页数据 。2)接着调用 get_context_object_name。3 )然后调用paginate_queryset 进行分页处理 。4)最后拿到的数据转为dict并返回 。③调用 render_to_response 谊染数据到页面中 。1)在render_to_response 中调用 get_tempalte_names 拿到模板名 。2)然后把 request 、context、template_name 等传递到模板中 。

Paginator和Page

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

1、Paginator常用属性和方法:

1)count:总共有多少条数据。

2)num_pages:总共有多少页。

3)page_range:页面的区间,即页码列表。比如有3页,那么就range(1,4)。

2、Page类的常用属性和方法:

1)has_next:是否还有下一页。

2)has_previous:是否还有上一页。

3)next_page_number:下一页的页码。

4)previous_page_number:上一页的页码。

5)number:当前页。只有这一个是属性,其他的都是方法

6)start_index:当前这一页的第一条数据的索引值。

7)end_index:当前这一页的最后一条数据的索引值。

基本使用

我们来看下:

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

class TagListView(View):
    def get(self, request):
        tags = Tag.objects.all()
        # 实例化一个Paginator对象,传入一个需要分页的列表对象以及每页显示几条数据
        paginator = Paginator(tags, 2)

        print(paginator.count)  # 获取总共有多少条数据。55
        print(paginator.num_pages)  # 获取总有多少页。   6
        print(paginator.page_range)  # 获取页数的范围,要前不要后   range(1, 7)

        page1 = paginator.page(1)  # 第1页的page对象
        print(type(page1), page1)  # <class 'django.core.paginator.Page'> <Page 1 of 6>

        # 显示某一页数据的两种方式
        for i in page1:  # 遍历第1页的所有数据对象
            print(i)

        print(page1.object_list)  # 第1页的所有数据

        page2 = paginator.page(2)
        print(page2.number)  # 当前页,只有这一个是属性,其他的都是方法
        print(page2.start_index())  # 当前这一页的第一条数据的索引值。
        print(page2.end_index())   # 当前这一页的最后一条数据的索引值。
        print(page2.has_next())    # 是否还有下一页。
        print(page2.has_previous())  # 是否还有上一页。
        print(page2.next_page_number())   # 下一页的页码。判断是否还有下一页,否则报错
        print(page2.previous_page_number())  # 上一页的页码。判断是否还有上一页,否则报错

        # 抛错
        # page=paginator.page(12)   # error:EmptyPage
        # page=paginator.page("z")   # error:PageNotAnInteger

        current_page = int(request.GET.get('page', 1))

        #  如果页数十分多时,页码导航就会显示比较长,我们可以始终选择当前页码数左边固定显示多少。
        if paginator.num_pages > 11:  # 11表示只显示多少个页码数,即如果总共28页,如果只显示11个页码,     # 如果我们要当前页左右最多显示5个,则进行判断
            if current_page - 5 < 1:     # 当前页码<5,显示11个
                page_range = range(1, 12) # 显示11个
            elif current_page + 5 > paginator.num_pages:  # 当前页码右边没5个,则
                page_range = range(current_page - 5, paginator.num_pages + 1)

            else:
                page_range = range(current_page - 5, current_page + 5)

        else:
            page_range = paginator.page_range
            

        try:
            page_obj = paginator.page(current_page)  # 获取current_page页page对象

        except PageNotAnInteger:
            # 如果用户请求的页码号不是整数,显示第1页
            page_obj = paginator.page(1)
        except EmptyPage:
            # 如果用户请求的页码号超过了最大页码号,显示最后一页
            page_obj = paginator.page(paginator.num_pages)

        context = {
            'current_page': current_page,
            'page_obj': page_obj,
            'page_range': page_range
            }

        return render(request, 'tag_list.html', context=context)

模板tag_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<ul>
    {% for tag in page_obj %}
        <li>{{ tag.name }}</li>
    {% endfor %}
</ul>

<ul class="pagination" id="pager">
    {% if page_obj.has_previous %}
        <li class="previous"><a href="?page={{ page_obj.previous_page_number }}">上一页</a></li>
    {% else %}
        <li class="previous disabled"><a href="#">上一页</a></li>
    {% endif %}

    {% for page_num in page_range %}
        {% if page_num == current_page %}
            <li class="item active"><a href="?page={{ page_num }}">{{ page_num }}</a></li>
        {% else %}
            <li class="item"><a href="?page={{ page_num }}">{{ page_num }}</a></li>
        {% endif %}
    {% endfor %}
    {% if page_obj.has_next %}
        <li class="next"><a href="?page={{ page_obj.next_page_number }}">下一页</a></li>
    {% else %}
        <li class="next disabled"><a href="#">下一页</a></li>
    {% endif %}

</ul>
</body>
</html>


使用通用视图ListView自带分页功能

这个一般是在视图函数中使用分页的代码逻辑,既然现在我们学习了类视图,那我们就使用类视图提供的一些类视图封装好的功能,稍微配置下即可使用,而类视图 ListView 已经帮我们写好了上述的分页逻辑,我们只需通过指定 paginate_by 属性来开启分页功能即可,即在类视图中指定 paginate_by 属性的值:

class TagListView(ListView):
    model = Tag   # 指定需要获取哪个Model的数据。
    template_name = 'tag_list.html'  # 视图渲染的模板
    context_object_name = 'tags'    # 指定获取的模型列表数据保存的变量名,这个变量会被传递给模板。默认是object_list变量
    paginate_by = 3    # 每一页需要展示多少条数据
    ordering = 'id'     #  排序展示
    page_kwarg = 'page'    # 指定换页面参数,默认page,以GET方式传递参数

    # 重写父类的get_context_data方法,添加自己的参数
    def get_context_data(self, *, object_list=None, **kwargs):
        # 先继承父类的get_context_data方法,否则很多方法将不能使用
        context = super().get_context_data(**kwargs)
        # 然后添加自定义的参数
        context['username'] = 'qmpython'

        #print(context)
        # paginator = context.get('paginator')  # paginator类
        # print(paginator.count)  # 获取总共有多少条数据。55
        # print(paginator.num_pages)  # 获取总有多少页。   6
        # print(paginator.page_range)  # 获取页数的范围,要前不要后   range(1, 7)
        #
        # page_obj = context.get('page_obj')  # 当前请求页面分页对象。
        # 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):
    #     # 重写get_queryset,根据需要进行过滤,若没有重写方法默认返回所有的数据
    #     queryset = super().get_queryset()
    #     return queryset.filter(id__gt=2)  # id大于2的

如果我使用访问第2页即加?page=2,那么

page_obj.number = 2
page_obj.start_index() = 11
page_obj.end_index() = 20
page_obj.has_next() = True
page_obj.has_previous() = True
page_obj.next_page_number() = 3
page_obj.previous_page_number = 1

ListView 传递了以下和分页有关的模板变量供我们在模板中使用:

  • paginator ,即 Paginator 的实例。
  • page_obj ,当前请求页面分页对象。
  • is_paginated,是否已分页。只有当分页后页面超过两页时才算已分页。
  • object_list,请求页面的对象列表,和 context_object_name设置为tags等价。所以在模板中循环当前页列表时可以选 tags ,也可以选 object_list,还可以选择page_obj

模板文件tag_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<ul>
    {% for tag in tags %}
        <li>{{ tag.name }}-{{ qmpython }}</li>
    {% endfor %}
</ul>

<ul class="pagination" id="pager">
    {% if page_obj.has_previous %}
        <li class="previous"><a href="?page={{ page_obj.previous_page_number }}">上一页</a></li>
    {% else %}
        <li class="previous disabled"><a href="#">上一页</a></li>
    {% endif %}

    {% for page_num in paginator.page_range %}
        {% if page_num == page_obj.number %}
            <li class="item active"><a href="?page={{ page_num }}">{{ page_num }}</a></li>
        {% else %}
            <li class="item"><a href="?page={{ page_num }}">{{ page_num }}</a></li>
        {% endif %}
    {% endfor %}
    {% if page_obj.has_next %}
        <li class="next"><a href="?page={{ page_obj.next_page_number }}">下一页</a></li>
    {% else %}
        <li class="next disabled"><a href="#">下一页</a></li>
    {% endif %}

</ul>
</body>
</html>


Django第三方分页库

使用 Django 内置的 Pagination 只能实现上面的简单分页效果,但通常更加高级的分页效果,当前页面高亮显示,且显示当前页面前后几页的连续页码,始终显示第一页和最后一页的页码,中间可能还有省略号的效果,表示还有未显示的页码。比如像这种:


仅仅使用 Django Pagination 内置的方法,需要自己写分页逻辑,前面我们已经试过进行处理了,但是很低效也不是很满意,那我们可以借助第三方库django-pure-pagination

1)首先安装

# 进入虚拟环境
workon django_project
# 安装库
pip install django-pure-pagination

2)settings.py文件进行配置

# app应用引用
INSTALLED_APPS = [
    'pure_pagination', # 添加第三方分页的模块,这个模块是在django的分页功能上封装的
]


# 一页显示多少条记录
ONE_PAGE_COUNT = 3

# django-pure-pagination提供的配置项,用于个性化配置分页效果

# 如果需要做分页的设置,需要设置成官网里面的,可以把10改成3,把2改成1,参考https://github.com/jamespacileo/django-pure-pagination.git
PAGINATION_SETTINGS = {
    'PAGE_RANGE_DISPLAYED': 4,   #  分页条当前页前后应该显示的总页数(两边均匀分布,因此要设置为偶数)
    'MARGIN_PAGES_DISPLAYED': 2,  # 分页条开头和结尾显示的页数(默认为2)
    'SHOW_FIRST_PAGE_WHEN_INVALID': True,  # 当请求不存在的页面时,显示第一页,而不是404页面
}

基本使用

视图views.py文件

#from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
import logging
from django.conf import settings


class TagListView(View):
    def get(self, request):

        try:
            current_page = request.GET.get('page', 1)
            print(type(current_page))

        except PageNotAnInteger:
            # 如果用户请求的页码号不是整数,显示第1页
            current_page = 1

        tags = Tag.objects.all()

        # 实例化一个Paginator分页对象,传入一个需要分页的列表对象以及每页显示几条数据
        paginator = Paginator(tags, settings.ONE_PAGE_COUNT, request=request)

        # 获取当前页的page对象
        tag_page_obj = paginator.page(current_page)

        print(tag_page_obj.pages)  # <bound method Page.pages of <Page 4 of 19>>

        context = {
            'tag_page_obj': tag_page_obj,
            }

        return render(request, 'tag_list.html', context=context)

模板tag_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<ul>
    {% for tag in tag_page_obj.object_list %}  {# 第page分页对象的元素列表,做分页获取数据要调用 .object_list #}
        <li>{{ tag.name }}</li>
    {% endfor %}
</ul>
<ul class="pagination" id="pager">
    {% if tag_page_obj.has_previous %}
        <li class="previous"><a href="?page={{ tag_page_obj.previous_page_number }}">上一页</a></li>
    {% else %}
        <li class="previous disabled"><a href="#">上一页</a></li>
    {% endif %}

    {% for page_num in tag_page_obj.pages %}
        {% if page_num %}
            {% ifequal page_num tag_page_obj.number %}
                <li class="item active"><a href="#">{{ page_num }}</a></li>
            {% else %}
                <li class="item"><a href="?page={{ page_num }}">{{ page_num }}</a></li>
            {% endifequal %}
        {% else %}
            <li class="item"><a href="">...</a></li>
        {% endif %}
    {% endfor %}

    {% if tag_page_obj.has_next %}
        <li class="next"><a href="?{{ tag_page_obj.next_page_number.querystring }}">下一页</a></li>
    {% else %}
        <li class="next disabled"><a href="#">下一页</a></li>
    {% endif %}

</ul>
</body>
</html>

与通用视图ListView混合使用

那我们也可以使用它和ListView结合使用,修改之前TagListView视图,让它继承 django-pure-pagination 提供的 PaginationMixin,这个混入类将为我们提供上述提到的分页功能。

from pure_pagination import PaginationMixin

class TagListView(PaginationMixin, ListView):
    model = Tag   # 指定需要获取哪个Model的数据。
    template_name = 'tag_list.html'  # 视图渲染的模板
    paginate_by = 3    # 每一页需要展示多少条数据
    ordering = 'id'     #  排序展示
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<ul>
    {% for tag in page_obj.object_list %}
        <li>{{ tag.name }}</li>
    {% endfor %}
</ul>
<ul class="pagination" id="pager">
    {% if page_obj.has_previous %}
        <li class="previous"><a href="?{{ page_obj.previous_page_number.querystring }}">上一页</a></li>
    {% else %}
        <li class="previous disabled"><a href="#">上一页</a></li>
    {% endif %}

    {% for page_num in page_obj.pages %}
        {% if page_num %}
            {% ifequal page_num page_obj.number %}
                <li class="item active"><a href="#">{{ page_num }}</a></li>
            {% else %}
                <li class="item"><a href="?{{ page_num.querystring }}">{{ page_num }}</a></li>
            {% endifequal %}
        {% else %}
            <li class="item"><a href="">...</a></li>
        {% endif %}
    {% endfor %}

    {% if page_obj.has_next %}
        <li class="next"><a href="?{{ page_obj.next_page_number.querystring }}">下一页</a></li>
    {% else %}
        <li class="next disabled"><a href="#">下一页</a></li>
    {% endif %}

</ul>
</body>
</html>


给类视图添加装饰器

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

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

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

from django.utils.decorators import method_decorator # 对类视图进行装饰我们一般都使用这个方法。

def login_required(func):
    def inner(request, *args, **kwargs):
        username = request.GET.get('username')
        print('******===>', username)
        # 根据是否传参username来判断是否已登录,
        if username:  # 真实业务场景可能是先从数据库查询账号和密码
            print(username)
            return func(request, *args, **kwargs)
        else:
            print('None')
            return redirect(reverse('user:login'))

    return inner

class ProfileView(View):
    def get(self, request, *args, **kwargs):
        return HttpResponse('个人中心页面')

    @method_decorator(login_required) # 等同于 dispatch = login_required(dispatch)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

def login(request):
    return HttpResponse('登录')    

前面我们学过不管是get请求还是post请求,都会走dispatch(request,*args,**kwargs)方法,所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。然后再使用装饰器将dispatch装饰,就达到了对类视图进行装饰的目地了。

2. 直接装饰在整个类上

上面第一种方式,我们已经达到目的了,但是这种方法并不太好,我们实现类视图的时候,绝大多数不会去重写dispatch方法,比如说我使用get请求,那么我就重写get方法就行了,使用post请求,重写post方法,而不用去重写dispatch方法。

@method_decorator(login_required, name='dispatch')
class ProfileView(View):
    def get(self, request, *args, **kwargs):
        return HttpResponse('个人中心页面')

    # @method_decorator(login_required)
    # def dispatch(self, request, *args, **kwargs):
    #     return super().dispatch(request, *args, **kwargs)

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

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

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

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

相关推荐

10款超实用JavaScript音频库(js播放音频代码)

HTML5提供了一种新的音频标签实现和规范用一个简单的HTML对象而无需音频插件来控制音频。这只是一个简单的整合这些新的HTML5音频特征及使用JavaScript来创建各种播放控制。下面将介绍10款...

Howler.js,一款神奇的 JavaScript 开源网络音频工具库

o...

PROFINET转Modbus网关——工业协议融合的智能枢纽

三格电子SG-PNh750-MOD-221,无缝连接Profinet与Modbus,赋能工业物联产品概述...

简单实用的Modbus类库,支持从站和DTU

一、简介...

[西门子PLC] S7-200 SMART PROFINET :通过GSD组态PLC设备

从S7-200SMARTV2.5版本开始,S7-200SMART开始支持做PROFINETIO通信的智能设备。从而,两个S7-200SMART之间可以进行PROFINETI...

Modbus(RTU / TCP)有什么异同(modbus tcp和tcp)

Modbus是一种广泛使用的工业自动化通信协议,它支持设备之间的数据交换。Modbus协议有两个主要的变体:ModbusRTU(二进制模式)和ModbusTCP(基于TCP/IP网络的模式)。尽管...

Modbus通信调试步骤详解(modbus调试工具怎么用)

Modbus通信调试步骤详解  Modbus通信分为串口和以太网,无论是串口还是以太网,只要是标准Modbus,就可以用Modbus模拟器进行调试。按以下几步进行调试。...

理解Intel手册汇编指令(intel 汇编指令手册)

指令格式...

「西门子PLC」S7-200 SMART的Modbus RTU通讯

S7-200SMART集成的RS485端口(端口0)以及SBCM01RS485/232信号板(端口1)两个通信端口可以同时做MODBUSRTU主站,或者一个做MODBUSRTU主站一个做MO...

InfiniBand网络运维全指南:从驱动安装到故障排查

一、InfiniBand网络概述InfiniBand(直译为“无限带宽”技术,缩写为IB)是一种用于高性能计算的计算机网络通信标准,具有极高的吞吐量和极低的延迟,用于计算机与计算机之间的数据互连。它...

一加回归 OPPO,背后的秘密不可告人

有这样一个手机品牌,它诞生于互联网品牌。在大众群体看来,它的身世似乎模糊不清,许多人以为它是国外品牌。它的产品定位是极客群体,深受国内发烧友,甚至国外极客玩家喜爱。...

[西门子PLC] S7-200SMART快速高效的完成Modbus通信程序的设计

一、导读Modbus通信是一种被广泛应用的通信协议,在变频器、智能仪表还有其他一些智能设备上都能见到它的身影。本文呢,就把S7-200SMART系列PLC当作Modbus主站,把...

狂肝10个月手搓GPU,他们在我的世界中玩起我的世界,梦想成真

梦晨衡宇萧箫发自凹非寺量子位|公众号QbitAI自从有人在《我的世界》里用红石电路造出CPU,就流传着一个梗:...

[西门子PLC] 博途TIA portal SCL编程基础入门:1-点动与自锁

一、S7-SCL编程语言简介...

工作原理系列之:Modbus(modbus工作过程)

MODBUS是一种在自动化工业中广泛应用的高速串行通信协议。该协议是由Modion公司(现在由施耐德电气公司获得)于1979年为自己的可编程逻辑控制器开发的。该协议充当了PLCS和智能自动化设备之间的...

取消回复欢迎 发表评论: