Django框架学习——13—(通用代码分页功能、错误处理、表单、常用的Field、常用验证器)

1、通用代码分页功能

实例代码:

前端界面:article_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

</head>
<body>
{% for article in articles %}
<p>{{ article.title }}-{{ article.content }}-{{ article.create_time }}</p>

{% endfor %}


<nav aria-label="Page navigation">
    <ul class="pagination">
        <!--  判断是否有上一页    -->
        {% if page_obj.has_previous %}
        <li>
            <!--     previous_page_number如果有上一页,点击后进入上一页页面url       -->
            <a href="{% url 'list' %}?page={{ page_obj.previous_page_number }}" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        {% else %}
        <li class="disabled">
            <!--     如果没有上一页,页面按钮上显示禁止点击符号url       -->
            <a href="#" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        {% endif %}

        <!--    左边显示页码为1重复的问题    -->
        {% if left_has_more == True %}
             <li><a href="{% url 'list' %}?page=1">1</a></li>
             <li><a href="#">...</a></li>
        {% endif %}

        <!--    修改上面页码导航栏的显示,数据渲染从article/views.py中的context来    -->
        <!--    左边页码导航显示        -->
        {% for page in left_range %}
            <li><a href="{% url 'list' %}?page={{ page }}">{{ page }}</a></li>
        {% endfor %}

        <!--   当前页显示    -->
        <li><a href="#">{{ page_obj.number }}</a></li>

        <!--    右边页码导航显示    -->
<!--        {% for page in right_range %}-->
<!--            <li><a href="{% url 'list' %}?page={{ paginator.num_pages }}">{{ paginator.num_pages }}</a></li>-->
<!--        {% endfor %}-->

        <!--    右边页码导航显示        -->
        {% if right_has_more == True %}
            <li><a href="#">...</a></li>
            <li><a href="{% url 'list' %}?page={{ paginator.num_pages }}">{{ paginator.num_pages }}</a></li>
        {% endif %}

        <!--    判断是否是最后一页    -->
        {% if page_obj.has_next %}
        <li>
            <a href="{% url 'list' %}?page={{ page_obj.next_page_number }}" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
        {% else %}
        <li class="disabled">
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
        {% endif %}


    </ul>
</nav>

</body>
</html>

视图文件:views.py

from django.shortcuts import render
from django.views.generic import ListView
from .models import Article
from django.http import HttpResponse


class ArticleListView(ListView):
    model = Article
    template_name = 'article_list.html'
    paginate_by = 10
    context_object_name = 'articles'
    ordering = 'create_time'
    page_kwarg = 'page'
    
    '''
    # def get_context_data(self, **kwargs):
        # get_context_data的源码中包含有属性
        # context = {
        #                 'paginator': paginator,
        #                 'page_obj': page,
        #                 'is_paginated': is_paginated,
        #                 'object_list': queryset
        #             }
        # context = super(ArticleListView, self).get_context_data(**kwargs)

        # count:总共有多少条数据。
        # num_pages:总共有多少页。
        # page_range:页面的区间。比如有三页,那么就range(1,4)。
        # paginator = context.get('paginator')
        # print(paginator.count)       # 89
        # print(paginator.num_pages)   # 9
        # print(paginator.page_range)  # range(1, 10)
        # return context

        # has_next:是否还有下一页。
        # has_previous:是否还有上一页。
        # next_page_number:下一页的页码。
        # previous_page_number:上一页的页码。
        # number:当前页。
        # start_index:当前这一页的第一条数据的索引值。
        # end_index:当前这一页的最后一条数据的索引值。
      
        # page = context.get('page_obj')
        # print(page.has_next)           # <bound method Page.has_next of <Page 5 of 9>>
        # print(page.has_previous)       # <bound method Page.has_previous of <Page 5 of 9>>
        # print(page.next_page_number)   # <bound method Page.next_page_number of <Page 5 of 9>>
        # print(page.previous_page_number)  # <bound method Page.previous_page_number of <Page 5 of 9>>
        # print(page.number)                # 5
        # print(page.start_index)           # <bound method Page.start_index of <Page 5 of 9>>
        # print(page.end_index)             # <bound method Page.end_index of <Page 5 of 9>>
        
    # def get_queryset(self):
    #     return Article.objects.filter(id__lte=89)
    '''
    
    # 自己编写get_context_data源码,修改其属性
    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        paginator = context.get('paginator')
        page_obj = context.get('page_obj')
        
        paginator_data = self.get_page(paginator, page_obj)
        context.update(paginator_data)
        return context
        
    # 编写get_page方法, page_offset=2页码左右的偏移量
    def get_page(self, paginator, page_obj, page_offset=2):
        current_page = page_obj.number
        # 左右页码
        # left_range = range(current_page-page_offset, current_page)          # range(9,11)---->9,10
        # right_range = range(current_page+1, current_page+page_offset+1)     # range(12,14)--->12,13
        
        # 定义变量解决左右页码最前最后页码显示重复的问题
        left_has_more = False
        right_has_more = False
        
        # 解决页码显示为0或者负值的问题
        if current_page <= page_offset + 2:
            left_range = range(1, current_page)
        else:
            left_range = range(current_page - page_offset, current_page)  # range(9,11)---->9,10
            # 当前页码      1....13 14 15 16 17....20
            left_has_more = True

        # 解决页码显示末尾的问题
        if current_page >= paginator.num_pages - page_offset - 1:   # 总页码-偏移量-1
            right_range = range(current_page+1, paginator.num_pages+1)         # range取不到最右边的值,所以+1
        else:
            right_range = range(current_page+1, current_page+page_offset+1)     # range(12,14)--->12,13
            right_has_more = True

        # 返回数据
        return {
            "left_range": left_range,
            "right_range": right_range,
            "left_has_more": left_has_more,
            "right_has_more": right_has_more,
        }
        
    
