对Form进行更深入的研究。
使用Form,需要 1、创建一个验证用户请求的模板,就是一个类: from django import forms class MyForm(forms.Form) ? ??user = forms.CharField(...)? ? ? ?# 默认生成 input type=‘text’ ? ? email =? forms.EmailField(...)? ?# 默认生成input type=‘email’ ? ? pwd = forms.PasswordField(...) # 默认生成input type=‘password’
通过这个模板,前端就可以自动生成相应的html标签,CharField(...)默认是生成一个input的text标签等等。类就是一个模板,里面有几个字段,就验证几个字段
2、类中要有相应的字段:user = forms.CharField(...)
字段是继承了Field类的对象,主要是用来验证输入的某个字段的数据的合法性,如可以在参数中定义长度等,这个类主要是封装了一些正则表达式。
当obj = MyForm(req.POST) ? ? obj.is_valid()? ? ?# 这个方法就是循环执行类中每个字段的验证规则,如这里是3个字段,如果有一个字段的验证结果为False,结果就为False,全正确,才为True。
3、插件: 对于字段,在前端生成HTML标签时,如果没有其他配置,会生成默认的标签的,就如CharField(...)默认是生成一个input的text标签。那是否可以改变这个默认生成的标签呢?可以使用插件来改变,就是在参数中配置“widget=”选项。如下形式
user = forms.CharField(...,widget=Input框)?
对于CharField类,看其源代码如下
?在CharField中没有widget,在其父类Field中定义了widget=TextInput
对于其他的字段类,如下:
?基本上都是在本类中定义了默认的widget。
django提供的字段类列表如下:
所以,在定义不同的字段时,是可以通过widget修改不同的插件来生成不同的标签,fields.py中的这些类都是插件。
如user = forms.CharField(widget=forms.PasswordField),生成的就是密码输入框,而不是默认的文本输入框。
还可以给生成的标签添加属性:
user = forms.CharField(widget=forms.TextField(attrs={‘class’:‘c1’,‘placeholder’:‘用户名’}))
测试:
模板类:
from django import forms
class MyForm(forms.Form):
user = forms.CharField()
user1 = forms.CharField(widget=forms.PasswordInput)
user2 = forms.CharField(widget=forms.TextInput(attrs={'class':'cl1','placeholder':'用户名'}))
user3 = forms.ChoiceField(choices=[(1,'足球'),(2,'篮球'),(3,'排球'),(4,'乒乓球')])
视图函数:
def detail(req):
obj = myviews.MyForm()
return render(req,'detail.html',{'obj':obj})
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>user:{{ obj.user }}</p>
<p>user1:{{ obj.user1 }}</p>
<p>user2:{{ obj.user2 }}</p>
<p>user3:{{ obj.user3 }}</p>
</body>
</html>
结果:
?
?模板的字段还具有自动类型转换的功能,如:
user4 = forms.IntegerField(),前端传来的是字符串,到了这里,会自动转换为整型数。
?字段的参数:
Field
required=True, 是否必须输入值,即不能为空,默认设置
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
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类型
...
测试:
后端:
from django.shortcuts import render,HttpResponse,redirect,HttpResponseRedirect
from myadminzdy import models
from django import forms
from django.core.validators import RegexValidator
class MyForm(forms.Form):
# user = forms.CharField()
# user1 = forms.CharField(widget=forms.PasswordInput)
# user2 = forms.CharField(widget=forms.TextInput(attrs={'class':'cl1','placeholder':'用户名'}))
# user3 = forms.ChoiceField(choices=[(1,'足球'),(2,'篮球'),(3,'排球'),(4,'乒乓球')],)
f1 = forms.CharField() #默认字段不能为空,即required=True
f2 = forms.CharField(required=False) # 字段可以为空
f3 = forms.CharField(label="f3的label") # 前端可以使用.label、.id_for_label、.label_tag
f4 = forms.CharField(initial='初始值') # 生成的标签有初始值
f5 = forms.CharField(initial='初始值11111',show_hidden_initial=True) #在生成一个显示的标签外,还生成一个隐藏的标签,其值保持原始值,可用于标签值的比对
f6 =forms.CharField(validators=[RegexValidator(r'^[0-9]+$','jiaoyan1:quanshuzi'),RegexValidator(r'^135[0-9]+$','jiaoyan2:135kaishi')])
# 添加自定义的校验规则,使用validators=参数,规则是RegexValidator的对象,这里添加两条,它的两个参数,一个是规则,一个是错误信息
# 打印的错误信息:{"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}]}
f7 = forms.CharField(validators=[RegexValidator(r'^[0-9]+$', 'jiaoyan1:quanshuzi'),
RegexValidator(r'^135[0-9]+$', 'jiaoyan2:135kaishi')],
error_messages={'required':'111-buweikong','invalid':'222-geshicuowu'})
# 测试error_message参数
# {"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}],
# "f7": [{"message": "222-geshicuowu", "code": "invalid"}, {"message": "222-geshicuowu", "code": "invalid"}]}
# 可以看到,设置error_message后,最后的错误信息以error_message设置的为主
# 对于RegexValidator,还可以第三个参数:code,来定义不同的类型,如系统中的required、invalid等
f8 = forms.CharField(validators=[RegexValidator(r'^[0-9]+$', 'jiaoyan1:quanshuzi',code='f1'),
RegexValidator(r'^135[0-9]+$', 'jiaoyan2:135kaishi',code='f2')],
error_messages={'required':'111-buweikong','invalid':'222-geshicuowu'})
# {"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}],
# "f7": [{"message": "222-geshicuowu", "code": "invalid"}, {"message": "222-geshicuowu", "code": "invalid"}],
# "f8": [{"message": "jiaoyan1:quanshuzi", "code": "f1"}, {"message": "jiaoyan2:135kaishi", "code": "f2"}]}
# error_message优先级高,根据code进行覆盖,想要修改原生的错误信息,可以设置error_message。
f9 = forms.RegexField(r'^135[0-9]+$') # 自定义正则表达式,自定义校验规则的字段,前端默认生成文本输入框
f10 = forms.FileField() # 上传文件,生成input type为file的标签,
# clean()中显示的:'f10': <InMemoryUploadedFile: 1.jpg (image/jpeg)>
f11 = forms.ImageField() # 类似FileField,生成标签多了accept="image/*"
f12 = forms.ChoiceField(
choices=[(1,'羽毛球'),(2,'乒乓球'),(3,'蓝球'),(4,'排球')],
initial=3
)
# 下列选择框,值是字符串
f13 = forms.TypedChoiceField(
coerce=lambda x:int(x), #类型转换
choices=[(1, '羽毛球'), (2, '乒乓球'), (3, '蓝球'), (4, '排球')],
initial=3
)
#f12和f13结果进行比较:'f12': '3', 'f13': 3,一个是字符串,一个是整型
f14 = forms.MultipleChoiceField(
choices=[(1, '羽毛球'), (2, '乒乓球'), (3, '蓝球'), (4, '排球')],
initial=[1,3]
)
# 多选框,要注意初始值是列表。
f15 = forms.FilePathField(path='myadminzdy/',allow_folders=True,allow_files=True,recursive=True)
# 下拉选择框,内容是对应路径下的文件
def detail(req):
if req.method == "GET":
obj = MyForm()
return render(req,'detail.html',{'obj':obj})
else:
obj = MyForm(req.POST,req.FILES)
# 如果要接收文件,参数中增加req.FILES,因为上传的文件是保存在FILES中的
obj.is_valid()
print(obj.clean())
print(obj.errors.as_json())
return render(req,'detail.html',{'obj':obj})
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script src="/static/jquery-3.6.0.js"></script>
<script>
$('#id_f5').attr(value)
</script>
<body>
<form action="detail.html" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>f1:{{ obj.f1 }}</p>
<p>f2:{{ obj.f2 }}</p>
<!-- <p>{{ obj.f3.label }}:{{ obj.f3 }}</p> 这种写法标签只是一个文本 -->
<!--<p>{{ obj.f3.id_for_label }}{{ obj.f3 }}</p> --> <!-- 还有一个属性id_for_label,将id值做标签 -->
<!-- <p><label for="{{ obj.f3.id_for_label }}">{{ obj.f3.label }}</label>{{ obj.f3 }}</p>
这种写法,点击标签,焦点进入相应的输入框 -->
{{ obj.f3.label_tag }}{{ obj.f3 }} <!-- 上面写法可以这样实现 -->
<p>测试初始值{{ obj.f4 }}</p>
<p>测试生成隐藏标签{{ obj.f5 }}</p>
<!-- <input type="text" name="f5" value="初始值11111" id="id_f5">
<input type="hidden" name="initial-f5" value="初始值11111" id="initial-id_f5">
-->
<p>自定义校验规则 : {{ obj.f6 }}</p>
<p>自定义校验规则 : {{ obj.f7 }}</p>
<p>自定义校验规则 : {{ obj.f8 }}</p>
<p>自定义校验规则 : {{ obj.f9 }}</p>
<p>文件上传: {{ obj.f10 }}</p>
<p>tuoian上传: {{ obj.f11 }}</p>
<p>下拉选择框: {{ obj.f12 }}</p>
<p>下拉选择框类型转换: {{ obj.f13 }}</p>
<p>下拉多选选择框: {{ obj.f14 }}</p>
<p>下拉选择框-文件列表: {{ obj.f15 }}</p>
<p><input type="submit" value="提交"></p>
</form>
</body>
</html>
django提供的widget插件:
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
# 单radio,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
# 单radio,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.RadioSelect
# )
# 单select,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
# 单select,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.Select
# )
# 多选select,值为列表
# user = fields.MultipleChoiceField(
# choices=((1,'上海'),(2,'北京'),),
# initial=[1,],
# widget=widgets.SelectMultiple
# )
# 单checkbox
# user = fields.CharField(
# widget=widgets.CheckboxInput()
# )
# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
# initial=[2, ],
# choices=((1, '上海'), (2, '北京'),),
# widget=widgets.CheckboxSelectMultiple
# )
对于字段,主要是进行验证的,对于widget插件,主要是定义前端生成的标签类型,如果字段定义的是CharField,而插件使用的是MultipleSelect,前端返回的是一个列表,但是验证是按字符串进行验证,就失去验证的意义,所以要做好两者的匹配。
下拉单选框,通过数据库动态获取数据:
页面:
?后端代码:
# models中代码,生成数据库
class UserType(models.Model):
caption = models.CharField(max_length=32)
# 视图函数中
# 定义模板类
from myadminzdy import models
class MyFormDb(forms.Form):
host = forms.CharField()
host_type = forms.IntegerField(
# widget=forms.Select(choices=[(1,'BJ'),(2,'SH')]) # 静态的获取
# widget = forms.Select(choices=models.UserType.objects.all().values_list('id','caption'))
# 从数据库动态获取,django启动时获取,只执行一次
widget=forms.Select(choices=[])
)
# 为了在数据库记录改变时,前端动态获取到最新的数据,需要每次生成对象时执行一次获取数据
def __init__(self,*args,**kwargs):
super(MyFormDb,self).__init__(*args,**kwargs)
self.fields['host_type'].widget.choices = models.UserType.objects.all().values_list('id','caption')
# 每次实例化都会重新获取数据库数据并赋值给choices。
#视图函数
def db(req):
if req.method == "GET":
obj = MyFormDb()
return render(req, 'db.html', {'obj': obj})
else:
obj = MyForm(req.POST, req.FILES)
# 如果要接收文件,参数中增加req.FILES,因为上传的文件是保存在FILES中的
obj.is_valid()
print(obj.clean())
print(obj.errors.as_json())
return render(req, 'db.html', {'obj': obj})
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ obj.host }}
{{ obj.host_type }}
</body>
</html>
关键点是,在定义模板类时,按照以前的写法,只定义字段,在定义字段时设置choices获取数据库数据,测试时,在后台数据库改变时,前端数据没有改变,因为字段的choices值只在第一次加载时执行了一次,以后都是这个加载值的拷贝,所以需要定义__init__(),在每次实例化时都执行一遍查询数据库,并赋值给choices,这样就实现数据的实时更新。
注意这一句: self.fields['host_type'].widget.choices = models.UserType.objects.all().values_list('id','caption')
通过这个推断,模板类中有一个字典类型字段fields,我们定义的字段作为其中的键值对,所以可以使用fields['host_type']获取到forms.IntegerField(widget=forms.Select(choices=[])),这又是一个字典结构,可以通过.widget获取到forms.Select(choices=[]),又是一个字典结构,在.choices获取到choices。
定义一个用户表:
class UserType(models.Model):
caption = models.CharField(max_length=32)
class User(models.Model):
username = models.CharField(max_length=32)
user_type = models.ForeignKey('UserType',on_delete=models.DO_NOTHING)
前台请求传递一个用户id,返回其用户名和类型:
def db(req):
if req.method == "GET":
nid = req.GET.get('nid')
m = models.User.objects.filter(id=nid).first()
dic = {'host':m.username,'host_type':m.user_type_id}
obj = MyFormDb(dic)
return render(req, 'db.html', {'obj': obj})
只需要形成一个验证模板类字段的字典格式数据,这里就是dic ={'host':'','host_type':''},传递给生成的模板对象就可以了。
|