FORM 组件
概念
内置的Django form组件操作更加简单
form组件的主要功能:
- 生成页面可用的HTML标签
- 对用户提交的数据进行校验
- 保留上次输入内容
普通的方式创建 form 表单
在html文件中创建好form表单并做好排版,然后动态数据通过后台函数提供
views.py
1 def register(request): 2 error_msg = "" 3 if request.method == "POST": 4 username = request.POST.get("name") 5 pwd = request.POST.get("pwd") 6 # 对注册信息做校验 7 if len(username) < 6: 8 # 用户长度小于6位 9 error_msg = "用户名长度不能小于6位" 10 else: 11 # 将用户名和密码存到数据库 12 return HttpResponse("注册成功") 13 return render(request, "register.html", {"error_msg": error_msg})
login.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>注册页面</title> 6 </head> 7 <body> 8 <form action="/reg/" method="post"> 9 {% csrf_token %} 10 <p> 11 用户名: 12 <input type="text" name="name"> 13 </p> 14 <p> 15 密码: 16 <input type="password" name="pwd"> 17 </p> 18 <p> 19 <input type="submit" value="注册"> 20 <p style="color: red">{{ error_msg }}</p> 21 </p> 22 </form> 23 </body> 24 </html>
使用form组件实现注册功能
在后台函数创建好form类实例化后和动态数据一起传到前端HTML文件,再由HTML文件排版
form.py
(通常会将 form 表单的操作在 一个单独的文件里面写 这样和model.py同级便于区分)
1 from django import forms 2 # 按照Django form组件的要求自己写一个类 3 class RegForm(forms.Form): 4 name = forms.CharField(label="用户名") 5 pwd = forms.CharField(label="密码")
views.py
1 def register2(request): 2 form_obj = RegForm() 3 if request.method == "POST": 4 # 实例化form对象的时候,把post提交过来的数据直接传进去 5 form_obj = RegForm(request.POST) 6 # 调用form_obj校验数据的方法 7 if form_obj.is_valid(): 8 return HttpResponse("注册成功") 9 return render(request, "register2.html", {"form_obj": form_obj})
login2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册2</title> </head> <body> <form action="/reg2/" method="post" novalidate autocomplete="off"> {% csrf_token %} <div> <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label> {{ form_obj.name }} {{ form_obj.name.errors.0 }} </div> <div> <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label> {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }} </div> <div> <input type="submit" class="btn btn-success" value="注册"> </div> </form> </body> </html>
常用字段与插件
label
标签内显示内容
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", # 显示内容 )
initial
初始值,input框里面的初始值。
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三" # 设置默认值)
error_messages
重写错误信息。
内置字段格式:
“错误类型”:“错误显示信息”
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={
"required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } )
widget
插件,添加 各类的HTML 的标签,并调整属性样式等操作,对每一种标签除了常用的属性以外都有不同的属性值可以设置
- attr={"k":"v"} 可以添加自定义的字段。通常可用来添加 类 ,id 等
- render_value = True 错误提交时原数据是否保留.
class LoginForm(forms.Form): pwd = forms.CharField( min_length=6, label="密码", widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) )
password
密码类型input标签
- attr={"k":"v"} 可以添加自动以的字段。通常可用来添加 类 id 等
- render_value = True 错误提交时原数据是否保留.
class LoginForm(forms.Form): pwd = forms.CharField( min_length=6, label="密码", widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) )
单选 radioSelect
- choices=((num,"v"),((num,"v"),((num,"v")) choice为选项内容,num为后台提交数据标识,v为选项数据内容
- initial 初始选项值
class LoginForm(forms.Form): gender = forms.fields.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性别", initial=3, widget=forms.widgets.RadioSelect() )
多选Select
- choices=((num,"v"),((num,"v"),((num,"v")) choice为选项内容,num为后台提交数据标识,v为选项数据内容
- initial 初始选项值,多选的时候可用列表形式提交
class LoginForm(forms.Form): hobby = forms.fields.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=[1, 3], widget=forms.widgets.SelectMultiple() )
单选checkbox
- initial 初始选项值,被选中为 “checked”
class LoginForm(forms.Form): keep = forms.fields.ChoiceField( label="是否记住密码", initial="checked", widget=forms.widgets.CheckboxInput() )
多选checkbox
- choices=((num,"v"),((num,"v"),((num,"v")) choice为选项内容,num为后台提交数据标识,v为选项数据内容
- initial 初始选项值,多选的时候可用列表形式提交
class LoginForm(forms.Form): ... hobby = forms.fields.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
choice字段注意事项
choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写 构造方法 从而实现choice实时更新。
方式一:
重写 init 方法,在继承父类的前提下重新在去数据库查询一次
from django.forms import Form from django.forms import widgets from django.forms import fields class MyForm(Form): user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) # self.fields['user'].choices = ((1, '上海'), (2, '北京'),) # 或 self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二:
from django import forms from django.forms import fields from django.forms import models as form_model class FInfo(forms.Form): authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选 # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
全部的内置字段
1 Field 2 required=True, 是否允许为空 3 widget=None, HTML插件 4 label=None, 用于生成Label标签或显示内容 5 initial=None, 初始值 6 help_text='', 帮助信息(在标签旁边显示) 7 error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} 8 validators=[], 自定义验证规则 9 localize=False, 是否支持本地化 10 disabled=False, 是否可以编辑 11 label_suffix=None Label内容后缀 12 13 14 CharField(Field) 15 max_length=None, 最大长度 16 min_length=None, 最小长度 17 strip=True 是否移除用户输入空白 18 19 IntegerField(Field) 20 max_value=None, 最大值 21 min_value=None, 最小值 22 23 FloatField(IntegerField) 24 ... 25 26 DecimalField(IntegerField) 27 max_value=None, 最大值 28 min_value=None, 最小值 29 max_digits=None, 总长度 30 decimal_places=None, 小数位长度 31 32 BaseTemporalField(Field) 33 input_formats=None 时间格式化 34 35 DateField(BaseTemporalField) 格式:2015-09-01 36 TimeField(BaseTemporalField) 格式:11:12 37 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 38 39 DurationField(Field) 时间间隔:%d %H:%M:%S.%f 40 ... 41 42 RegexField(CharField) 43 regex, 自定制正则表达式 44 max_length=None, 最大长度 45 min_length=None, 最小长度 46 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} 47 48 EmailField(CharField) 49 ... 50 51 FileField(Field) 52 allow_empty_file=False 是否允许空文件 53 54 ImageField(FileField) 55 ... 56 注:需要PIL模块,pip3 install Pillow 57 以上两个字典使用时,需要注意两点: 58 - form表单中 enctype="multipart/form-data" 59 - view函数中 obj = MyForm(request.POST, request.FILES) 60 61 URLField(Field) 62 ... 63 64 65 BooleanField(Field) 66 ... 67 68 NullBooleanField(BooleanField) 69 ... 70 71 ChoiceField(Field) 72 ... 73 choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) 74 required=True, 是否必填 75 widget=None, 插件,默认select插件 76 label=None, Label内容 77 initial=None, 初始值 78 help_text='', 帮助提示 79 80 81 ModelChoiceField(ChoiceField) 82 ... django.forms.models.ModelChoiceField 83 queryset, # 查询数据库中的数据 84 empty_label="---------", # 默认空显示内容 85 to_field_name=None, # HTML中value的值对应的字段 86 limit_choices_to=None # ModelForm中对queryset二次筛选 87 88 ModelMultipleChoiceField(ModelChoiceField) 89 ... django.forms.models.ModelMultipleChoiceField 90 91 92 93 TypedChoiceField(ChoiceField) 94 coerce = lambda val: val 对选中的值进行一次转换 95 empty_value= '' 空值的默认值 96 97 MultipleChoiceField(ChoiceField) 98 ... 99 100 TypedMultipleChoiceField(MultipleChoiceField) 101 coerce = lambda val: val 对选中的每一个值进行一次转换 102 empty_value= '' 空值的默认值 103 104 ComboField(Field) 105 fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 106 fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),]) 107 108 MultiValueField(Field) 109 PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 110 111 SplitDateTimeField(MultiValueField) 112 input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y'] 113 input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'] 114 115 FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 116 path, 文件夹路径 117 match=None, 正则匹配 118 recursive=False, 递归下面的文件夹 119 allow_files=True, 允许文件 120 allow_folders=False, 允许文件夹 121 required=True, 122 widget=None, 123 label=None, 124 initial=None, 125 help_text='' 126 127 GenericIPAddressField 128 protocol='both', both,ipv4,ipv6支持的IP格式 129 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 130 131 SlugField(CharField) 数字,字母,下划线,减号(连字符) 132 ... 133 134 UUIDField(CharField) uuid类型 135 136 Django Form内置字段
form 组件的标签生成方式
全部默认生成
直接将 form 类中的所有标签以默认的方式整体生成
操纵更加简单,但是默认会添加很多的不必要的属性
而且不方便控制form表单的整体样式
{{ form.as_p }}
自写框架,自动生成标签
自己写form表单框架,但是标签内容用form 组件来填充循环一个一个生成
对于form表单的样式可以调节了,但是生成的标签依旧很多不必要的属性
<form action="/reg2/" method="post" novalidate autocomplete="off"> {% csrf_token %}
<div> <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label> {{ form_obj.name }} {{ form_obj.name.errors.0 }} </div>
<div> <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label> {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }} </div>
<div> <input type="submit" class="btn btn-success" value="注册"> </div> </form>
完全手写
完全不用form组件的标签生成,全部手动写
完全控制了标签的样式自由
不再需要基于form组件的内置字段来控制样式
<form action="/addbook/" method="post"> {% csrf_token %} <p>名称<input type="text" name="title"></p> <p>价格<input type="text" name="price"></p> <input type="submit"> </form>
ps:
但是我还是想要用form 组件的 验证功能则需要注意一点 : 自己写的标签的 “ name ” 属性一定要和 form 组件要验证的字段名字对的上
class BookForm(forms.Form): title = forms.CharField() price = forms.FloatField() def addbook(request): if request.method == "POST": print(request.POST) form = BookForm(request.POST) if form.is_valid(): print("cleaned_data", form.cleaned_data) else: print("errors", form.errors) form = BookForm() return render(request, "addbook.html", locals())
详解:
面代码中 BookForm 注册的字段名字为 “ title ” 以及 “ price ”
那么自己手写的 标签的 “ name ” 属性一定要和这个对上才可以
<form action="/addbook/" method="post"> {% csrf_token %} <p>名称<input type="text" name="title"></p> <p>价格<input type="text" name="price"></p> <input type="submit"> </form>
在校验的时候,
如果 BookForm 中要求字段比 实际传入的字段要少 ,会引发空数据的 error (相当于有数据没拿到)
如果 BookForm 中要求字段比 实际传入的字段要多 , BookForm 对要求验证字段验证结束无错误后是不会处理多余的字段的。
最明显的特征为 csrf_token 的值不会进行处理也不会报错
重写钩子函数
1 # 重写全局的钩子函数,对确认密码做校验 2 from django.core.exceptions import ValidationError 3 4 5 def clean(self): 6 password = self.cleaned_data.get("password") 7 re_password = self.cleaned_data.get("re_password") 8 if re_password and re_password != password: 9 self.add_error("re_password", ValidationError("两次密码不一致")) 10 return self.cleaned_data 11 12 13 14 15 # 重写局部钩子函数 对名字字段的敏感字符进行判断 16 from django.core.exceptions import ValidationError 17 18 19 def clean_name(self): 20 value = self.cleaned_data.get("name") 21 if "金瓶mei" in value: 22 raiseValidationError("不符合社会主义核心价值观!") 23return value
关于 form is_vaild() 的执行顺序
0. form is_vaild()
1. 判断是否有数据 以及 是否有错误 (是否有数据直接可以判断)
2 . 进行错误信息的判断 (默认的错误信息为 none 定义在 init 中)
2.1 执行 full_clean()方法
2.1.1 创建一个 保存错误信息的 空字典 ( self._error = ErroDict() )
2.2.2 创建一个 保存校验通过数据的 空字典 ( self.cleaned_data = {} )
2.2 然后分别执行下面的三个方法
2.2.1 _clean_fields()
2.2.1.1 for 循环每个字段 ( self.fields.items() ) 分别校验 ( 利用的是内置的校验规则 )
如果有错误 在 self._error[] 中 在 add_error 中可以捕获到 ValidationError 异常进行处理 (稍微有些废话就隐藏把)
如果没有报错 在cleaned_date[] 中 加入已经通过校验的字段
2.2.1.2 for 循环结束后 进行一次反射 查询是否有 clean_%s %name 的方法 进行调用后执行
如果有错误 在 self._error[] 中 在 add_error 中可以捕获到 ValidationError 异常进行处理 (稍微有些废话就隐藏把)
如果没有报错 在cleaned_date[] 中 加入已经通过校验的字段
即 这个 clean_%s 的方法为一个钩子函数 此钩子可以帮我们解决什么问题呢?
这是个局部变量的钩子 可以对局部变量内部进行一定程度的操作 比如对变量的值进行操作
2.2.2 _clean_form()
调用对象的 .clean()方法,默认的继承的 .clean() 是什么都不做的
即 .clean() 为一个全局的钩子 可以对所有的变量进行操作 比如两个变量的对比等
因此可以利用这个clean方法进行 重写 自定义想要的校验操作
2.2.3 post_clean()
通过上面的流程可以发现.不论是使用全局钩子还是局部钩子都是对已经校验通过的数据进行操作
即是说 在调用 def clean_name(self): 或者 def clean(self): 的时候
self.cleaned_data 里面已经装好了校验通过的数据对象
在调用 def clean(self): 通过get("name") 也可以拿到所有的变量对象
在调用 def clean_name(self): 通过get("name") 也可以拿到当前的局部变量对象
通过后面的流程也发现这两个函数都要有返回值
def clean(self): 需要返回全局的 self.cleaned_data
ddef clean_name(self): 需要返回被处理的局部变量 value