def index(request):
    for i in range(1,101):
        article = Article(title='标题%s'% i, content='内容:%s' % i)
        article.save()
    return HttpResponse("index")

实现效果如下:
在这里插入图片描述

2、错误处理

在一些网站开发中。经常会需要捕获一些错误,然后将这些错误返回比较优美的界面,或者是将这个错误的请求做一些日志保存。

常用的错误码

404:服务器没有指定的url。
403:没有权限访问相关的数据。
405:请求的method错误。
400:bad request,请求的参数错误。
500:服务器内部错误,一般是代码出bug了。
502:一般部署的时候见得比较多,一般是nginx启动了,然后uwsgi有问题

自定义错误模板

在碰到比如404,500错误的时候,想要返回自己定义的模板(html文件名称只能是404.html或者500.html,否则定位不了)。那么可以直接在templates文件夹下创建相应错误代码的html模板文件。那么以后在发生相应错误后,会将指定的模板返回回去。

修改配置文件 settings.py

DEBUG = False
ALLOWED_HOSTS = ["127.0.0.1"]

错误处理的解决方案

对于404和500这种自动抛出的错误。我们可以直接在templates文件夹下新建相应错误代码的模板文件。而对于其他的错误,我们可以专门定义一个app,用来处理这些错误。

视图文件views.py

from django.http import HttpResponse
from django.shortcuts import render

def view_405(request):
    return render(request,"errors/405.html",status=405)

绑定路由:urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("405",views.view_405,name="405")
]

3、表单

HTML中的表单:

单纯从前端的html来说,表单是用来提交数据给服务器的,不管后台的服务器用的是Django还是PHP语言还是其他语言。只要把input标签放在form标签中,然后再添加一个提交按钮,那么以后点击提交按钮,就可以将input标签中对应的值提交给服务器了。

Django中的表单

Django中的表单丰富了传统的HTML语言中的表单。在Django中的表单,主要做以下两件事

  1. 渲染表单模板
  2. 表单验证数据是否合法

Django中表单使用流程

在讲解Django表单的具体每部分的细节之前。我们首先先来看下整体的使用流程。
首先我们在后台服务器定义一个表单类,继承自django.forms.Form

# -*- encoding: utf-8 -*-
"""
@File    : forms.py
@Time    : 2020/7/17 16:27
@Author  : chen

表单文件:forms.py
"""
from django import forms     # 直接导入form


