表单是搜集用户数据信息,实现网页数据交互的关键。Django的表单功能由Form类实现,主要分两种:django.forms.Form
和django.forms.ModelForm
。前者是一个基础的表单功能,后者是在前者的基础上结合模型所生成的数据表单。
虽然在模板文件中直接编写表单是一种较为简单的实现方法,但如果表单元素较多,会在无形之中增加模板的代码量,对日后维护和更新造成极大的不便。为了简化表单的实现过程和提高表单的灵活性,Django提供了完善的表单功能。先来看个简单的例子。
在mysite的index中添加一个form.py用于编写表单的实现功能,然后在templates文件夹中添加模板data_form.html,用于将表单数据显示到网页上。
首先在form.py中添加以下代码:
#index 的 form.py
from django import forms
from .models import *
class ProductForm(forms.Form):
name = forms.CharField(max_length=20, label='名字')
weight = forms.CharField(max_length=50, label='重量')
size = forms.CharField(max_length=50, label='尺寸')
#设置下拉框的值
## choices_list = [(1,'手机'),(2, '平板')]
choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
type = forms.ChoiceField(choices=choices_list, label='产品类型')
在form.py中定义表单类ProductForm,类属性就是表单字段,对应HTML里的每一个控件。
然后在视图函数中导入form.py所定义的ProductForm类,在index函数中实例化生成对象,再将对象传递给模板文件。
#index 的 views.py
from django.shortcuts import render
from .form import *
def index(request):
product = ProductForm()
return render(request, 'data_form.html', locals())
最后在模板文件data_form.html中将对象product以HTML的<table>的形式展现在网页上。
templates 下的 data_form.html
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<table>
{{ product.as_table }}
</table>
<input type="submit" value="提交">
</form>
</body>
</html>
保存后运行结果如下:
image.png
表单的定义
Django的表单功能主要是通过定义表单类,再由类的实例化生成HTML的表单元素控件,以减少再模板中的硬编码。上例中的<table>源代码如下:
<table>
<tr><th><label for="id_name">名字:</label></th><td><input type="text" name="name" maxlength="20" required id="id_name"></td></tr>
<tr><th><label for="id_weight">重量:</label></th><td><input type="text" name="weight" maxlength="50" required id="id_weight"></td></tr>
<tr><th><label for="id_size">尺寸:</label></th><td><input type="text" name="size" maxlength="50" required id="id_size"></td></tr>
<tr><th><label for="id_type">产品类型:</label></th><td>
<select name="type" id="id_type">
<option value="1">手机</option>
<option value="2">平板电脑</option>
<option value="3">智能穿戴</option>
<option value="4">通用配件</option>
</select></td></tr>
</table>
通过对比,可以很容易发现HTML源代码和定义的ProductForm类中的字段和参数的对应关系。
除了上述表单字段外,Django还提供多种内置的表单字段:
字段 | 说明 |
---|---|
BooleanField | 复选框,如果字段带有initial=True, 复选框被勾上 |
CharField | 文本框,参数max_length和min_length |
ChoiceField | 下拉框,参数choices设置数据内容 |
TypedChoiceField | 与ChoiceField相似,但多出参数coerce和empty_value,分别代表强制转换数据类型和用于表示空值,默认为空字符串 |
DateField | 文本框,具有验证日期格式的功能,参数input_formats设置日期格式 |
EmailField | 文本框,验证输入数据是否符合邮箱格式,可选参数max_length和min_length |
FileField | 文件上传功能,参数max_length和allow_empty_file分别设置最大长度和文件内容是否为空 |
FilePathField | 在特定的目录选择并上传文件,参数path是必需参数,参数recursive、match、allow_files和allow_folders为可选参数 |
FloatField | 验证数据是否为浮点数 |
ImageField | 验证文件是否为Pillow库可识别的图像格式 |
IntegerField | 验证数据是否为整型 |
GenericIPAddressField | 验证数据是否为有效数值 |
SlugField | 验证数据是否只包含字母、数字、下划线及连字符 |
TimeField | 验证数据是否为datetime.time或指定特定时间格式的字符串 |
URLField | 验证数据是否为有效的URL地址 |
表单字段除了转换HTML控件之外,还具有一定的数据格式规范,规范主要由字段类型和字段共同实现。每个不同类型的表单字段都由一些自己特殊的参数,但每个表单字段都具有以下的共同参数:
参数 | 说明 |
---|---|
Required | 输入数据是否为空,默认为True |
Widget | 设置HTML控件的样式 |
Label | 生成Label标签或显示内容 |
Initial | 设置初始值 |
help_text | 设置帮助提示信息 |
error_messages | 设置错误信息,以字典格式表示:{‘required’:'不能为空','invalid':'格式错误'} |
show_hidden_initial | 值为True或False,是否在当前控件后面再加一个 隐藏的且具有默认值的控件(可用于检验两次输入值是否一致) |
Validators | 自定义数据验证规则。以列表格式表示,列表元素为函数名 |
Lacalize | 值为True/False,是否支持本地化,如不同时区显示相应的时间 |
Disabled | 值为True/False,是否可以编辑 |
label_suffix | Label内容后缀,在Label后添加内容 |
根据以上参数说明,对form.py的表单ProductForm的字段进行优化:
#form.py
from django import forms
from .models import *
from django.core.exceptions import ValidationError
#自定义数据验证函数
def weight_validate(value):
if not str(value).isdigit():
raise ValidationError('请输入正确的重量 ')
class ProductForm(forms.Form):
#设置错误信息并设置样式
name = forms.CharField(max_length=20, label='名字',
widget=forms.widgets.TextInput(attrs={'class':'c2'}),
error_messages={'required':'名字不能为空'})
#使用自定义数据验证函数
weight = forms.CharField(max_length=50, label='重量',
validators=[weight_validate])
size = forms.CharField(max_length=50, label='尺寸')
#设置下拉框的值
## choices_list = [(1,'手机'),(2, '平板')]
choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
#设置CSS样式
type = forms.ChoiceField(widget=forms.widgets.Select(attrs={'class':'type','size':3}),
choices=choices_list, label='产品类型')
优化的代码分别使用了参数widget、label、error_messages和validators,这4个参数是实际开发中常用的参数。为了进一步验证优化后的表单是否正确运行,还要对views.py的视图函数index代码进行优化
#views.py 的 index 函数
from django.http import HttpResponse
from django.shortcuts import render
from .form import *
def index(request):
#GET请求
if request.method == 'GET':
product = ProductForm()
return render(request, 'data_form.html', locals())
#POST请求
else:
product = ProductForm(request.POST)
if product.is_valid():
#获取网页控件name的数据
#方法1
## name = product['name']
#方法2
#cleaned_data将控件name的数据进行清洗,转换成Python数据类型
name = product.cleaned_data['name']
return HttpResponse('提交成功')
else:
#将错误信息输出,error_msg是将错误信息以json格式输出
error_msg = product.errors.as_json()
return render(request, 'data_form.html', locals())
上述代码,首先判断了用户的请求方式,index函数对GET和POST请求做了不同的响应处理。
- 用户首次访问url地址时,等于向项目发送一个GET请求,函数index将表单ProductForm实例化并传递给模板,由模板引擎生成HTML表单返回给用户。
- 当用户在表单中输入相关信息并提交时,等于向项目发送一个POST请求,函数index首先获取表单数据对象product,然后由
is_valid()
方法对数据对象product进行数据验证。 -
如果验证成功,可以使用product['name']或prodcut.cleaned_data['name']方法来获取用户在某个控件上的输入值,实现表单和模型的信息交互。
下面来验证一下是否能正常工作,在姓名栏输入了空格
image.png
点击提交按钮后,
image.png
验证正常工作了。
在上述例子中,模板data_form.html的表单是使用HTML的<table>标签展现在网页上,除此之外,表单还可以用其他HTML标签展现,只需将模板data_form.html的对象product使用以下方法即可生成其他HTML标签:
将表单生成HTML的 ul 标签
{{ product.as_ul }}
将表单生成HTML的 p 标签
{{ product.as_p }}
生成单个HTML元素控件
{{ product.type }}
获取表单字段的参数 label 属性值
{{ product.type.label }}
模型与表单
Django的表单分两种:基础表单django.forms.Form
和数据表单django.forms.ModelForm
。数据表单是将模型的字段转换成表单的字段,再从表单的字段生成HTML的元素控件。Django通过ModelForm表单功能模块实现了表单数据与模型数据之间的交互开发。
首先再form.py中定义表单ProductModelForm。该类可分为三大部分:添加模型外的表单字段、模型与表单设置和自定义表单字段的数据清洗。
from django import forms
from .models import *
from django.core.exceptions import ValidationError
#数据库表单
class ProductModelForm(forms.ModelForm):
#步骤1:添加模型外的表单字段
productId = forms.CharField(max_length=20, label='产品序号')
#步骤2:模型与表单设置
class Meta:
#绑定模型,必选
model = Product
#设置转换字段,必选,属性值为'__all__'时全部转换
#fields = '__all__'
fields = ['name','weight','size','type']
#禁止模型转换的字段,可选,若设置了该属性,fields则可以不设置
exclude = []
#设置HTML元素控件的label标签,可选
labels = {'name':'产品名称',
'weight':'重量',
'size':'尺寸',
'type':'产品类型',
}
#设置表单字段的CSS样式,可选
widgets = {'name':forms.widgets.TextInput(attrs={'class':'c1'}),
}
#定义字段的类型,可选,默认时自动转换的
field_classes = {
'name': forms.CharField,
}
#设置提示信息
help_texts = {
'name':'',
}
#自定义错误信息
error_messages = {
#设置全部错误信息
'__all__':{'required':'请输入内容',
'invalid':'请检查输入内容'},
#设置某个字段的错误信息
'weight':{'required':'请输入重量数值',
'invalid':'请检查数值是否正确'},
}
#步骤3: 自定义表单字段的数据清洗
def clean_weight(self):
#获取字段weight的值
data = self.cleaned_data['weight']
return data + 'g'
模型字段转换成表单字段主要在类Meta中实现,由类Meta的属性实现两者之间的转换,其属性说明如下:
属性 | 说明 |
---|---|
model | 必需属性,用于绑定Model对象 |
fields | 必需属性,设置模型内哪些字段转换成表单字段。属性值为"all"时表示整个模型的字段,若设置一个或多个,使用列表或元组表示,列表或元组里的元素是模型的字段名 |
exclude | 可选属性,与fields相反,表示禁止模型哪些字段转换成表单字段。若设置了该属性,则属性fields可以不用设置 |
labels | 可选属性,设置表单字段里的参数label。属性值以字典表示,字典的键是模型的字段 |
widgets | 可选属性,设置表单字段里的参数widget |
field_classes | 可选,将模型的字段类型重新定义为表单字段类型,默认模型字段类型会自动转换为表单字段类型 |
help_texts | 可选,设置表单字段里的参数help_text |
error_messages | 可选,设置表单字段里的参数error_messages |
需要注意的是,一些较为特殊的模型字段在转换表单时会有不同的处理方式。例如模型字段的类型为AutoField,该字段在表单中不存在对应的表单字段;模型字段类型为ForeignKey和ManyToManyField,在表单中对应的表单字段为ModelChoiceField和ModelMultipleChoiceField。
在自定义数据清洗函数时,必须以“clean_字段名”的格式作为函数名,而且函数必须有return返回值。如果在函数中设置了ValidationError异常抛出,那么该函数可视为带有数据验证的清洗函数。
通过定义表单类ProductModelForm将模型Product与表单相互结合起来后,还要通过视图函数来使用和展现表单,继续沿用前面的模板data_form.html,在项目的urls.py和views.py中分别定义新的URL地址和视图函数:
#urls.py
path('<int:id>.html', views.model_index),
#views.py 的视图函数 model_index
def model_index(request, id):
if request.method == 'GET':
instance = Product.objects.filter(id=id)
#判断数据是否存在
if instance:#如果存在,将数据传递给参数instance
product = ProductModelForm(instance=instance[0])
else:#如果不存在,为name字段设置一个默认值
product = ProductModelForm(initial={'name':'iphone XS'})
return render(request, 'data_form.html',locals())
else:
product = ProductModelForm(request.POST)
if product.is_valid():
#获取并清洗weight的数据
weight = product.cleaned_data['weight']
#数据保存方法1:直接保存到数据库
## product.save()
#数据保存方法2:save方法设置commit=False,将生成数据库对象product_db,
#然后对该对象的属性值修改并保存
product_db = product.save(commit=False)
product_db.name = '我的 iphone'
product_db.save()
#数据保存方法3:save_m2m()保存ManyTOMany的数据模型
## product.save_m2m()
return HttpResponse('提交成功!weight清洗后的数据是:'+weight)
else:
#将错误信息以json格式输出
error_msg = product.errors.as_json()
print(error_msg)
return render(request, 'data_form.html', locals())
函数model_index的处理逻辑和上面的index函数大致相同:
- 首先判断用户的请求方式,不同的请求方式做不同的处理。
- 若是GET请求,函数根据URL传递的变量id来查找模型Product的数据,如果数据存在,以参数的形式传递给表单ProductModelForm的参数instance,在生成网页时,模型数据会填充到对应的元素控件上,如下图: image.png
- 若时POST请求,函数首先对表单数据进行验证,如果验证失败,返回失败信息;验证成功,则使用cleaned_data方法对字段weight进行清洗,最后将表单数据保存到数据库。
在以上代码中,还存在一点小问题,比如产品类型的下拉框数据是模型Type对象,如果要将模型Type的字段type_name的数据在下拉框中展示,可以通过定义模型或定义表单两方面解决:
定义模型是在定义模型Type时,设置该模型的返回值。
#models.py
class Type(models.Model):
id = models.AutoField(primary_key=True)
type_name = models.CharField(max_length=20)
#设置返回值,若不设置,则默认返回Type对象
def __str__(self):
return self.type_name
再运行一次刚才的网址,可以看到产品类型已经变成了名称。
image.png如果存在多个下拉框,而且每个下拉框的数据分别取同一个模型的不同字段,那么可以在定义表单类的时候重写初始化函数
__init__()
#forms.py
class ProductModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProductModelForm, self).__init__(*args, **kwargs)
#设置下拉框的数据
type_obj = Type.objects.values('type_name')
choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
self.fields['type'].choices = choices_list
#初始化字段name
self.fields['name'].initial = '我的手机'
表单初始化的方法有以下几种:
- 在视图函数中对表单实例化时,设置实例化对象的参数initial。如product = ProductModelForm(initial={'name':'iphone XS'}),参数值以字典格式表示,字典键为表单的字段名,该方法适用于所有表单类。
- 在表单类中进行实例化时,如果初始化的数据是一个模型对象的数据,可设置参数instance,如product = ProductModelForm(instance=instance[0]),该方法只适用于ModelForm.
- 定义表单字段时,对表单字段设置初始化参数initial,此方法不适用于ModelForm,如name = forms.CharField(initial='我的手机')。
-
重写表单类的初始化函数
__init__
,适用于所有表单类,如在初始化函数中设置slef.fields['name'].initial='我的手机'.
数据库的保存实际上只有save()和save_m2m()方法实现。save()只适合将数据保存在非多对多关系的数据表,而save_m2m只适合将数据保存在多对多关系的数据表。
网友评论