第八章表单与模型
表单主要分为两种表单
django表单功能由Form类实现,主要分为两种
1.django.forms.Form
2.django.forms.ModelForm # 结合模型生成的数据表单
表单form标签中的action用于设置用户提交的表单数据应由哪个路由来接收和处理,若为空,则提交由当前的路由来接收和处理。否则则跳转到属性action所指向的路由地址
提交请求由method决定
网页表单的构成
{% if v.errors %} <!--v为表单类的实例化-->
<p>
数据出错啦,错误信息:{{ v.errors }}
</p>
{% else %}
<form action="" method="post">
{% csrf_token %}
<table>
<!--将表单对象生成网页表单-->
{{ v.as_table }} as_table <!--表单的表示纯用模板变量么-->
</table>
<input type="submit" value="提交">
</form>
{% endif %}
定义表单类,新建表单文件
定义表单类需要导入模型类
from django import forms
from .models import *
class VocationForm(forms.Form):
job = forms.CharField(max_length=20, label='职位')
title = forms.CharField(max_length=20, label='职称')
payment = forms.IntegerField(label='薪资') # 难道IntegerField设置数字成可自增自减
# 设置下拉框的值
# 查询模型PersonInfo的数据
value = PersonInfo.objects.values('name') # 获取模型字段中的姓名
# 将数据以为列表格式表示,列表元素为元组格式
choices = [(i+1, v['name']) for i, v in enumerate(value)] # 这里又为什么只显示v['name']
# 表单字段设为ChoiceField类型,用生成下拉框
person = forms.ChoiceField(choices=choices, label='姓名') # choices=choices下拉框
表单类中定义的字段和html标签的比较
字段label转化为label标签, forms.CharField转化为
input type="text"
,job的命名转换为<input>
控件的参数name,表单字段的max_length变为input控件的maxlength参数
# 表单类VocationForm的表单字段job
job = forms.CharField(max_length=20, label='职位')
# html标签
<tr><th><label for="id_job">职位:</label></th><td><input type="text" name="job" maxlength="20" required id="id_job"></td></tr>
form源码分析
表单的定义过程、表单的字段类型和表单字段的参数类型是表单的核心功能。
表单的定义过程
继承自两个类,具体可看源码位置:django/forms/forms.py
class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass):
表单的属性和方法;源码位置:django/forms/forms.py
#BaseForm中定义的属性
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
#表单的方法
errors():验证表单数据是否存在异常,若存在,则获取异常信息,异常信息可设为字典或json格式
is_valid():验证表单数据是否存在异常,若存在,则返回false,否则返回true
as_table():将表单字段以html的<table>标签生成网页表单
as_ul():将表单字段以html的<ul>标签生成网页表单
as_p():将表单字段以html的<p>标签生成网页表单
has_changed():对比用于提交的表单数据与表单初始化数据是否发送变化。
表单字段的参数类型;源码文件位置:django/forms/fields.py
#可在文件中查看
__all__ = (
'Field', 'CharField', 'IntegerField',
'DateField', 'TimeField', 'DateTimeField', 'DurationField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'GenericIPAddressField', 'FilePathField',
'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField', 'UUIDField',
)
# 列举几个常用表单;反正都是为了在html页面上显示出这几类框:文本框;数值框;文本上传框;下拉框;复选框
CharField:文本框,参数max_length和min_length分别设置文本长度
IntegerField:数值框,参数max_value设置最大值,min_value设置最小值
FileField:文本上传框,参数max_length设置上传文件名的最大长度,参数allow_empty_file设置是否允许文件内容为空。
ChoiceField:下拉框,参数choice与元组形式表示,用于设置下拉框的选项列表
BooleanField:复选框,设有选项true和false,如果字段带有required=true,复选框就默认为true.
实例
from django import forms
from .models import *
from django.core.exceptions import ValidationError
# 自定义数据验证函数
def payment_validate(value):
if value > 30000:
raise ValidationError('请输入合理的薪资')
class VocationForm(forms.Form): # form.Form的字段和models中的字段类似
job = forms.CharField(max_length=20, label='职位')
# 设置字段参数widget、error_messages
# 参数widwget必须是个forms.widgets对象,而widwget的对象必须和表单字段类型相互对应,对应该则分为4大类,该定义的函数中可以传入css样式的class,id等属性
title = forms.CharField(max_length=20, label='职称', #下面这句话啥意思
# 以修饰的css样式为先,但它这里还能更改html表单的类型,TextInput表述文本框,若是charfield显示下拉框,优先使用文本框
widget=forms.widgets.TextInput(attrs={'class': 'c1'}), # 设置表单的css样式,TextInput表示文本输入框,要和charfield输入样式相同
error_messages={'required': '职称不能为空'},) # 设置验证失败后的参数信息,字典的键为表单的参数名称,字典的值为错误信息
# 设置字段参数validators;validators参数一定要列表形式么;该参数是自定义验证函数
payment = forms.IntegerField(label='薪资',validators=[payment_validate]) # IntegerField字段还可以添加额外验证函数
# 设置下拉框的值
# 查询模型PersonInfo的数据
value = PersonInfo.objects.values('name')
# 将数据以为列表格式表示,列表元素为元组格式
choices = [(i+1, v['name']) for i, v in enumerate(value)] # enumerate()生成(索引,值)的列表
# 表单字段设为ChoiceField类型,用生成下拉框
person = forms.ChoiceField(choices=choices, label='姓名')
# 自定义表单字段title的数据清洗(修改该字段的返回值)
# 最好函数名要与获取的表单字段名一样,方便阅读
def clean_job(self): # 函数应该是自定义的,并不是,返回值的话会返回给该字段
# 获取字段title的值
# 类可以调用方法中的属性,是由于有self么
# 提取的是表单字段的属性
data = self.cleaned_data['job'] # cleaned_data是默认的属性
return '初级' + data # 通过views.py返回给终端
ModelForm源码分析
djano/forms/models.py
主要类
class BaseModelForm:中
表单类ModelForm于模型之间没有直接的数据交互,模型表单与模型之间的数据交互是由函数modelform_factory实现的,该函数将自定义的模型表单与模型进行绑定,从而实现两者之间的数据交互。
模型表单的主要方法
clean():重写父类BaseForm的clean()方法,并将属性_validate_unique设为True。
validate_unique():验证表单数据是否存在异常
_save_m2m():将带有多对多关系的模型表单保存到数据库里
save():将模型表单的数据保存到数据库里。如果参数commit为True,就直接保存在数据库;否则生成数据库实例对象。
函数modelform_factory与类ModelForm定义在同一个源码文件中
模型表单的主要属性
model:必需属性,用于绑定Model对象
fields=None,:可选属性,设置模型内那些字段转换成表单字段,默认值为None,代表所有的模型字段,也可以将属性值设为"__all__",同样表示所有的模型字段。若只需部分模型字段,则将模型字段写入一个列表或一个元组里,再把该列表或元组作为属性值。
exclude=None:与field相反,禁止模型字段转换成表单字段。属性值以列表或元组表示,若设置了该属性,则属性fields无需设置。
widgets=None:可选属性,设置表单字段的参数widget,属性值以字典表示,字典的键为模型字段
localized_fields=None:可选参数,将模型字段设为本地化的表单字段,常用于日期类型的模型字段。
help_texts=None:可选属性,设置表单字段的参数help_text.
error_messages=None:可选属性,设置表单字段的参数error_messages
field_classes=None:可选属性,将模型字段重新定义,默认情况下,模型字段与表单字段遵从Django内置的转换规则。
模型字段与表单字段的转换规则
待补
实例
from django import forms
from .models import *
class VocationForm(forms.ModelForm):
# 添加模型外的表单字段;在模型已有的字段下再添加字段;添加的html位置在表单最下方
LEVEL = (('L1', '初级'),
('L2', '中级'),
('L3', '高级'),)
level = forms.ChoiceField(choices=LEVEL, label='级别') # 看来choices的前缀L1,L2之类的就是索引
# 模型与表单设置
class Meta:
# 绑定模型
model = Vocation
# 以下属性都是函数modelform_factory的属性
# fields属性用于设置转换字段,'__all__'是将全部模型字段转换成表单字段
# fields = '__all__'
# fields = ['job', 'title', 'payment', 'person']
# exclude用于禁止模型字段转换表单字段
exclude = []
# labels设置HTML元素控件的label标签
labels = {
'job': '职位',
'title': '职称',
'payment': '薪资',
'person': '姓名'
}
# 定义widgets,设置表单字段的CSS样式
widgets = {
'job': forms.widgets.TextInput(attrs={'class': 'c1'}),
}
# 重新定义字段类型
# 一般情况下模型字段会自动转换成表单字段
field_classes = {
'job': forms.CharField
}
# 帮助提示信息
help_texts = {
'job': '请输入职位名称'
}
# 自定义错误信息
error_messages = {
# __all__设置全部错误信息
'__all__': {'required': '请输入内容',
'invalid': '请检查输入内容'},
# 设置某个字段的错误信息
'title': {'required': '请输入职称',
'invalid': '请检查职称是否正确'}
}
# 自定义表单字段payment的数据清洗
def clean_payment(self):
# 获取字段payment的值
data = self.cleaned_data['payment'] + 1
return data
表单与模型表单小结
表单forms的属性和方法的源文件
在
django/forms/forms.py
主要类class BaseForm: (类中)
属性调用不加(),函数调用加()
表单字段类型和模型字段类型类似
源码位置django/forms/fields.py
视图里使用form
属性
prefix
prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
# 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据,把数据库中的数据写到表单上
无论是统计字符串还是列表
xx.count(传入统计的参数)
filter查询字段为0的情况下,就无法使用update,需要使用get_or_create(d),除主键外,只要有一个数据不同就插入
create(d),直接插入
详情实例待补
#这么多没写,不做笔记,一点也不记得了
from django.shortcuts import render
from django.http import HttpResponse
from .form import * # 导入表单进视图
from .models import * # 导入模型进视图
def index(request):
# GET请求
if request.method == 'GET':
id = request.GET.get('id', '')
if id:
d = Vocation.objects.filter(id=id).values() # 获取数据库数据
d = list(d)[0]
print(d)
d['person'] = d['person_id'] # 增加person键值对
# initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
# 将参数i传入表单VocationForm执行实例化
print(i)
v = VocationForm(**i) # 这是把数据库中的数据写到表单上
else: # 不带get请求参数,并设置参数prefix,这参数的作用是
# prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
# 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
v = VocationForm(prefix='vv')
return render(request, 'index.html', locals())
# POST请求
else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
# 由于在GET请求设置了参数prefix
# 实例化时必须设置参数prefix,否则无法获取POST的数据
v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
if v.is_valid():
# 获取网页控件name的数据
# 方法一
title = v['title']
# 方法二
# cleaned_data将控件name的数据进行清洗
ctitle = v.cleaned_data
print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
# 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
id = request.GET.get('id', '')
d = v.cleaned_data # 获取数据
# count统计的话一定要传递参数
print(d['person']) # 这是多的
# 外键要特别加
d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
# 删除字典中的person字段
del d['person']
# 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
# Vocation.objects.create(**d) # 更新数据需以字典显示
# 不这样更新,这样更新太难受了,常报错
# 是这个Vocation这个模型有毒把
# result = Vocation.objects.filter(id=id) # 提取所有的id字段
# result = False
# if not result:
# v.save()
# else:
# return int(value)
# ValueError: invalid literal for int() with base 10: ''
# Vocation.objects.filter(id=id).update(**d)
# 可能是多了一个person字段
Vocation.objects.create(**d) # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
return HttpResponse('提交成功')
else:
# 获取错误信息,并以json格式输出
error_msg = v.errors.as_json()
print(error_msg)
return render(request, 'index.html', locals())
视图里使用ModelForm
表单类Form和模型实现数据交互最主要的问题是表单字段和模型字段的匹配性,如果将表单类Form改为ModelForm,就无需考虑字段匹配性的问题。
最好使用模型表单,表单类提取数据保存太难受了,我的报错问题:
ValueError: invalid literal for int() with base 10: ''
# commit=False就会生成一个数据库对象,然后可以对该数据库对象进行增删改除,然后将修改的数据保存到数据库中。
# commit=True则直接将表单数据保存到数据库中
实例
from django.shortcuts import render
from django.http import HttpResponse
from .form import *
from .models import *
def index(request):
# GET请求
if request.method == 'GET':
id = request.GET.get('id', '')
if id:
i = Vocation.objects.filter(id=id).first() #这里id提取的是int类型么
# 将参数i传入表单VocationForm执行实例化
v = VocationForm(instance=i, prefix='vv') # instance参数为空的话,则返回一个新的表单
else:
v = VocationForm(prefix='vv')
return render(request, 'index.html', locals())
# POST请求
else:
# 由于在GET请求设置了参数prefix
# 实例化时设置参数prefix,否则无法获取POST的数据
v = VocationForm(data=request.POST, prefix='vv') #实例化的模型字段
# is_valid()会使字段payment自增加10
if v.is_valid():
# 根据请求参数id查询模型数据是否存在
id = request.GET.get('id')
result = Vocation.objects.filter(id=id) # 提取所有的id字段
# 数据不存在,则新增数据
if not result:
# 数据保存方法一
# 直接将数据保存到数据库
# v.save()
# 数据保存方法二
# 将save的参数commit=False
# 生成数据库对象v1,修改v1的属性值并保存
# commit=False就会生成一个数据库对象,然后可以对该数据库对象进行增删改除,然后将修改的数据保存到数据库中。
# commit=True则直接将表单数据保存到数据库中
v1 = v.save(commit=False) # 直接保存字段
v1.title = '初级' + v1.title
v1.save()
# 数据保存方法三
# save_m2m()方法用于保存ManyToMany的数据模型
# v.save_m2m()
return HttpResponse('新增成功')
# 数据存在,则修改数据
else:
d = v.cleaned_data
d['title'] = '中级' + d['title']
result.update(**d)
print(d)
return HttpResponse('修改成功')
else:
# 获取错误信息,并以json格式输出
error_msg = v.errors.as_json()
print(error_msg)
return render(request, 'index.html', locals())
8.4视图里使用Form
from django.shortcuts import render
from django.http import HttpResponse
from .form import * # 导入表单进视图
from .models import * # 导入模型进视图
def index(request):
# GET请求
if request.method == 'GET':
id = request.GET.get('id', '')
if id:
d = Vocation.objects.filter(id=id).values() # 获取数据库数据
d = list(d)[0]
print(d)
d['person'] = d['person_id'] # 增加person键值对
# initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
# 将参数i传入表单VocationForm执行实例化
print(i)
v = VocationForm(**i) # 这是把数据库中的数据写到表单上
else: # 不带get请求参数,并设置参数prefix,这参数的作用是
# prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
# 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
v = VocationForm(prefix='vv')
return render(request, 'index.html', locals())
# POST请求
else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
# 由于在GET请求设置了参数prefix
# 实例化时必须设置参数prefix,否则无法获取POST的数据
v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
if v.is_valid():
# 获取网页控件name的数据
# 方法一
title = v['title']
# 方法二
# cleaned_data将控件name的数据进行清洗
ctitle = v.cleaned_data
print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
# 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
id = request.GET.get('id', '')
d = v.cleaned_data # 获取数据
# count统计的话一定要传递参数
print(d['person']) # 这是多的
# 外键要特别加
d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
# 删除字典中的person字段
del d['person']
# 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
# Vocation.objects.create(**d) # 更新数据需以字典显示
# 不这样更新,这样更新太难受了,常报错
# 是这个Vocation这个模型有毒把
# result = Vocation.objects.filter(id=id) # 提取所有的id字段
# result = False
# if not result:
# v.save()
# else:
# return int(value)
# ValueError: invalid literal for int() with base 10: ''
# Vocation.objects.filter(id=id).update(**d)
# 可能是多了一个person字段
Vocation.objects.create(**d) # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
return HttpResponse('提交成功')
else:
# 获取错误信息,并以json格式输出
error_msg = v.errors.as_json()
print(error_msg)
return render(request, 'index.html', locals())
8.5### 表单的数据提取判断等逻辑都值得好好推敲
表单类的定义可参考前面的表单类
from django.shortcuts import render
from django.http import HttpResponse
from .form import * # 导入表单进视图
from .models import * # 导入模型进视图
def index(request):
# GET请求
if request.method == 'GET':
id = request.GET.get('id', '')
if id:
d = Vocation.objects.filter(id=id).values() # 获取数据库数据
d = list(d)[0]
print(d)
d['person'] = d['person_id'] # 增加person键值对
# initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
# 将参数i传入表单VocationForm执行实例化
print(i)
v = VocationForm(**i) # 这是把数据库中的数据写到表单上
else: # 不带get请求参数,并设置参数prefix,这参数的作用是
# prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
# 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
v = VocationForm(prefix='vv')
return render(request, 'index.html', locals())
# POST请求
else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
# 由于在GET请求设置了参数prefix
# 实例化时必须设置参数prefix,否则无法获取POST的数据
v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
if v.is_valid():
# 获取网页控件name的数据
# 方法一
title = v['title']
# 方法二
# cleaned_data将控件name的数据进行清洗
ctitle = v.cleaned_data
print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
# 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
id = request.GET.get('id', '')
d = v.cleaned_data # 获取数据
# count统计的话一定要传递参数
print(d['person']) # 这是多的
# 外键要特别加
d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
# 删除字典中的person字段
del d['person']
# 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
# Vocation.objects.create(**d) # 更新数据需以字典显示
# 不这样更新,这样更新太难受了,常报错
# 是这个Vocation这个模型有毒把
# result = Vocation.objects.filter(id=id) # 提取所有的id字段
# result = False
# if not result:
# v.save()
# else:
# return int(value)
# ValueError: invalid literal for int() with base 10: ''
# Vocation.objects.filter(id=id).update(**d)
# 可能是多了一个person字段
Vocation.objects.create(**d) # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
return HttpResponse('提交成功')
else:
# 获取错误信息,并以json格式输出
error_msg = v.errors.as_json()
print(error_msg)
return render(request, 'index.html', locals())
实现表单数据与模型交互需要注意以下事项
表单字段最好与模型字段相同,否则两种再进行数据交互时,不许将两者的字段进行转化
使用同一表单并且需要多次实例化表单时,处理参数initial和data的数据不同之外,其他参数设置必须相同,否则无法接收上一个表单对象所传递的数据信息。
参数initial是表单实例化的初始化数据,它只适用于模型数据传递给表单,再由表单显示在网页上;参数data是在表单实例化后,再将数据传递给实例化对象,只适用于表单接收http请求的请求参数。
参数prefix设置表单的控件属性name和id的值;若在一个网页里使用同一个表单类生成多个不同的网页表单,参数prefix可区分每个网页表单,则在接收或设置某个表单数据时与其他的表单数据混淆。
模型表单ModelForm实现数据保存只有save()和save_m2m()两种方法。使用save()保存数据时,参数commit的值会影响数据的保存方式。如果参数Commit为True,就直接将表单数据保存到数据库;如果参数commit为False,就会生成一个数据库对象,然后可以对该对象进行增、删、改、查等数据操作。再将修改后的数据保存到数据库。
save()只适合将数据保存在非多对多关系的数据表中,而save_m2m()只适合将数据保存在多对多关系的数据表中。