class MessageForm(forms.Form):
    # 渲染到前端页面
    title = forms.CharField(max_length=3, label='标题', min_length=2, error_messages={"min_length": '标题字符段不符合要求!'})
    content = forms.CharField(widget=forms.Textarea, label='内容', error_messages={"required": 'content字段必须填写!'})
    email = forms.EmailField(label='邮箱')
    reply = forms.BooleanField(required=False, label='回复')

然后在视图中,根据是GET还是POST请求来做相应的操作。如果是GET请求,那么返回一个空的表单,如果是POST请求,那么将提交上来的数据进行校验。

from django.shortcuts import render, redirect
from django.views import View
from .forms import MessageForm
from django.http import HttpResponse


class IndexView(View):
    def get(self, request):
        form = MessageForm()
        return render(request, "index.html", {"form": form})

    def post(self, request):
        form = MessageForm(request.POST)
        if form.is_valid():
            title = form.cleaned_data.get('title')
            content = form.cleaned_data.get('content')
            email = form.cleaned_data.get('email')
            reply = form.cleaned_data.get('reply')
            return HttpResponse("success")
        else:
            print(form.errors)                     # 报错信息
            print(type(form.errors))               # <class 'django.forms.utils.ErrorDict'>
            print(form.errors.get_json_data())     # {'title': [{'message': '标题字符段不符合要求!', 'code': 'min_length'}]}
            return HttpResponse("fail")
        

在使用GET请求的时候,我们传了一个form给模板,那么以后模板就可以使用form来生成一个表单的html代码。在使用POST请求的时候,我们根据前端上传上来的数据,构建一个新的表单,这个表单是用来验证数据是否合法的,如果数据都验证通过了,那么我们可以通过cleaned_data来获取相应的数据。在模板中渲染表单的HTML。

index.html 前端界面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>首页</h1>
    <form action="" method="post">
        <table>
            {{ form.as_table }}
            <tr>
                <td></td>
                <td><input type="submit" value="提交"></td>
            </tr>
        </table>
    </form>
</body>
</html>

我们在最外面给了一个form标签,然后在里面使用了table标签来进行美化,在使用form对象渲染的时候,使用的是table的方式,当然还可以使用ul的方式(as_ul),也可以使用p标签的方式(as_p),并且在后面我们还加上了一个提交按钮。这样就可以生成一个表单了。
在这里插入图片描述

4、常用的Field

使用Field可以是对数据验证的第一步。你期望这个提交上来的数据是什么类型,那么就使用什么类型的Field。

CharField

用来接收文本。

参数:
max_length:这个字段值的最大长度。
min_length:这个字段值的最小长度。
required:这个字段是否是必须的。默认是必须的。
error_messages:在某个条件验证失败的时候,给出错误信息。

EmailField

用来接收邮件,会自动验证邮件是否合法。
错误信息的key:required、invalid。

FloatField

用来接收浮点类型,并且如果验证通过后,会将这个字段的值转换为浮点类型。

参数:
max_value:最大的值。
min_value:最小的值。
错误信息的key:required、invalid、max_value、min_value。

IntegerField

用来接收整形,并且验证通过后,会将这个字段的值转换为整形。

参数:
max_value:最大的值。
min_value:最小的值。
错误信息的key:required、invalid、max_value、min_value。

URLField

用来接收url格式的字符串。
错误信息的key:required、invalid。

5、常用验证器

在验证某个字段的时候,可以传递一个validators参数用来指定验证器,进一步对数据进行过滤。验证器有很多,但是很多验证器我们其实已经通过这个Field或者一些参数就可以指定了。比如EmailValidator,我们可以通过EmailField来指定,比如MaxValueValidator,我们可以通过max_value参数来指定。

MaxValueValidator:验证最大值。
MinValueValidator:验证最小值。
MinLengthValidator:验证最小长度。
MaxLengthValidator:验证最大长度。
EmailValidator:验证是否是邮箱格式。
URLValidator:验证是否是URL格式。
RegexValidator:如果还需要更加复杂的验证,那么我们可以通过正则表达式的验证器:RegexValidator。比如现在要验证手机号码是否合格,那么我们可以通过以下代码实现:

class MyForm(forms.Form):
    telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='请输入正确格式的手机号码!')])

实例如下:

from django import forms     # 直接导入form
from django.core import validators


class MessageForm(forms.Form):
   
    # 正则表达式编写验证器
    tel = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}", message="手机格式不正确")])

