ModelForm组件
Model,Form,ModelForm的比较
form组件的功能
之前web开发的模式,以注册为例:
1. 要有一个注册的页面,页面里面要有form表单 --> 生成HTML代码
2. form表单要能提交数据到后端,后端要做有效性校验 --> 数据有效性校验
3. 要把校验的提示信息展示在页面上 --> 校验信息返回并展示,保存原来填写的内容
关于校验:
1. 前端通过JS代码做校验 --> 最好有
2. 后端做校验 --> 必须要有(因为前端的校验可以被跳过)
我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。
与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示显示对应的错误信息.。
form组件的主要功能如下:
1.生成页面可用的HTML标签
2.对用户提交的数据进行校验
3.保留上次输入内容
使用form组建就能完成1.生成HTML代码2.数据有效性校验3校验信息返回并展示
form组件的用法
1. from django import forms
2, 定义一个form类
class RegForm(forms.Form):
user = forms.CharField()
pwd = forms.CharField()
email = forms.EmailField()
生成HTML:
3. 实例化一个form对象, 传递到模板语言中
4. 在目标语言中调用form对象的响应方法和属性
方式:
1. {{ form_obj.as_p }}
2. 单独写
{{ form_obj.pwd.label }}
{{ form_obj.pwd }}
做校验:
1. form_obj = RegForm(request.POST)
2. form_obj.is_valid()
校验字段数据
from django import forms class UserForm(forms.Form): name = forms.CharField(max_length=32) age = forms.IntegerField() email = forms.EmailField() ###只要定义了校验规则,该字段就默认不能为空 form = UserForm({"names": "alex", "email": "123@qq.com", "age": 123}) True form = UserForm({"name": "alex"}) False age和email不能为空 form = UserForm({"name": "alex", "email": "123@qq.com", "age": 123, "a": 123}) True
form组件登陆
<html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>login</title> <style> .error { color: red; } </style> </head> <body> <form action="/login2/" method="post" novalidate> {% csrf_token %} <p> {{ form_obj.username.label }} {{ form_obj.username }} <span class="error">{{ form_obj.username.errors.0 }}</span> </p> <p> {{ form_obj.pwd.label }} {{ form_obj.pwd }} <span class="error">{{ form_obj.pwd.errors.0 }}</span> </p> <p> <input type="submit"> <span class="error">{{ error_msg }}</span> </p> </form> </body> </html>
class LoginForm(forms.Form): username = forms.CharField(min_length=8, label="用户名", error_messages={ "required": "该字段不能为空", "min_length": "用户名不能少于6位", } ) pwd = forms.CharField(min_length=6, label="密码", widget = forms.widgets.PasswordInput(attrs={'class': 'c1'}, ) ) def login2(request): error_msg = "" form_obj = LoginForm() if request.method == "POST": form_obj = LoginForm(request.POST) if form_obj.is_valid(): username = form_obj.cleaned_data.get("username") pwd = form_obj.cleaned_data.get("pwd") if username == "Q1mi" and pwd == "123456": return HttpResponse("OK") else: error_msg = "用户名或密码错误" return render(request, "login2.html", {"form_obj": form_obj, "error_msg": error_msg})
前端页面是form类的对象生成的 -->生成HTML标签功能
当用户名和密码输入为空或输错之后 页面都会提示 -->用户提交校验功能
当用户输错之后 再次输入 上次的内容还保留在input框 -->保留上次输入内容
form中常用的字段和插件
创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三" # 设置默认值 error_messages={ "required": "该字段不能为空", "min_length": "用户名不能少于6位", }, widget=forms.widgets.TextInput(attrs={"class": "use"}) ) pwd = forms.CharField(min_length=6, label="密码") #密码显示明文 pwd = forms.CharField(min_length=6,label="密码", widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) )
1.label 输入框前面的内容,对输入框的描述
2.initial 输入框中的默认值
3.error_messages 定义错误信息,由于可定义多个,在html页面调用时使用errors,常用form_obj.username.errors.0
4.widgte HTML插件要使用forms.widgets模块,其中attrs的值为字典,定义一个属性,便于设置样式
5.forms.widgets.PasswordInput将密码显示为密文。render_value=True保存原来填写的密码
6.radioSelect 单radio值为字符串
class LoginForm(forms.Form): gender = forms.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性别", initial=3, widget=forms.widgets.RadioSelect )
7.单选Select 下拉框
class LoginForm(forms.Form): hobby = forms.ChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=3, widget=forms.widgets.Select )
8.多选Select
class LoginForm(forms.Form): hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=[1, 3], widget=forms.widgets.SelectMultiple )
9.单选checkbox
class LoginForm(forms.Form): ... keep = forms.ChoiceField( label="是否记住密码", initial="checked", widget=forms.widgets.CheckboxInput )
10.多选checkbox
class LoginForm(forms.Form): hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple )
渲染标签
渲染方式1
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <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> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form action="" method="post"> {% csrf_token %} <div> <label for="">用户名</label> {{ form.name }} </div> <div> <label for="">密码</label> {{ form.pwd }} </div> <div> <label for="">确认密码</label> {{ form.r_pwd }} </div> <div> <label for=""> 邮箱</label> {{ form.email }} </div> <input type="submit" class="btn btn-default pull-right"> </form> </div> </div> </div> </body> </html>
渲染方式2
<form action="" method="post"> {% csrf_token %} {% for field in form %} <div> <label for="">{{ field.label }}</label> {{ field }} </div> {% endfor %} <input type="submit" class="btn btn-default pull-right"> </form>
渲染方式3
<form action="" method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" class="btn btn-default pull-right"> </form>
form中的选择从数据库中取时
在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,
那么需要自定义构造方法从而达到此目的。
city = forms.ChoiceField( choices=models.City.objects.all().values_list("id", "name"), label="城市", initial=1, widget=forms.widgets.Select ) # 重写父类的init方法 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["city"].widget.choices = models.City.objects.all().values_list("id", "name")
form校验
1.form_obj.is_valid()的执行流程:
self._errors = ErrorDict() --> {} 存放字段和错误的信息
self.cleaned_data = {} 校验通过的字段和用户输入的值放到大字典中
self.fields.items()字段名 字段对象
如果有错误:
self._errors["name"] = ""
没有报错:
self.cleaned_data["name"] = value(用户填写的值)
2.钩子函数(hook)的使用
在RegForm中不仅可以定义字段,还可以定义方法,使用clear_字段名
from django.core.exceptions import ValidationError
1.如检测用户输入的有否敏感字
def clean_name(self):
"""具体字段的自定义校验方法"""
value = self.cleaned_data.get("user")user #获取字段值,进行判断
if "nimp" in value:
raise ValidationError("不符合社会主义核心价值观!")
return value
2.两个字段及进行比较,判断两次密码是否一致
# 重写父类的clean方法
def clean(self):
pwd = self.cleaned_data.get("pwd")
re_pwd = self.cleaned_data.get("re_pwd")
if pwd != re_pwd:
self.add_error("re_pwd", ValidationError("两次密码不一致"))
raise ValidationError("两次密码不一致")
return self.cleaned_data
3.在字段中添加正则表达式自定义校验 validators
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
4.将错误信息以json形式展示:obj.errors.as_json()
用error_str = obj.errors.as_json() 和 result['message'] = json.loads(error_str)来完成错误信息收集。
print(form_obj.errors) print("---------------------->") data=form_obj.errors.as_json() result = json.loads(data) print(result) print("------------------------>") error_message={} for key,value in result.items(): if key == '__all__':pass else: error_message[key]=value[0]['message'] print(error_message)
<ul class="errorlist"><li>username<ul class="errorlist"><li>用户名不能少于6位</li></ul></li><li>phone<ul class="errorlist"><li>数字必须以159开头</li></ul></li><li>rep_passwd<ul class="errorlist"><li>两次密码不一致</li></ul></li><li>__all__<ul class="errorlist nonfield"><li>两次密码不一致</li></ul></li></ul> ----------------------> {'username': [{'message': '用户名不能少于6位', 'code': 'min_length'}], 'phone': [{'message': '数字必须以159开头', 'code': 'invalid'}], 'rep_passwd': [{'message': '两次密码不一致', 'code': ''}], '__all__': [{'message': '两次密码不一致', 'code': ''}]} ------------------------> {'username': '用户名不能少于6位', 'phone': '数字必须以159开头', 'rep_passwd': '两次密码不一致'}
注册验证,为使用ajax
<form action="/register/" method="post"> {% csrf_token %} <p> {{ form_obj.username.label }} {{ form_obj.username }} {{ form_obj.username.errors.0 }} </p> <p> {{ form_obj.passwd.label }} {{ form_obj.passwd }} {{ form_obj.passwd.errors.0 }} </p> <p> {{ form_obj.rep_passwd.label }} {{ form_obj.rep_passwd }} {{ form_obj.rep_passwd.errors.0 }} </p> <p> {{ form_obj.phone.label }} {{ form_obj.phone }} {{ form_obj.phone.errors.0 }} </p> <input type="submit" value="注册">
from django.shortcuts import render # Create your views here. from django import forms from django.core.validators import RegexValidator from django.core.exceptions import ValidationError class RegForm(forms.Form): username=forms.CharField( max_length=12, min_length=6, label='用户名:', # initial="张三", widget=forms.widgets.TextInput(attrs={"class": "use"}), error_messages={ "required": "该字段不能为空", "min_length": "用户名不能少于6位", "max_length": "用户名过长", } ) passwd = forms.CharField( max_length=10, min_length=6, label='密 码:', error_messages={ "required": "密码不能为空", "min_length": "密码不能少于6位", }, widget = forms.widgets.PasswordInput(attrs={'class': 'c1'},render_value=True ) ) rep_passwd= forms.CharField( max_length=10, min_length=6, label='再次输入密码:', error_messages={ "required": "密码不能为空", "min_length": "密码不能少于6位", }, widget = forms.widgets.PasswordInput(attrs={'class': 'c2'}, render_value=True) ) phone = forms.CharField( validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], label='手机号:', ) def clean(self): pwd = self.cleaned_data.get("passwd") re_pwd = self.cleaned_data.get("rep_passwd") if pwd != re_pwd: self.add_error("rep_passwd", ValidationError("两次密码不一致")) raise ValidationError("两次密码不一致") return self.cleaned_data def register(request): form_obj=RegForm() if request.method=="POST": form_obj = RegForm(request.POST) ###校验通过### if form_obj.is_valid(): # 所有经过校验的数据都保存在 form_obj.cleaned_data #两次密码都相同,就删除一个 del form_obj.cleaned_data["rep_passwd"] #将用户名和密码存到数据库中,字典需要打散,再存 models.UserInfo.objects.create(**form_obj.cleaned_data) return HttpResponse("注册成功!") return render(request,"register.html",{"form_obj":form_obj})