(8)Django - 表单

2019-03-10  本文已影响0人  libdream

表单是搜集用户数据信息,实现网页数据交互的关键。Django的表单功能由Form类实现,主要分两种:django.forms.Formdjango.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请求做了不同的响应处理。

将表单生成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函数大致相同:

定义模型是在定义模型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 = '我的手机'
表单初始化的方法有以下几种:

数据库的保存实际上只有save()和save_m2m()方法实现。save()只适合将数据保存在非多对多关系的数据表,而save_m2m只适合将数据保存在多对多关系的数据表。

上一篇 下一篇

猜你喜欢

热点阅读