自定义验证

有时候对一个字段验证,不是一个长度,一个正则表达式能够写清楚的,还需要一些其他复杂的逻辑,那么我们可以对某个字段,进行自定义的验证。比如在注册的表单验证中,我们想要验证手机号码是否已经被注册过了,那么这时候就需要在数据库中进行判断才知道。对某个字段进行自定义的验证方式是,定义一个方法,这个方法的名字定义规则是:clean_fieldname。如果验证失败,那么就抛出一个验证错误。比如要验证用户表中手机号码之前是否在数据库中存在,那么可以通过以下代码实现。

# -*- encoding: utf-8 -*-
"""
@File    : forms.py
@Time    : 2020/7/17 16:27
@Author  : chen

表单文件:forms.py
"""
from django import forms     # 直接导入form
from django.core import validators
from .models import User


class MessageForm(forms.Form):
    # 渲染到前端页面
    title = forms.CharField(max_length=3, label='标题', min_length=2, error_messages={"min_length": '标题字符段不符合要求!'})
    content = forms.CharField(widget=forms.Textarea, label='内容', error_messages={"required": 'content字段必须填写!'})
    email = forms.EmailField(label='邮箱')
    reply = forms.BooleanField(required=False, label='回复')
    
    # 正则表达式编写验证器
    tel = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}", message="手机格式不正确")])
    pwd1 = forms.CharField(max_length=11)
    pwd2 = forms.CharField(max_length=11)

    # 手机号验证唯一性,针对某个字段进行自定义验证的命名必须是: clean_字段名
    def clean_tel(self):
        tel = self.cleaned_data.get('tel')
        result = User.objects.filter(tel=tel).exists()
        if result:
            raise forms.ValidationError("号码已经存在")
        return tel                            # 验证某个字段,就必须返回该字段
    
    # 验证两次密码是否一致
    def clean(self):            # 当之前的所有字段都验证之后,才会执行这个clean函数
        # 先调用父类中的clean方法,源码中返回的就是cleaned_data数据
        cleaned_data = super().clean()
        pwd1 = cleaned_data.get("pwd1")
        pwd2 = cleaned_data.get("pwd2")
        if pwd1 != pwd2:
            raise forms.ValidationError("密码不一致")
        return cleaned_data                 # 当验证多个字段时候,必须返回cleaned_data
           

对某个字段进行验证,如果验证数据的时候,需要针对多个字段进行验证,那么可以重写clean方法。比如要在注册的时候,要判断提交的两个密码是否相等。

注意:

  • 针对某个字段进行自定义验证的命名必须是: clean_字段名;
  • 验证某个字段,就必须返回该字段;
  • 当之前的所有字段都验证之后,才会执行这个clean函数;
  • 当验证多个字段时候,必须返回cleaned_data

提取错误信息

如果验证失败了,那么有一些错误信息是我们需要传给前端的。这时候我们可以通过以下属性来获取:

  • 1.form.errors:这个属性获取的错误信息是一个包含了html标签的错误信息。
  • 2.form.errors.get_json_data():这个方法获取到的是一个字典类型的错误信息。将某个字段的名字作为key,错误信息作为值的一个字典。
  • 3.form.as_json():这个方法是将form.get_json_data()返回的字典dump成json格式的字符串,方便进行传输。
  • 4.上述方法获取的字段的错误值,都是一个比较复杂的数据。比如以下:
    {'username': [{'message': 'Enter a valid URL.', 'code': 'invalid'}, {'message': 'Ensure this value has at most 4 characters (it has 22).', 'code': 'max_length'}]}

那么如果我只想把错误信息放在一个列表中,而不要再放在一个字典中。这时候我们可以定义一个方法,把这个数据重新整理一份。

重新编写form.errors.get_json_data()的输出错误信息;通过print(forms.get_errors)输出错误信息;这样就可以把某个字段所有的错误信息直接放在这个列表中。

class MyForm(forms.Form):
    username = forms.URLField(max_length=4)

    def get_errors(self):
        errors = self.errors.get_json_data()
        new_errors = {}
        for key,message_dicts in errors.items():
            messages = []
            for message in message_dicts:
                messages.append(message['message'])
            new_errors[key] = messages
        return new_errors
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值