1. form组件引入
写一个注册功能
获取用户名密码,利用form表单提交.
在后端判断用户名字和密码是否符合一些条件.
用户名不能含有 xx,密码不能少与三位.
如果不符合要求将信息展示到前端页面中.
* span 行级标签 字符有多则占多大的空间,空字符则看不出.
# 1. 用户登入
url('^register/', views.register)
# 0. 用户注册
def register(request):
# 0.1 返回注册页面
return render(request, 'register.html')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<!--0. 动态获取静态资源目录名称-->
{% load static %}
<!--1. 导入 jQuery js 文件-->
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<!--2. 导入 bootstrap css 文件-->
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<!--3. 导入 bootstrap js 文件-->
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
<style>
span {
color: red;
}
</style>
</head>
<body>
<div>
<!-- 4. form表单 -->
<form action="" method="post">
<p>
<label for="username">名称:
<input type="text" id="username" name="username">
<span id="sp1"></span>
</label>
</p>
<p>
<label for="password">密码:
<input type="password" id="password" name="password">
<span id="sp2"></span>
</label>
</p>
<p><input type="submit"></p>
</form>
</div>
<script>
// 5. 绑定事件
$(':submit').on('click', function () {
// 6. 获取表单中的数据
var username = $(':text').val()
var password = $(':password').val()
// 7. 禁止后续事件 禁止submit的提交
event.preventDefault()
// 8. ajax 请求
$.ajax({
url: '',
type: 'post',
data: {username: username, password: password},
success: function (args) {
if (args.username_bug === '✓') {
$('#sp1').text('✓')
} else {
$('#sp1').text(args.username_bug)
}
if (args.password_bug === '✓') {
$('#sp2').text('✓')
} else {
$('#sp2').text(args.password_bug)
}
}
})
})
</script>
</body>
</html>
from django.shortcuts import render
# Create your views here.
# 0. 用户注册
def register(request):
# 0.2 判断 ajax请求
if request.is_ajax():
# 0.3 获取ajax的谁
username = request.POST.get('username')
password = request.POST.get('password')
print(username,password)
# 0.4 组织返回的数据格式
back_dict = {'code': 200, 'username_bug': '√', 'password_bug': '√'}
# 0.5 判断用户名与密码是否符合条件
if 'xx' in username:
back_dict['username_bug'] = '用户名不能含有xx'
if not username:
back_dict['username_bug'] = '用户名称不能为空'
if 3 > len(password):
back_dict['password_bug'] = '用户密码不能少于三位'
if not password:
back_dict['password_bug'] = '用户密码不能为空'
# 0.6 返回json格式字符串
from django.http import JsonResponse
print(back_dict)
return JsonResponse(back_dict)
# 0.1 返回注册页面
return render(request, 'register.html')
2. forms组件
forms组件的主要功能:
1.生成页面可用的HTML标签
2.后端对获取数据进行校验 数据校验
3.数据不符要求在前端展示 展示错误提示信息 并 保留上次输入内容
ps:
数据校验前端可有可无,后端必须有,全端页面的校验功能不完善.
能利用爬虫程序直接绕开前端页面向后端提交数据.
例:
购物网站
选中货物之后会计算价格发送给后端,价格的值可能被篡改,如果后端不做价格校验···
实际中获取到用户选择的商品的主键值,后端查询到的价格后在计算一次
如果前后端计算结果一致,完成支付,否则直接拒绝.
一般会进行前后端双重验证,保险、
2.1 导入组件
写在views.ps 中
# 1. forms 组件
# 1.1 导入组件
from django import forms
# 1.2 定义类
class MyForm(forms.Form):
# 1.3 用户名密码 字符类型 min_length 最小长度 max_length 最大长度
username = forms.CharField(min_length=3, max_length=8)
password = forms.CharField(min_length=3, max_length=8)
# 1.4 邮箱格式 xxx@qq.com
email = forms.EmailField()
2.2 检验数据
1. tests.py 中测试
2. pycharm Console 中测试
form对象 = views.MaForm(测试的数据-->dict)
可以多传,多传的字段直接被忽略.
不能少传,必须给存在的字段写入值.
对象.is_valid() 判断数据是否全部合法,返回布尔值.
对象.cleaned_data 清理不符合的数据,也就就符合要求的数据.
对象.errors 不符合的数据及错误的原因
# app01 应用下 tests.py
from django.test import TestCase
# Create your tests here.
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django7_2.settings")
import django
django.setup()
# 0. 导入 视图层
from app01 import views
username = input('username>>>:').strip()
password = input('password>>>:').strip()
email = input('email>>>:').strip()
# 1. 实例化对象
form_obj = views.MyForm({'username': username, 'password': password, 'email': email})
# 2. 判断数据是否符合要求
print(f'数据是否符合要求>>>:{form_obj.is_valid()}')
# 3. 过滤出符合要求的数据 --> dict
print(f'符合要求的数据>>>:{form_obj.cleaned_data}')
# 4. 查看不符合要求的数据和原因
print(f'不符合要求的数据>>>:{form_obj.errors}')
tests.py中
存在的字段必须给参数.
python Console
多出的参数被忽略
2.3 渲染标签
forms组件只会渲染获取用户输入的便签(input select radio checkbox)
不会渲染提交按键
# 2. 用户注册 forms 组件
url('^register_forms/', views.register_forms)
# 2. 注册 + forms组件
def register_forms(request):
# 2.1 生成对象
forms_obj = MyForm()
# 2.2 返回页面 & forms对象
return render(request, 'register_forms.html')
第一种渲染方式:
代码书写极少,封装程度太高,不便于后续的拓展,一般情况下只在本地测试使用.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> forms组件 使用方式1 </title>
</head>
<body>
{{ forms_obj.as_p }}
<br>
<br>
{{ forms_obj.as_ul }}
<br>
<br>
{{ forms_obj.as_table}}
</body>
</html>
第二种渲染方式:
可扩展性强,但书写代码太多
label属性默认展示的字段名称,也可以自己修改,直接在类中为label属性设置参数.
email = forms.EmailField(label='邮箱')
form_obj.username.label 获取属性的名称,如果没有设置就拿字段名称.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> forms组件 使用方式2 </title>
</head>
<body>
{{ forms_obj.username }}{{ forms_obj.password }}
<br>
<br>
<p>{{ forms_obj.username.label }} :{{ forms_obj.username }}</p>
<p>{{ forms_obj.password.label }} :{{ forms_obj.password }}</p>
<p>{{ forms_obj.email.label }} :{{ forms_obj.email }}</p>
</body>
</html>
第三种渲染方式: (推荐)
代码简单扩展性高
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>form组件获取输入</title>
</head>
<body>
<form action="" method="post">
<p></p>
{% for form in form_obj %}
<p>{{ form.label }}:{{ form }}</p>
{% endfor %}
<input type="submit" class="btn btn-info">
</form>
</body>
</html>
2.4 渲染错误信息
在from表单中点击提交会触发前端的数据检验,但是前端的检验能被篡改,
在form标签中添加 novalidate 属性,让浏览器不做校验.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> forms组件 使用方式3 </title>
</head>
<body>
<form action="" method="post">
{% for form in forms_obj %}
<p>{{ form.label }}:{{ form }}</p>
<span style="color: red">{{ form.errors }}</span>
{% endfor %}
<input type="submit">
</form>
</body>
</html>
在后端做数据校验:
1. 获取前端POST请求数据
2. Forms对象(字典的格式数据)
request.POST可以看成一个字典
# 2. 注册 + forms组件
def register_forms(request):
# 2.1 生成对象
forms_obj = MyForm()
# 2.2 判断请求方式
if request.method == 'POST':
# 2.3 获取POST数据
print(request.POST) # 看成是一个字典
# 2.4 检验数据
forms_obj = MyForm(request.POST)
# 返回页面 & forms对象
return render(request, 'register_forms.html', locals())
request.POST的数据
<QueryDict: {'username': ['kid'], 'password': ['123'], 'email': ['1360012768@qq.com']}>
必备条件 get请求和post传给html的变量名必须一样,共有用一个渲染方式.
forms组件当数据不合法的情况下,会保存你上次的数据,让用户可以基于之前的结果进行修改.
{{ form.errors }} 获取的是ul>li嵌套标签
{{ form.errors.0 }} 获取的是ul>li标签中问文本信息
前端错误信息是英文的,自定义中文错误信息.
error_messages中设置
min_length 不符合最小位数对应的报错信息
max_length 不符合最大位数对应的报错信息(几乎不会触发)
invalid 不符合邮箱格式对应的报错信息
required 不输入信息对应的报错信息
# 1.2 定义类
class MyForm(forms.Form):
# 1.3 用户名密码 字符类型 min_length 最小长度 max_length 最大长度
username = forms.CharField(min_length=3, max_length=8,
error_messages=
{
'min_length': '用户名不能少于3位数!',
'max_length': '用户名不能大于8位数!'
})
password = forms.CharField(min_length=3, max_length=8,
error_messages={
'min_length': '密码不能少于3位数!',
'max_length': '密码不能超过8位数!'
})
# 1.4 邮箱格式 xxx@qq.com
email = forms.EmailField(
error_messages={
'invalid': '邮箱格式不正确',
'required': '邮箱不能为空'
}
)
调整一下span标签的位置.报错信息和输入款在一个p标签中.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> forms组件 使用方式3 </title>
<!--0. 动态获取静态资源目录名称-->
{% load static %}
<!--1. 导入 jQuery js 文件-->
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<!--2. 导入 bootstrap css 文件-->
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<!--3. 导入 bootstrap js 文件-->
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<form action="" method="post" novalidate>
{% for form in forms_obj %}
<p>{{ form.label }}:{{ form }} <span style="color: red">{{ form.errors.0 }}</span></p>
{% endfor %}
<input type="submit">
</form>
</body>
</html>
密码为空
数据不符合要求.
max_length设置之后,不篡改前端代码.
在输入框输入数据的时候不能超过这个限制.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4NX4re5Y-1647596259671)(C:\Users\13600\Desktop\11.png)]
2.4 钩子函数(HOOK)
在特定的节点自动触发完成响应操作.
钩子函数在forms组件中类似于第二道关卡,能够设置自定义校验规则.
在forms组件中有两类钩子
1. 局部钩子
获取单个字段的数据,增加额外的校验规则.
2. 全局钩子
获取全部字段的数据,为多字段增加校验规则.
钩子函数只有在通过了字段中设置的规则,才能触发对应该字段的钩子函数.
第一层规则过了的数据会被打包cleaned_data中.
第二层则是通过钩子函数将cleaned_data中的数据取出来在作判断.
通过则直接将勾取的数据返回.
不通过则通过 .add_error('字段', '错误信息') 设置错误的提示.
例题:
1.校验用户名只能是(数字 字母 下划线的组合) 只校验 username 字段 单个字段 使用局部钩子
2.检验密码和确认密码是否一致 校验 password confirm 多个字段 使用全局钩子
# 1.2 定义类
class MyForm(forms.Form):
# --------------------------第一层规则-------------------------------
# 1.3 用户名密码 字符类型 min_length 最小长度 max_length 最大长度
username = forms.CharField(min_length=3, max_length=8, label='名称',
error_messages=
{
'min_length': '用户名不能少于3位数!',
'max_length': '用户名不能大于8位数!',
'required': '用户名不能为空'
})
# 1.4 用户密码 字段类型
password = forms.CharField(min_length=3, max_length=8, label='密码',
error_messages={
'min_length': '密码不能少于3位数!',
'max_length': '密码不能超过8位数!',
'required': '密码不能为空'
})
# 1.5 确认密码
confirm = forms.CharField(min_length=3, max_length=8, label='确认密码',
error_messages={
'min_length': '密码不能少于3位数!',
'max_length': '密码不能超过8位数!',
'required': '密码不能为空'
})
# 1.6 邮箱格式 xxx@qq.com
email = forms.EmailField(label='邮箱',
error_messages={
'invalid': '邮箱格式不正确',
'required': '邮箱不能为空'
}
)
# --------------------------第二层规则-------------------------------
# 1.7 局部钩子 clean_字段名
def clean_username(self):
# 1.7.0 获取字段的值
username = self.cleaned_data.get('username')
# 1.7.1 增加额外的规则 只能是字母数字下划线的组合,只能是字母开头
import re
res = re.findall(r'^[a-zA-Z]([a-zA-Z0-9_]{3,8})$', username)
# 返回一个列表,不有匹配到数据就不为空
print(res)
# 1.7.2 没有匹配到数据就说明用户名称不符合要求,为字段添加错误信息
if not res: # 两个参数 第一个参数是字段 第二次参数是 错误的信息
self.add_error('username', '只能是字母数字下划线的组合,只能是字母开头!')
return username
# 1.8 全局钩子 获取全部的字段信息
def clean(self):
# 1.8.0 获取字段的信息
password = self.cleaned_data.get('password')
confirm = self.cleaned_data.get('confirm')
# 增加额外的规则
# 1.8.1 对密码进行检验
import re
res = re.findall(r'^[a-zA-Z]([a-zA-Z0-9_]{3,8})$', password)
if not res: # 两个参数 第一个参数是字段 第二次参数是 错误的信息
self.add_error('password', '只能是字母数字下划线的组合,只能是字母开头!')
# 1.8.2 两次密码一致校验
if password != confirm: # 两次密码不一直为 confirm二次确认密码添加错误信息
self.add_error('confirm', '两次密码不一致!')
# 1.8.3 返回所有的数据
return self.cleaned_data
2.5 所有字段及参数
针对不同的input框有不同的字段类型和标签样式.
1.针对不同的类型input修改
text 普通文本(默认)
password 密文
date 日期
···
2.参数
label 字段名
error_messages 自定义报错信息
initial 设置默认值 也可以在在样式中设置value
required 控制字段是否必填 required=False 可以不填
3. 字段设置样式 多个属性,空格隔开
widget = forms.widget.TextTnput() 普通文本
widget = forms.widget.PasswordTnput() 密文
widget = forms.widget.EmailTnput() 邮箱
widget = forms.widget.TextTnput(attrs={'class':'form-control c1 c2', 'value':'kid'})
1. text文本框
字段类型 CharField 对应文本参数 TextInput
# 3. 定义类
class MyForm2(forms.Form):
# 3.1 用户名
username = forms.CharField(
label='名称',
# input 框内的值
initial='字母数字下划线的组合,只能是字母开头',
# 第一层规则
min_length=3, max_length=8,
# 错误提示
error_messages={'min_length': '名称至少3位!', 'max_length': '名称最多8位!'},
# 样式
widget=forms.widgets.TextInput(
attrs={'class': 'form-control', 'style': 'color: green;'}),
)
<QueryDict: {'username': ['kid']}>
2. password密文
字段类型 CharField 对应密文参数 PasswordInput
# 3.2 密码
password = forms.CharField(
label='密码',
initial='不要太简单',
# 第一层规则
min_length=3, max_length=8,
# 错误提示
error_messages={
'min_length': '密码至少3位',
'max_length': '密码最长8位'},
# 样式
widget=forms.widgets.PasswordInput(
attrs={'class': 'form-control'}), )
# 3.2 密码
password = forms.CharField(
label='密码',
# initial='不要太简单',
# 第一层规则
min_length=3, max_length=8,
# 错误提示
error_messages={
'min_length': '密码至少3位',
'max_length': '密码最长8位'},
# 样式
widget=forms.widgets.PasswordInput(
attrs={'class': 'form-control', 'value': '不要太简单'}), )
<QueryDict: {'password': ['不要太简单']}>
3. 邮箱
字段类型 EmailField 对应邮箱参数 EmailInput
invalid 邮箱地址无效 设置的报错信息
# 4. 邮箱
email = forms.EmailField(
label='邮箱',
error_messages={
'invalid': '邮箱格式不正确',
'required': '邮箱不能为空'},
widget=forms.widgets.EmailInput(
attrs={'style': 'color:red;'}
)
)
<QueryDict: {'email': ['1360012768@qq.com']}>
4. 单选框 radio
字段类型 ChoiceField 对应单选框参数 RadioSelect
choices=(('提交的值', "显示的值"),) 提供可选的值
initial 设置默认选中的值
# 3.5 单选框 radio
gender = forms.fields.ChoiceField(
label="性别",
# - 提交的值 显示的值
choices=(('male', "男"), ('female', "女"), ('secret', "保密")),
# 设置默认选中
initial='secret',
widget=forms.widgets.RadioSelect())
<QueryDict: {'gender': ['female']}>
5.下拉框
字段类型 ChoiceField 对应下拉框参数 Select
choices=(('提交的值', "显示的值"),) 提供可选的值
initial 设置默认选中的值
# 3.6 下拉款 select
course = forms.ChoiceField(
label='课程',
choices=(('Python', 'Python'), ('Linux', 'Linux'), ('MySQL', 'MySQL')),
initial='MySQL',
widget=forms.widgets.Select())
<QueryDict: {'course': ['MySQL']}>
6.多选框单选
字段类型 ChoiceField 对应多选框单选参数 CheckboxInput
* 这东西有问题
# 3.7 单选 checkbox
protocol = forms.ChoiceField(
label='xxx协议',
initial='checked',
widget=forms.widgets.CheckboxInput())
7.多选框
字段类型 MultipleChoiceField 对应下多选框参数 CheckboxSelectMultiple
choices=(('提交的值', "显示的值"),) 提供可选的值
initial 设置默认选中的值
# 3.8 多选 checkbox
hobby = forms.MultipleChoiceField(
label='兴趣',
choices=(('web', 'web开发'), ('reptile', '爬虫'), ('financial_quantification', '金融量化')),
initial=[1, 2],
widget=forms.widgets.CheckboxSelectMultiple())
8.手机正则匹配
字段类型 CharField 对应正则参数 RegexValidator
RegexValidator(r'正则匹配', '错误提示')
# 正则模块
from django.core.validators import RegexValidator
# 3.9 正则匹配
phone = forms.CharField(
label='号码',
validators=[ # - 正则表达 提示
RegexValidator(r'^[0-9]+$', '输入的必须是数字'),
RegexValidator(r'^159[0-9]+$', '号码必须以159开头'),
]
)
9.数字
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度
# 3.10
number = forms.IntegerField(
min_value=1,
max_value=6,
error_messages={
'min_value': '最小只能是1',
'max_value': '最大只能是6'
}
)
3. forms组件源码
切入点在form_obj.is_valid()
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not self.errors
# 如果is_avlid要返回True 那么self.is_bound要为True self.errors 为False
# 只要传的值不为空就是 True
# self.is_bound = data is not None or files is not None
@property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
# self._errors = None 默认就是 None 百分百走 self.full_clean()
if self._errors is None:
self.full_clean()
return self._errors
# forms组件所有功能都出自于该方法
def full_clean(self):
···
self._clean_fields() # 检验字典 + 局部钩子
self._clean_form() # 全局钩子
self._post_clean() # 用不到