分页器,Form组件,Form组件之常用字段及插件,自定义验证器,Form组件渲染前端,Form组件之渲染错误信息,全局钩子和局部钩子
Ⅰ 分页器推导
【一】问题引入
- 针对上一小节批量插入的数据
- 我们在前端展示的时候发现一个很严重的问题
- 一页展示了所有的数据,数据量太大,查看不方便
- 针对数据量大但又需要全部展示给用户观看的情况下
- 我们统一做法都是做分页处理
【二】分页推导
【1】理论
- 首先我们需要明确的时候
- get请求也是可以携带参数的
- 所以我们在朝后端发送查看数据的同时可以携带一个参数告诉后端我们想看第几页的数据
- 其次我们还需要知道一个点
- queryset对象是支持索引取值和切片操作的
- 但是不支持负数索引情况
【2】数据引入
(1)建表,并进行数据迁移
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=50)
price = models.CharField(max_length=255)
(2)插入数据
import os
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'second.settings')
import django
django.setup()
from user.models import Book
book_list = [Book(title=f'第{i}本书', price=1 * i) for i in range (10001)]
Book.objects.bulk_create(
book_list
)
# 因为 修改了一下插入数据量一万变成一万零一
# 所以下图是一万条数据
(3)数据在前端展示
- 路由
from django.urls import path
from user.views import home
urlpatterns = [
path('', home),
]
- 视图
from django.shortcuts import render, redirect,HttpResponse
from user.models import Book
def home(request):
book_all = Book.objects.all()
return render(request, 'home.html', locals())
- 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% for book_obj in book_all %}
<p>{{ book_obj.title }}:{{ book_obj.price }}</p>
{% endfor %}
</body>
</html>
- 前端展示
【3】分页器处理思路
# 在页面上展示出这 1万条数据
# 对页面上的数据进行分页处理
# 1.分页处理思路
# 在后端是通过 model.objects.all() ---> queryset 对象 [obj,obj .... ]
# queryset对象 ---> 能不能进行切片 --- > 可以进行切片
# 对 queryset 进行切片的时候支持正向切片但是不支持反向切片
# 2.在前端 展示数据
# 在前段瑶传入当前的页数
# 在后端根据指定的页数对数据进行切片
# 在前端 页面的 URL 上声明当前的页码
# http://127.0.0.1:8000/?page=1/
# http://127.0.0.1:8000/?page=2/
# http://127.0.0.1:8000/?page=3/
# 后端 request.GET.get('page') ---> 拿到当前的页码
# 3.在后端如何处理 queryset 进行切片
# 当前的 页码 current_page
# 每一页的数据量 size
# 结束的页码 end_page
# queryset[起始位置:结束位置]
# 4.模拟分页
# http://127.0.0.1:8000/?page=3/
# 当前在第三页 size = 5
# current_page = 3
# start_index = (current_page - 1 ) * size = 10
# start_index = current_page * size = 15
# 0:5
# 5:10
# 10:15
# 5.推导小结
# 当前页码 current_page
# 每一页的数据量 size
# 起始索引 start_index = (current_page - 1 ) * size
# 结束索引 end_index = current_page * size
# 获取用户想访问的页码 如果没有 默认展示第一页
current_page = request.GET.get("page",1)
# 由于后端接受到的前端数据是字符串类型所以我们这里做类型转换处理加异常捕获
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
# 还需要定义页面到底展示几条数据
per_page_num = 10 # 一页展示10条数据
# 需要对总数据进行切片操作 需要确定切片起始位置和终止位置
start_page = ?
end_page = ?
【4】自己的推导实现
- 路由
from django.urls import path
from user.views import home
urlpatterns = [
path('', home),
]
- 视图
from django.shortcuts import render, redirect,HttpResponse
from user.models import Book
def home(request):
# 【1】需要当前的页码
current_page = int(request.GET.get("page", 1))
# 【2】每一也得数量
size = 10
# 【3】计算起始和结束索引
start_index = (current_page - 1) * size
end_index = current_page * size
# 【4】获取所有的原始数据
book_all = Book.objects.all()
# 【5】如何计算一共需要多少页的数据?
# 内置函数 divmod(除数,被除数) -- 商和余数
all_count, last = divmod(book_all.count(), size)
all_page = all_count + 1 if last else all_count
# 【5】对原始数据切片处理
book_all = book_all[start_index:end_index]
first_page = current_page - 1
next_page = current_page + 1
li_list = []
if current_page <= 2:
current_page = 3
if current_page > 4:
li_list.append('<li><a href=""> ... </a></li>')
for page in range(current_page - 2, current_page + 3):
if page >= all_count or page == 1:
continue
a = reverse("home")
now_page = f'<li><a href="%s">第 {page} 页 </a></li>' % (f"{a}?page={page}",)
li_list.append(now_page)
if current_page < all_page - 2:
li_list.append('<li><a href=""> ... </a></li>')
return render(request, "home.html", locals())
- 前端
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="{% static 'js/jquery_min.js' %}"></script>
<script src="{% static 'js/boostrap_main.js' %}"></script>
<link href="{% static 'css/bootstrap_main.css' %}" rel="stylesheet">
</head>
<body>
{% for book_obj in book_all %}
<p>{{ book_obj.title }}:{{ book_obj.price }}</p>
{% endfor %}
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="{% url 'home' %}?page={{ first_page }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li><a href="{% url 'home' %}?page=1">第 1 页 </a></li>
{% for li_now in li_list %}
{{ li_now|safe }}
{% endfor %}
<li><a href="{% url 'home' %}?page={{ all_count }}">第 {{ all_count }} 页 </a></li>
<li>
<a href="{% url 'home' %}?page={{ next_page }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</body>
</html>
- 前端展示
- 最后略有小瑕疵
【三】分页器模板
- 路由
from django.urls import path
from user.views import home
urlpatterns = [
path('', home),
]
- 视图
from django.shortcuts import render, redirect,HttpResponse
from user.models import Book
from user.Pagination import Pagination
def home(request):
book_list = Book.objects.all()
current_page = request.GET.get("page", 1)
all_count = book_list.count()
page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=10)
page_queryset = book_list[page_obj.start:page_obj.end]
return render(request, 'booklist.html', locals())
- 放在app文件夹下
- Pagination.py
# -*-coding: Utf-8 -*-
# @File : paginations .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2024/7/1
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
- 前端
- booklist.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="{% static 'js/jquery_min.js' %}"></script>
<script src="{% static 'js/boostrap_main.js' %}"></script>
<link href="{% static 'css/bootstrap_main.css' %}" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% for book in page_queryset %}
<p>{{ book.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
</div>
</div>
</div>
</body>
</html>
- 前端展示
【四】总结
# 第一步 创建一个 py 文件
# 第二步 将分页器源码复制进去
# 第三步 在视图函数中使用
# 第一步 导入分页器类 from user.paginations import Pagination
# 第二步 查询表模型所有数据 book_list = Book.objects.all()
# 第三步 获取当前页面的页码 current_page = request.GET.get("page", 1)
# 第四步 创建分页器对象 all_count = book_list.count()
# page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=10)
# 第五步 对原始数据进行切片 page_queryset = book_list[page_obj.start:page_obj.end]
# 第六步 返回给前端进行渲染 return render(request, 'booklist.html', locals())
# 第四步前端使用
# 第一步 遍历 切分好 的 page_queryset 进行渲染
'''
{% for book in page_queryset %}
<p>{{ book.title }}</p>
{% endfor %}
'''
# 第二步 添加分页器
'''
{{ page_obj.page_html|safe }}
'''
Ⅱ Form组件
【一】前言引入
# serializer : 序列化器
# FORM组件 内置也有序列化器 并且能对参数进行校验
【二】Django的表单系统
# 在Django内部 可以通过 Python代码生成 form 表单中的标签
# form组件
【1】使用表单
- 我们之前在HTML页面中利用form表单向后端提交数据时
- 都会写一些获取用户输入的标签并且用form标签把它们包起来。
- 与此同时我们在好多场景下都需要对用户的输入做校验
- 比如校验用户是否输入
- 输入的长度和格式等正不正确。
- 如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。
<h1>form表单登陆页面</h1>
<form action="">
<p>username : <input type="text" maxlength="5" minlength="3"></p>
<p><input type="submit"></p>
</form>
【三】需求介绍
- 写一个注册功能
- 获取用户名和密码,利用form表单提交数据
- 在后端判断用户名和密码是否符合一定的条件
- 用户名不能以 nb_ 开头
- 密码不能少于三位
- 如果符合条件需要你将提示信息展示到前端页面
【三】实现
【1】原始form表单实现
- 路由
from django.urls import path
from user.views import login, register_view, home_page,beautyful,home,right
urlpatterns = [
path('right/', right, name="right"),
]
- 视图
from django.shortcuts import render, redirect,HttpResponse
def right(request):
back_dict = {
"username": "",
"password": "",
}
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
if username.startswith("nb_"):
back_dict["username"] = f"不能以 nb_ 开头"
if len(password) != 3:
back_dict["password"] = f"密码长度必须为3"
return render(request, 'index.html', locals())
return render(request, 'index.html', locals())
- 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>form表单登陆页面</h1>
<form action="" method="post">
<p>
username : <input type="text" name="username">
<span style="color: red">{{ back_dict.username }}</span>
</p>
<p>
password : <input type="text" name="password">
<span style="color: red">{{ back_dict.password }}</span>
</p>
<p><input type="submit"></p>
</form>
</body>
</html>
- 前端展示
【2】Django中的form组件
# 帮助我们渲染页面
# 校验数据
# 展示提示信息
# 为什么数据校验都是发生在后端而不是前端
# 前端的代码很容易就被修改了
- 路由
from django.urls import path
from user.views import login
urlpatterns = [
path('login/', login),
]
- userForms.py 放在app下的py文件
# 【一】引入Django的 form组件
from django import forms
# 【二】创建一个 form 的组件类
# 继承 form 组件的类 forms.Form
class LoginForm(forms.Form):
# 【三】定义组件属性
# 【1】用户名 : 名字中不能以 nb_ 开头
username = forms.CharField(
max_length=8,
min_length=3
)
# 【2】密码 长度不能小于三位
password = forms.CharField(
min_length=3
)
# 【3】邮箱
email = forms.EmailField()
- 视图
from django.shortcuts import render, redirect,HttpResponse
# 【一】导入自定义的组件类
from user.userForms import LoginForm
def login(request):
# 【二】实例化组件类得到一个组件对象
login_form = LoginForm()
# 【三】渲染 标签
# 【1】直接渲染 上面的 form 对象
# {{ login_form }}
if request.method == "POST":
# 【四】form组件帮我们校验数据
# 【1】将前段输入的数据交给 form 组件进行校验
data = request.POST
data = {'username': '12345678', 'password': '13', 'email': '333@qq.com',"gender":"male"}
form_obj = LoginForm(data) # False
# 【2】触发校验 is_valid
# print(form_obj.is_valid())
if not form_obj.is_valid():
# 【3】查看错误信息
print(form_obj.errors)
# <ul class="errorlist"><li>password<ul class="errorlist"><li>Ensure this value has at least 3 characters (it has 2).</li></ul></li></ul>
# 【4】查看校验成功的信息
print(form_obj.cleaned_data) # {'username': '12345678', 'email': '333@qq.com'}
# 【5】查看当前那些字段数据进行过校验
print(form_obj.changed_data) # ['username', 'password', 'email']
# 【传入的参数没有】定义了但是没传 就会报错 提示参数是必须的
# 【传入的参数多了】没定义但是传了 就不会管 参数不会被校验
return render(request, 'index.html', locals())
- 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>form表单登陆页面</h1>
<form action="" method="post">
{{ login_form }}
{{ form_obj.errors }}
<p><input type="submit"></p>
</form>
</form>
</body>
</html>
- 前端展示
Ⅲ Form组件之常用字段及插件
【一】引入
- 创建Form类时主要涉及到 【字段】 和 【插件】
- 字段用于对用户请求数据的验证
- 插件用于自动生成HTML;
【二】Field.clean(value)
- 注意:这里说的是字段Field的clearn方法,不是表单Form的clean方法。
- 虽然表单字段的Field类主要使用在Form类中,但也可以直接实例化它们来使用,以便更好地了解它们是如何工作的。
- 每个Field的实例都有一个clean()方法,它接受一个参数,然后返回“清洁的”数据或者抛出一个django.forms.ValidationError异常:
from django import forms
f = forms.EmailField()
f.clean('foo@example.com')
# 'foo@example.com'
f.clean('invalid email address')
# Traceback (most recent call last):
# ...
# ValidationError: ['Enter a valid email address.']
- 这个clean方法经常被我们用来在开发或测试过程中对某个字段的数据进行验证和测试。
【三】核心字段参数
【1】required
- 设置表单项是否为必填项,如果未填写必填项,提交表单时将触发验证错误。
【2】label
- label参数用来给字段添加提示信息。
- 如果没有设置这个参数,那么就用字段的首字母大写名字。
【3】 label_suffix
- Django默认为上面的label参数后面加个冒号后缀,如果想自定义,可以使用label_suffix参数。
【4】widget
- 最重要的参数之一,指定渲染Widget时使用的widget类,也就是这个form字段在HTML页面中是显示为文本输入框?密码输入框?单选按钮?多选框?还是别的....
【5】help_text
- 该参数用于设置字段的辅助描述文本。
【6】error_messages
- 该参数允许你覆盖字段引发异常时的默认信息。
- 传递的是一个字典,其键为你想覆盖的错误信息。
【7】disabled
- 设置有该属性的字段在前端页面中将显示为不可编辑状态。
- 该参数接收布尔值,当设置为True时,使用HTML的disabled属性禁用表单域,以使用户无法编辑该字段。
- 即使非法篡改了前端页面的属性,向服务器提交了该字段的值,也将依然被忽略。
【8】代码展示
- 路由
from django.urls import path
from user.views import form_check
urlpatterns = [
path('form_check/', form_check, name="form_check"),
]
- 视图
from django.shortcuts import render, redirect, HttpResponse
from django import forms
def form_check(request):
class CheckForm(forms.Form):
username = forms.CharField(
# 【1】最大长度
max_length=5,
# 【2】最小长度
min_length=3,
# 【3】限制当前字段非必须传入
# required=False,
# 【4】label 就是前端显示在 input 输入框之前的提示文本
label="用户名",
# 【5】input 框内的默认值
# initial="请输入用户名"
# 【6】控制 提示内容和 input 框之间的 字符
label_suffix="&",
# 【7】控制前端样式
widget=forms.widgets.TextInput(
# attrs 可以放前端的 键值属性
attrs={
'class': 'form-control',
# "id": "username",
"placeholder": "请输入用户名"
}
),
# 【8】显示在 input 框下面的注释文本
help_text="用户名的注解文本",
# 【9】存放错误信息
error_messages={
"required": "用户名必须传",
"min_length": "你怎么吃的这么少!"
},
# 【10】不可选中状态
disabled=True,
)
# password输入框
password = forms.CharField(
widget=forms.widgets.PasswordInput(
)
)
# Select单选框
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# Select多选框
hobby_one = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# checkbox单选框
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# checkbox多选框
hobby_two = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
form_obj = CheckForm({
"username": "1"
})
# 【1】先调用校验
form_obj.is_valid()
# 【2】获取到校验失败的数据
print(form_obj.errors)
# 【3】获取到校验成功的数据
print(form_obj.cleaned_data)
return render(request, 'forms_check.html', locals())
- 前端
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="{% static 'js/jquery_min.js' %}"></script>
<script src="{% static 'js/boostrap_main.js' %}"></script>
<link href="{% static 'css/bootstrap_main.css' %}" rel="stylesheet">
</head>
<body>
{{ form_obj }}
</body>
</html>
【9】更多字段
# Field
# required=True, 是否允许为空
# widget=None, HTML插件
# label=None, 用于生成Label标签或显示内容
# initial=None, 初始值
# help_text='', 帮助信息(在标签旁边显示)
# error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
# validators=[], 自定义验证规则
# localize=False, 是否支持本地化
# disabled=False, 是否可以编辑
# label_suffix=None Label内容后缀
#
#
# CharField(Field)
# max_length=None, 最大长度
# min_length=None, 最小长度
# strip=True 是否移除用户输入空白
#
# IntegerField(Field)
# max_value=None, 最大值
# min_value=None, 最小值
#
# FloatField(IntegerField)
# ...
#
# DecimalField(IntegerField)
# max_value=None, 最大值
# min_value=None, 最小值
# max_digits=None, 总长度
# decimal_places=None, 小数位长度
#
# BaseTemporalField(Field)
# input_formats=None 时间格式化
#
# DateField(BaseTemporalField) 格式:2015-09-01
# TimeField(BaseTemporalField) 格式:11:12
# DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
#
# DurationField(Field) 时间间隔:%d %H:%M:%S.%f
# ...
#
# RegexField(CharField)
# regex, 自定制正则表达式
# max_length=None, 最大长度
# min_length=None, 最小长度
# error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
#
# EmailField(CharField)
# ...
#
# FileField(Field)
# allow_empty_file=False 是否允许空文件
#
# ImageField(FileField)
# ...
# 注:需要PIL模块,pip3 install Pillow
# 以上两个字典使用时,需要注意两点:
# - form表单中 enctype="multipart/form-data"
# - view函数中 obj = MyForm(request.POST, request.FILES)
#
# URLField(Field)
# ...
#
#
# BooleanField(Field)
# ...
#
# NullBooleanField(BooleanField)
# ...
#
# ChoiceField(Field)
# ...
# choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
# required=True, 是否必填
# widget=None, 插件,默认select插件
# label=None, Label内容
# initial=None, 初始值
# help_text='', 帮助提示
#
#
# ModelChoiceField(ChoiceField)
# ... django.forms.models.ModelChoiceField
# queryset, # 查询数据库中的数据
# empty_label="---------", # 默认空显示内容
# to_field_name=None, # HTML中value的值对应的字段
# limit_choices_to=None # ModelForm中对queryset二次筛选
#
# ModelMultipleChoiceField(ModelChoiceField)
# ... django.forms.models.ModelMultipleChoiceField
#
#
#
# TypedChoiceField(ChoiceField)
# coerce = lambda val: val 对选中的值进行一次转换
# empty_value= '' 空值的默认值
#
# MultipleChoiceField(ChoiceField)
# ...
#
# TypedMultipleChoiceField(MultipleChoiceField)
# coerce = lambda val: val 对选中的每一个值进行一次转换
# empty_value= '' 空值的默认值
#
# ComboField(Field)
# fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
# fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
#
# MultiValueField(Field)
# PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
#
# SplitDateTimeField(MultiValueField)
# input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
# input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
#
# FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
# path, 文件夹路径
# match=None, 正则匹配
# recursive=False, 递归下面的文件夹
# allow_files=True, 允许文件
# allow_folders=False, 允许文件夹
# required=True,
# widget=None,
# label=None,
# initial=None,
# help_text=''
#
# GenericIPAddressField
# protocol='both', both,ipv4,ipv6支持的IP格式
# unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
#
# SlugField(CharField) 数字,字母,下划线,减号(连字符)
# ...
#
# UUIDField(CharField) uuid类型
Ⅳ 自定义验证器
# 第一步 定义一个 Form 组件类
# 第二步 Form组件类继承 forms.Form
# 第三步 定义字段验证规则 forms.CharField()
# 第四步 定义一个验证函数 函数的参数就是 当前需要验证的值
# 第五步 将验证函数添加到上面的验证规则里面 forms.CharField(validators=[验证函数])
- 路由
from django.urls import path
from user.views import form_check
urlpatterns = [
path('form_check/', form_check, name="form_check"),
]
- 视图
from django.shortcuts import render, redirect, HttpResponse
from django import forms
def form_check(request):
# 【场景】:我们有自己的规则需要对参数进行校验 ---> 自己主动写 验证器
# 比如需要对手机号的格式进行验证的时候内置的验证规则是没有的!所以我们需要自己主动写
import re
from django.core.exceptions import ValidationError
# 创建一个验证器函数
def phone_validate(value):
# print(f"phone_validate :>>> {value}")
# phone_validate :>>> 15936369696
# 手机号格式匹配规则
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
# 通过 正则表达式对象匹配输入的内容
if not mobile_re.match(value):
# 抛出异常 ---> 会被 Form 组件捕获到
raise ValidationError('手机号码格式错误')
# <ul class="errorlist"><li>phone<ul class="errorlist"><li>手机号码格式错误</li></ul></li></ul>
# 【1】先定义一个验证规则类
class CheckForm(forms.Form):
# 验证电话格式
phone = forms.CharField(
label="手机号",
validators=[phone_validate]
)
# 自定义的手机号方便检验 也可由前端输入传进来
form_obj = CheckForm({
"phone": "159363696"
})
# 【1】先调用校验
form_obj.is_valid()
# 【2】获取到校验失败的数据
print(form_obj.errors)
# 【3】获取到校验成功的数据
print(form_obj.cleaned_data)
return render(request, 'forms_check.html', locals())
- 前端
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="{% static 'js/jquery_min.js' %}"></script>
<script src="{% static 'js/boostrap_main.js' %}"></script>
<link href="{% static 'css/bootstrap_main.css' %}" rel="stylesheet">
</head>
<body>
{{ form_obj }}
</body>
</html>
Ⅴ Form组件渲染前端
# 提取每一个标签的规则
# form.label 获取到的事在 form组件类上定义的form 中 的 label 属性
# form.errors 提取当前form组件验证当前字段数据对应的错误信息
【一】直接渲染
<h1>form表单登陆页面</h1>
<form action="" method="post">
{{ login_form }}
{{ form_obj.errors }}
<p><input type="submit"></p>
</form>
- 前端展示
【二】渲染成列表元素 无序列表
<h1>form表单登陆页面</h1>
<form action="" method="post">
{{ login_form.as_ul }}
<p><input type="submit"></p>
</form>
- 前端展示
【三】渲染成P标签
<h1>form表单登陆页面</h1>
<form action="" method="post">
{{ login_form.as_p }}
<p><input type="submit"></p>
</form>
- 前端展示
【四】渲染成表格标签
<h1>form表单登陆页面</h1>
<form action="" method="post">
<table>
{{ login_form.as_table }}
</table>
<p><input type="submit"></p>
</form>
- 前端展示
Ⅵ Form组件之渲染错误信息
- 路由
from django.urls import path
from user.views import login
urlpatterns = [
path('login/', login),
]
- 视图 form 组件对象
from django.shortcuts import render, redirect, HttpResponse
from user.userForms import LoginForm
def login(request):
# 【二】实例化组件类得到一个组件对象
login_form = LoginForm()
# 【三】渲染 标签
# 【1】直接渲染 上面的 form 对象
# {{ login_form }}
if request.method == "POST":
# 【四】form组件帮我们校验数据
# 【1】将前段输入的数据交给 form 组件进行校验
data = request.POST
data = {'username': '12345678', 'password': '13', 'email': '333@qq.com', "gender": "male"}
form_obj = LoginForm(data) # False
# 【2】触发校验 is_valid
# print(form_obj.is_valid())
if not form_obj.is_valid():
# 【3】查看错误信息
print(form_obj.errors)
# <ul class="errorlist"><li>password<ul class="errorlist"><li>Ensure this value has at least 3 characters (it has 2).</li></ul></li></ul>
# 【4】查看校验成功的信息
print(form_obj.cleaned_data) # {'username': '12345678', 'email': '333@qq.com'}
# 【5】查看当前那些字段数据进行过校验
print(form_obj.changed_data) # ['username', 'password', 'email']
# 【传入的参数没有】定义了但是没传 就会报错 提示参数是必须的
# 【传入的参数多了】没定义但是传了 就不会管 参数不会被校验
return render(request, 'index.html', locals())
- 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>form表单登陆页面</h1>
<form action="" method="post">
{% for form in form_obj %}
{{ form.label }}
{{ form }}
{{ form.errors }} # 把错误信息渲染到前端
{% endfor %}
<p><input type="submit"></p>
</form>
</body>
</html>
- 前端展示
Ⅵ 全局钩子和局部钩子
【一】什么是钩子函数
钩子函数就是用来将某个或某些数据单独提取出来进行校验的函数
- 在forms组件中
- 钩子函数(Hooks)是用来在特定事件发生时执行自定义逻辑的函数。
- 它们提供了一种创建交互性和动态行为的方式,并可以用于处理表单的各种状态和数据。
【二】常见的钩子函数
【1】onInputChange
- 当输入框的值发生变化时触发。
- 你可以通过这个钩子函数获取最新的输入值,并进行相应的处理。
【2】onSubmit
- 当表单提交时触发。你可以在这个钩子函数中获取表单中的所有字段值,并进行数据验证、提交或其他操作。
【3】onBlur
- 当输入框失去焦点时触发。
- 你可以在这个钩子函数中执行验证操作
- 例如检查输入是否符合预期的格式或是否满足某些条件。
【4】onFocus
- 当输入框获得焦点时触发。
- 你可以在这个钩子函数中执行一些针对输入框焦点状态的逻辑操作
- 例如显示一个下拉列表或提示信息。
【5】onReset
- 当表单重置时触发。
- 你可以在这个钩子函数中对表单进行一些初始化操作
- 将表单恢复到初始状态。
【三】全局钩子和局部钩子
- 除了上述常见的钩子函数外,不同的forms组件可能还有其它特定的钩子函数,用于处理更具体的需求。
- 在使用特定的forms组件之前,建议查阅相应的文档或官方手册,以了解可用的钩子函数及其使用方式。
- 在特定的节点自动触发完成响应动作
- 钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则
- 在forms组件中有两类钩子
- 局部钩子
- 当需要给某个字段增加校验规则的时候使用
- 在自定义的 forms 类中添加类方法即可
- 全局钩子
- 当需要给多个字段增加校验规则的时候使用
- 在自定义的 forms 类中添加类方法即可
- 当需要给多个字段增加校验规则的时候使用
- 局部钩子
【四】案例
# 【一】在Form组件类中定义
# 【1】局部钩子
# 校验用户名中不能包含 nb_
# 【2】全局钩子
# 校验用户名和密码是否一致
# 【二】以前
# 在视图函数中将用户名和密码提取出来
# 做校验
# 再返回给前端渲染
- 路由
from django.urls import path
from user.views import register_one
urlpatterns = [
path('register_one/', register_one, name="register_one"),
]
- 视图
from django.shortcuts import render, redirect, HttpResponse
from user.userForms import RegisterForm
def register_one(request):
form_obj = RegisterForm()
if request.method == "POST":
form_obj = RegisterForm(request.POST)
if not form_obj.is_valid():
print(form_obj.errors)
print(form_obj.cleaned_data)
return render(request, 'registerone.html', locals())
return render(request, 'registerone.html', locals())
- userForms.py里面的py代码
class RegisterForm(forms.Form):
username = forms.CharField(
max_length=8,
min_length=3,
label="用户名"
)
password = forms.CharField(
min_length=3
)
# 能进入到局部狗子的前提是 上面定义的字段的校验规则已经符合才行
# 验证当前用户名不能以 nb_ 开头
def clean_username(self):
# 局部狗子 :>>>> {'username': 'dream'}
print(f"局部狗子 :>>>> {self.cleaned_data}")
# 应该在局部钩子中将参数取出来 然后返回的实时 键对应的值
username = self.cleaned_data.get("username")
if username.startswith("nb_"):
# 方式一 : 抛出异常
# raise forms.ValidationError("用户名不能以 nb_ 开头")
# 方式二 : 给字段添加错误信息
self.add_error(
"username", "用户名不能以 nb_ 开头"
)
# 在局部钩子和全局钩子函数中 拿出来的值要放回去
return username
# 全局钩子 : 可以对所有数据进行提取并校验
def clean(self):
# 去用户名和密码
username = self.cleaned_data.get("username")
password = self.cleaned_data.get("password")
if username != password:
# raise forms.ValidationError("用户名和密码不一致")
self.add_error(
"username", "用户名和密码不一致"
)
self.add_error(
"password", "密码和用户名不一致"
)
# 钩子函数用完的数据要记得返回回去
return self.cleaned_data
- 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{{ form_obj.as_p }}
<p>
<input type="submit">
</p>
</form>
</body>
</html>
- 前端页面展示
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY