forms组件
1.form组件--校验字段
基于用户注册演示,展现的功能为
- 校验字段
- 渲染标签
- 渲染错误与重置输入信息功能
- 局部钩子(单个字段自定义校验)与全局钩子(多个字段之间的关系,报错信息放在errors的全局key __all__中)
全局urls.py
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('reg/', views.reg),
]
app01/models.py
from django.db import models
class UserInfo(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
pwd = models.CharField(max_length=32)
email = models.EmailField()
tel = models.CharField(max_length=32)
app01/myforms.py 订制forms组件
from app01.models import *
import re
# 引入form组件
from django import forms
# widgets是一个配置forms组件的参数配置(标签类型,属性等)
from django.forms import widgets
# Error处理组件
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
# 创建一个继承了forms.Form的类,类名随便取
class UserForm(forms.Form):
"""
1. 不写验证规则,默认规则为不为空,
2. label是字段的名称(可用于form表单渲染)
3. error_messages字典里面为自定义错误信息key为 required表示告诉请求的类型 invalid表示非法
4. widget可以为form渲染模板提供相应支持,可以在widget中指定标签的类型(默认TextInput)和属性(值) attrs
"""
name = forms.CharField(min_length=4, label="用户名", error_messages={"required": "该字段不能为空"},
widget=widgets.TextInput(attrs={"class": "form-control"}))
pwd = forms.CharField(min_length=4, label="密码",
widget=widgets.PasswordInput(attrs={"class": "form-control"}))
r_pwd = forms.CharField(min_length=4, label="确认密码",
widget=widgets.PasswordInput(attrs={"class": "form-control"}))
email = forms.EmailField(label="邮箱",
widget=widgets.TextInput(attrs={"class": "form-control"}))
tel = forms.CharField(label="手机号",
widget=widgets.TextInput(attrs={"class": "form-control"}))
"""form组件校验的局部钩子(深度定制校验功能,源码利用反射提供的一个接口)
检验是通过is_valid()-->errors-->full_clean()--_clean_fields()开始进行,对应的源码(关键部分)
def _clean_fields(self):
for name, field in self.fields.items(): # 拿到自定义字段的名字name以及验证规则fields
...
try:
if isinstance(field, FileField): # 暂略
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else: # 执行这一步
value = field.clean(value)
self.cleaned_data[name] = value # 将匹配成功的字段以及值放入cleaned_data字典中
if hasattr(self, 'clean_%s' % name): # 局部钩子!判断是否有自定义验证规则clean_%s % name(所以自定义必须以clean_相关属性名为扩展函数名)
value = getattr(self, 'clean_%s' % name)() # 执行自定义规则
self.cleaned_data[name] = value
except ValidationError as e: # 抛出ValidationError错误,自定义的抛错也必须一致
self.add_error(name, e)
"""
def clean_name(self): # 验证name字段的扩展!
# 能执行到这一步说明之前已经校验正确了,cleaned_data已存在正确的值
val = self.cleaned_data.get("name")
# 查询名称是否已经存在表记录中
ret = UserInfo.objects.filter(name=val)
if not ret:
return val
else:
raise ValidationError("该用户已注册,请重新输入用户名!")
def clean_tel(self):
var = self.cleaned_data.get("tel")
if re.fullmatch("[0-9]{11}", var):
return var
else:
raise ValidationError("手机号必须是11位阿拉伯数字")
"""forms组件的全局钩子
is_valid()-->errors-->full_clean()--_clean_form()-->clean()
clean()方法没有写任何逻辑,这部分完全由用户写逻辑,用户写一个clean方法覆盖原来的clean方法
关键部分源码:
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e: # 全局校验如果出错,会把报错信息添加到如下:
self.add_error(None, e) # errors {"__all__":[e,]}
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
def clean(self):
return self.cleaned_data
"""
# 验证密码和确认密码是否相等
def clean(self): # 这部分始终会执行
"""如果两个密码分别校验成功则执行下面的,
如果存在一个为空说明其中有校验错误的字段,后面则没有必要校验了
"""
pwd = self.cleaned_data.get("pwd")
r_pwd = self.cleaned_data.get("r_pwd")
if pwd and r_pwd:
if pwd == r_pwd:
return self.cleaned_data # 对应源码的clean方法
else: # 对应源码_clean_form抛错部分
raise ValidationError("两次密码输入不一致")
else:
return self.cleaned_data
app01/view.py
from django.shortcuts import render,HttpResponse
from app01.myforms import UserForm
def reg(request):
"""forms组件校验演示(调用父类的方法进行验证)
1. is_valid 判断是否全部合法
2. forms.errors 存放错误信息
3. cleaned_data 存放正确匹配值
4. 前端传过来的key必须与自定义form组件对应的字段要匹配
forms校验必须将定义的字段交验完,如果传入其他不需要校验的字段无影响(不相关字段无法传出),
但是少一个必须校验的字段可不行
传递的参数为一个字典
forms = UserForm({"name":"yuan","email":"123"})
断值是否是符合自己定义的规则
print("is_valid",forms.is_valid())
如果不符规则放在errors字典(直接打印是一个ul形式)里面
每个键值下面是一个列表
if not forms.is_valid():
print(forms.errors)
print(forms.errors.get("name")[0])
return HttpResponse("%s"%str(forms.errors))
else:
print(forms.cleaned_data)
if 有字段校验成功
则正确的校验的数据都放在forms.cleaned_data字典中
"""
if request.method == "POST":
form = UserForm(request.POST)
if form.is_valid():
print("form.is_valid()",form.cleaned_data)
return HttpResponse("OK")
else:
print("form.is_valid()",form.errors)
pwd_error = form.errors.get("__all__")[0]
# forms组件渲染错误信息errors
# 通过forms组件生渲染的form表单!
return render(request,"index.html",locals())
"""forms组件的渲染标签功能(模板中实现)
保证forms里面的标签与定义的forms组件属性一致
在提交的时候会验证当前字段是否合法
"""
form = UserForm()
return render(request,"index.html",locals())
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>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>
<form action="" method="post">
{% csrf_token %}
用户名:<input type="text" name="name"> </br>
密码: <input type="password" name="pwd"> </br>
确认密码: <input type="password" name="r_pwd"> </br>
邮箱:<input type="text" name="email"> <br>
电话:<input type="text" name="tel">
<input type="submit" value="提交">
</form>
<hr>
forms组件渲染标签功能--重点是保证forms里面的标签与定义的forms组件属性一致
直接将需要渲染的标签用forms的相关变量代替
方式一: form.name.lable是取得相应的标签命
<h1>方式一</h1>
<form action="">
{% csrf_token %}
form.name.errors.0拿到报错信息
{{ form.name.label }}:{{ form.name }} <span>{{ form.name.errors.0 }}</span> <br>
{{ form.pwd.label }} : {{ form.pwd }} <span>{{ form.name.pwd.errors.0 }}</span> <br>
{{ form.r_pwd.label }}: {{ form.r_pwd }} <span>{{ form.name.r_pwd.errors.0 }}</span> <br>
{{ form.email.label }}: {{ form.email }} <span>{{ form.name.email.errors.0 }}</span> <br>
{{ form.tel.label }}:{{ form.tel }}
<input type="submit" value="提交">
</form>
<hr>
{# 方式二: 推荐的方式#}
<div class="container">
<div class="row text-center">
<h1>方式二</h1>
</div>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
<label for="field.id">{{ field.label }}</label>
{{ field }} <br>
<span style="color: red">{{ field.errors.0 }}</span> <br>
{% if field.name == "r_pwd" %}
<span style="color: red">{{ pwd_error }}</span> <br>
{% endif %}
{% endfor %}
<input type="submit" value="提交">
</form>
</div>
</div>
</div>
<hr>
{# 方式三: 不推荐使用 as_p做p标签#}
{# <form action="">#}
{# {% csrf_token %}#}
{# {{ form.as_p }}#}
{# <input type="submit" value="提交">#}
{# </form>#}
</body>
</html>