Python入门自学进阶-Web框架——18FormModelForm
Posted kaoa000
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——18FormModelForm相关的知识,希望对你有一定的参考价值。
Form:强大的数据验证,对前端的请求进行数据验证
基本的Form使用:
from django.shortcuts import render,HttpResponse
from django import forms
from django.forms import fields # 一般引入forms就可以,但是字段类型是保存在forms下的fields中的,可以直接引入fields
class UserForm(forms.Form):
# username = forms.CharField(label='用户名') # 可以直接用forms引用字段
username = fields.CharField() # 也可以用fields引用字段
email = fields.EmailField(label='邮箱') # label=参数在前端生成标签时,输入框前面的标签就为这个label的值,如果不设置label,就是字段名,如上面的username
def index(req):
if req.method == "GET":
obj = UserForm()
return render(req,'fm.html','obj':obj)
elif req.method == "POST":
obj = UserForm(req.POST)
# 对请求的数据进行验证:
# obj.is_valid()
# data = obj.clean()
# obj.errors
return render(req,'fm.html','obj':obj)
以上是Form类和views函数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
obj.username
obj.email
<!-- 如果字段比较多,上面的写法要写很多obj.xxx,可以用下面的方法 -->
<hr>
<!-- 列出所有字段,并包裹在p标签中 -->
obj.as_p
<hr>
<!-- 列出所有字段,并包裹在li标签中 -->
<ul>
obj.as_ul
</ul>
<hr>
<!-- 列出所有字段,并包裹在table标签中 -->
<table>
obj.as_table
</table>
</body>
</html>
前端,根据form自动生成表单输入框。
Form中,Form对象的is_valid()执行规则的验证,查看这个is_valid()的源码链:
is_valid()——>self.errors——>full_clean()——>_clean_fields()——>看这个的源代码
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
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
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
从这段代码看,先执行了value=field.clean(),这应该是系统的规则验证,验证无错误,添加到field.clean()中,系统验证完毕后,判断这个Form对象是否包含有clean_%s name这个属性,name就是字段名,我们这里有username和email,也就是判断是否有clean_username和clean_email,有的话就执行,没有的话就跳过,然后结束,我们要添加我们自己定义的验证,就可以增加clean_username和clean_email函数,定义自己校验。
至于校验错误时,系统是抛出异常,即ValidationError异常,然后通过add_error()添加到errors中。所以我们自定义的校验,如果出现校验错误的话,可以抛出一个ValidationError异常,相应的错误也就增加到errors中了。
class UserForm(forms.Form):
# username = forms.CharField(label='用户名') # 可以直接用forms引用字段
username = fields.CharField() # 也可以用fields引用字段
email = fields.EmailField(label='邮箱') # label=参数在前端生成标签时,输入框前面的标签就为这个label的值,如果不设置label,就是字段名,如上面的username
# 数据验证的过程,主要看源代码,先是经过自身的规则验证,然后再执行clean_字段名()这个函数
def clean_username(self):
value = self.cleaned_data['username']
if value == 'root':
return value
else:
raise ValidationError('输入的不是root。。。')
def index(req):
if req.method == "GET":
obj = UserForm()
return render(req,'fm.html','obj':obj)
elif req.method == "POST":
obj = UserForm(req.POST)
# 对请求的数据进行验证:
obj.is_valid()
data = obj.clean()
print(obj.errors)
return render(req,'fm.html','obj':obj)
我们在UserForm中增加一个clean_username函数,前端:
<form action="fm.html" method="post">
<table>
obj.as_table
% csrf_token %
</table>
<input type="submit" value="提交">
</form>
然后提交一个不是root的用户名,最后打印:
<ul class="errorlist"><li>username<ul class="errorlist"><li>输入的不是root。。。</li></ul></li><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li></ul>
前端:
以上是对单一字段的自定义验证,如果想对一组字段进行组合自定义验证,那写在哪里呢?跟踪源代码,在full_clean()中有:
self._clean_fields()
self._clean_form()
self._post_clean()
self._clean_fields() 进入刚才的单个字段的验证,self._clean_form() 是形成clean()结果,self._post_clean()中是pass,这就是一个钩子,自定义的组合验证可以通过重写这个方法来实现:
def _post_clean(self):
v1 = self.cleaned_data['username']
v2 = self.cleaned_data['email']
if v1 == 'root' and v2 == "root@com.cn":
pass
else:
self.add_error("__all__",ValidationError('用户名或邮箱错误。。。'))
运行结果:<ul class="errorlist"><li>__all__<ul class="errorlist nonfield"><li>用户名或邮箱错误。。。</li></ul></li></ul>
这里的关键一点是,添加错误信息时的字段只能是系统已有的,如“__all__”,如果是自己随便定义的是会出错的。也可以写None,即self.add_error(None,ValidationError('用户名或邮箱错误。。。')),这个None的错误最后会被系统加到“__all__”下。
这里是有BUG的,在获取v1和v2时,默认是前面已经通过验证了,即cleaned_data中有相关字段的值,否则,就会报错,如username输入其他值:
在self._clean_form()中也有一个钩子:看其源代码
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
其中的self.clean():
def clean(self):
"""
Hook for doing any extra form-wide cleaning after Field.clean() has been
called on every field. Any ValidationError raised by this method will
not be associated with a particular field; it will have a special-case
association with the field named '__all__'.
"""
return self.cleaned_data
wom也可以重写clean,它抛出异常,_clean_form中也能捕获。
def clean(self):
v1 = self.cleaned_data['username']
v2 = self.cleaned_data['email']
if v1 == 'root' and v2 == "root@com.cn":
pass
else:
raise ValidationError('用户名或邮箱错误。。。')
return self.cleaned_data
这样也是可以自定义验证。但是这里有一个问题是:当重写了clean()方法后,并抛出了异常,就不会执行return self.cleaned_data,这时,主程序中获取请求的数据就不能使用obj.clean(),直接使用cleaned_data就行了。这里抛出的异常,被加入到错误中时,字段是“__all__”。
ModelForm:适当的数据库操作、适当的数据验证。
Model中也是存在验证的,只是功能相对弱小:
class News(models.Model):
title = models.CharField(max_length=256,
error_messages='c1':'c1所对应的错误提示信息',
validators=[
RegexValidator(regex='root_\\d+',message='用户名错误了',code='c1')
])
def clean(self):
pass
model中可以重写clean()方法来自定义验证,只不过这个方法没有处理异常,需要自己来处理,否则出现异常程序中断。full_clean ——>字段正则判定——>clean方法(钩子)
对于form在前端生成的下拉选择框,使用ChoiceField,如果只是单纯定义这个字段,前端不会实时更新数据,解决方法有两个:
class UserForm(forms.Form):
# username = forms.CharField(label='用户名') # 可以直接用forms引用字段
username = fields.CharField() # 也可以用fields引用字段
email = fields.EmailField(label='邮箱') # label=参数在前端生成标签时,输入框前面的标签就为这个label的值,如果不设置label,就是字段名,如上面的username
usertype = fields.ChoiceField(choices=mymodels.UserType.objects.values_list('id','name'))
usertype2 = models.ModelChoiceField(queryset=mymodels.UserType.objects.all(),
empty_label="请选择类型",
to_field_name='id', # 这个参数定义选择项的value值,显示的下拉框的值是对象,要对UserType类增加__str__方法
limit_choices_to='id':1) #第二种解决实时显示数据的方法
def __init__(self,*args,**kwargs): # 重写构造函数,解决前端usertype不能实时更新的问题,这是第一种解决方法
super(UserForm,self).__init__(*args,**kwargs)
self.fields['usertype'].widget.choices = mymodels.UserType.objects.all().values_list('id','name')
# 数据验证的过程,主要看源代码,先是经过自身的规则验证,然后再执行clean_字段名()这个函数
def clean_username(self):
value = self.cleaned_data['username']
if value == 'root':
return value
else:
raise ValidationError('输入的不是root。。。')
def clean(self):
v1 = self.cleaned_data['username']
v2 = self.cleaned_data['email']
if v1 == 'root' and v2 == "root@com.cn":
pass
else:
raise ValidationError('用户名或邮箱错误。。。')
return self.cleaned_data
ModelForm就是为了补充Model验证较弱的缺点。
class UserModelForm(forms.ModelForm):
class Meta:
model = mymodels.UserInfo # 指定这个ModelForm对哪个类执行验证等操作
fields = "__all__" # 指定这个ModelForm处理Model类中哪些字段,__all__代表所有字段
def mymodelform(req):
if req.method == "GET":
obj = UserModelForm()
return render(req,'mf.html','obj':obj) # 注意obj是ModelForm对象,前端可以像Form一样,据此生成标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
obj.email <!-- 这里的obj是ModelForm,类似Form,也可以生成标签,实现Form功能 -->
</body>
</html>
对于Form和ModelForm,其继承关系如下:
Form: UserForm——>Form——>BaseForm
ModelForm: UserModelForm——>ModelForm——>BaseModelForm——>BaseForm
可以看到,最后都是继承于BaseForm。
所以前端可以使用 obj.as_p 生成全部字段
def mymodelform(req):
if req.method == "GET":
obj = UserModelForm()
return render(req,'mf.html','obj':obj) # 注意obj是ModelForm对象,前端可以像Form一样,据此生成标签
elif req.method == "POST":
obj = UserModelForm(req.POST)
ret = obj.is_valid() # Form有的ModelForm都有
print(ret)
print(obj.cleaned_data) # Form有的ModelForm都有
print(obj.errors) # Form有的ModelForm都有
if obj.is_valid():
print(obj.cleaned_data)
# mymodels.News.objects.create(**obj.cleaned_data) # Form时,保存数据,可以通过这样
obj.save() # ModelForm可以直接执行save()进行数据保存
return render(req,'mf.html','obj':obj)
<body>
obj.email <!-- 这里的obj是ModelForm,类似Form,也可以生成标签,实现Form功能 -->
<hr>
<form action="mf.html" method="post">
obj.as_p
% csrf_token %
<input type="submit" value="提交">
</form>
</body>
</html>
利用ModelForm进行修改操作:
后端:
def mfedit_mf(req,nid):
if req.method == "GET":
model_obj = mymodels.News.objects.get(id=nid) # 通过URL中的nid,找到对象,这个对象是Model对象
obj = UserModelForm(instance=model_obj) # 将Model对象赋值给ModelForm对象,这样才能在前端页面生成展示
return render(req,'mf1.html','obj':obj,'nid':nid)
elif req.method == "POST":
model_obj = mymodels.News.objects.get(id=nid)
obj = UserModelForm(req.POST,instance=model_obj) # 当更新的时候,除了要获取request提交的数据,还需要设置instance=
if obj.is_valid():
print(obj.cleaned_data)
# mymodels.News.objects.create(**obj.cleaned_data) # Form时,保存数据,可以通过这样
obj.save() # ModelForm可以直接执行save()进行数据保存
return render(req,'mf1.html','obj':obj)
urls中增加:
path('mfedit-mf-<int:nid>/',myform.mfedit_mf),
这样可以通过mfedit-mf-1形式直接将要修改的记录的id传递过去,从数据库获取到对于记录的Model对象。
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr>
<form action="/mfedit-mf- nid /" method="post">
obj.as_p
% csrf_token %
<input type="submit" value="提交">
</form>
</body>
</html>
主要注意点是获取时,是得到的一个Model对象,要想转换为ModelForm对象,在生成的ModelForm对象中使用instance=model_obj将获得的Model对象传递过去。
而在修改时,ModelForm对象中先获取前端的数据,即req.POST,然后必须使用instance=model_obj参数,告诉系统这是一个对相应Model对象的修改。
这里obj.save()完成了数据对表的保存。
这里的save,不仅能保存单表,还能够保存多表,即如果还有多对多字段,将会在第三张表中进行保存,还可以自己定制保存,即自己定制save()方法,是保存一张表,还是保存多张表。
save()有一个参数commit=True的默认参数,默认就是保存所有表,即字段中有多对多的字段,数据会保存多张表。如果commit=False,则不会进行保存表操作,这是mobj = obj.save(commit=False),这时save返回一个Model对象,使用这个对象的save,即mobj.save(),只保存当前主表,没有更新多对多表,要保存多对多表,需要使用mobj.save_多对多字段(),如mobj.save_m2m()。即分两步来分别保存主表和多对多表。
obj.save(commit=True)就等价于:mobj=obj.save(commit=False) mobj.save() obj.save_m2m()这三行代码
ModelForm的参数,除了Meta中的model和fields,还有其他的:Fields可以是“__all__”,也可使列表['name','email']格式,还有exclude,排除的字段,labels,标签,labels=‘name’:'用户名',‘enmail’:‘邮箱’,help_texts=,字段后的提示信息,widgets=,指定前端生成的元素
from django.forms import widgets as ws
class UserModelForm(forms.ModelForm):
class Meta:
model = mymodels.News # 指定这个ModelForm对哪个类执行验证等操作
fields = "__all__" # 指定这个ModelForm处理Model类中哪些字段,__all__代表所有字段
fields = ['title','content','url_laiyuan'] # 指定处理的具体字段
exclude = ['contents',] # 排除的处理字段
labels = 'title':'标题','content':'内容' # 输入框前的标签内容
help_texts = 'title':'请输入标题','contents':'*' # 输入框后的提示信息
widgets =
'name': ws.Textarea(attrs='class':'c1',), # 指定前端生成的元素类型
error_messages =
'title':'required':'必填,不能为空','valid':'格式错误' # 自定义错误消息
field_classes =
'title':forms.EmailField,
# 默认ModelForm的字段的验证,是使用Model的验证,这里可以使用这个参数,指定Form类型的验证
localized_fields = ('时间字段名',) # 本地化,更具不同时区显示数据,与settings.py中的设置有关,需要引入pytz模块
以上是关于Python入门自学进阶-Web框架——18FormModelForm的主要内容,如果未能解决你的问题,请参考以下文章
Python入门自学进阶-Web框架——20Django其他相关知识2
Python入门自学进阶-Web框架——3Django的URL配置
Python入门自学进阶-Web框架——21DjangoAdmin项目应用