在常规表单开发中,我们常用的做法是手动编写表单至html
模板中,请求数据到视图中处理,如果有多个表单就要多次这样操作,视图中也要多次处理验证这些数据,有一个字段要修改就要去所有与表单关联的地方处理,Django
为我们提供了Froms
组件帮助我们解决这种问题,统一管理统一处理。
类似模型,Django
表单也由各种字段组成。表单可以自定义(forms.Form
),也可以由模型Models创建(forms.ModelForm
)。值得注意的是模型里用的是verbose_name
来描述一个字段, 而表单用的是label
。这两种方式其实只在forms
中有区别,视图和模板中使用方式一致,我们这里就先将自定义表单,不同的地方会讲下模型表单。
Django
的常用做法是在app
文件夹下创建一个forms.py
,专门存放app
中所定义的各种表单,这样方便集中管理表单。如果要使用上述表单,我们可以在视图views.py
里把它们像模型一样import
进来直接使用。我们这里统一在我们的wechat
应用下创建forms.py
文件,来存放我们的自定义表单
表单字段类型
讲到表单之前我们先讲下表单字段的类型:
西面的StudentForm
类只有一个字段,字段类型是CharField
,对应的HTML
元素是
<input type=”text ” ...>
,这里的HTML
元素叫作字段的Widget
。除此之外, Django
的Form
类
还提供了几十种字段类型, 每种类型分别对应不同的HTML
元素,下面对这些类型进行简
单介绍。如果需要更详细的表单字段介绍,可以参考Django
官网: https://docs.djangoproject.com/en/2.0/ref/forms/fields 。
BooleanField
Widget
: Checkboxlnput( < input type=checkbox ” ...>)
。
空值: False
。
标惟值: True
、False
。
验证:如果设置了required =True
, 则验证字段值是否为True
。
验证点: required
。CharField
Widget
: Textlnput( < input type=" text ” ...> )
。
空值: empty_value
。
标准值: 字符串。
验证: 如果设置了max_length
, min_length
,则验证字段长度是再符合要求,否则不验证。
验证点: required
, max_length
, min_length
。ChoiceField
Widget
: Select( <select><option ...> ... </select>)
。
空值:””。
标准值: 字符串。
验证: 验证字段值是否存在。
验证点: required
, invalid_choice
DateField
Widget
: Datelnput( < input type="text” ...>)
。
空值: None
。
标准值: Python
datetime.date
巳对象。
验证:验证字段值是否是正确的时间格式字符串、datetime .date
对象、datetime.datetime
对象。
验证点: required
, invalid
。DateTimeField
Widget
: Datelnput( < input type=" text" ... > )
。
空值: None
标准值: Python
datetime. datetime
对象。
验证: 验证字段值是否是正确的时间格式字符串、datetime.date
对象、datetime.datetime
对象。
验证点: required
, invalid
。DecimalField
Widget
:当Field.localize=False
时对应Numberlnput(<input type=”number”...>)
,否则对
应Textlnput(<inputtype="text” ...>)
。
空值:None
。
标准值:Python decimal
对象。
验证:验证字段值是否是数值类型。
验证点:required
,invalid
,max_value
,min_value
,max_digits
,max_decimal_places
,max_whole_digits
。FileField
Widget
:ClearableFilelnput(<input type="file”...>)
。
空值:None
。
标准值:包含文件内容与文件名的UploadedFile
对象。
验证:空文件或者没有选择文件。
验证点:required
,invalid
,missing
,empty
,max_length
。FilePathField
Widget
:Select(<select><option ...> ...</select>)
。
空值:None
。
标准值:字符串。
验证:选中的选项是否存在于下拉列表中。
验证点:required
,invalid_choice
。lmageField
Widget
:ClearableFilelnput(<input type="file” ...>)
。
空值:None
。
标准值:包含文件内容与文件名的UploadedFile
对象。
验证:空文件或者没有选择文件。
验证点:required
,invalid
,missiing
,empty
,invalid_image
。lntegerField
Widget
:当Field.localize=False
时对应Numberlnput(<inputtype="number” ...>)
,否则对
应Textlnput(<inputtype=”text” ...>)
。
空值:None
。
标准值: Python integer
对象。
验证:验证宇段值是否是一个整数。
验证点: required
, invalid
, max_value
, min_value
。MultipleChoiceField
Widget
: SelectMultiple(<select multiple="multiple”> ... </select>)
。
空值: []
(空列表)。
标准值:一组字符串。
验证:所有选中值存在于下拉列表中。
验证点: required
, invalid_ choice
, invalid_list
。
表单通用属性
required
默认情况下,所有的表单字段都是必填字段,这样如果提交表单时没有为字段赋值,则
会抛出ValidationError
异常。
对于非必填宇段可以设置required=False
避免验证错误,例如:
1
| forms.CharField(required=False)
|
label
为表单字段指定一个label
元素用于显示字段信息,如上面your_name
字段将会额外显
示一个label
:
1
| <label for="your name">Your name : </label>
|
initial
为宇段设置初始值。
help_ text
为字段添加帮助性文字。
error_messages
重写字段的默认错误提示信息, error_messages
是一个字典类型。
例如设置当CharField
的required
验证失败时显示‘请输入你的名字’:
name= forms.CharField(error_messages={'required':'请输入你的名字可'})
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| 2. `localize` 设置表单宇段是否启用本地化。
3. `disabled` 当设置`disabled=True` 时,使用`HTML disabled` 属性禁用字段。
### 表单字段内置属性
除了上面的通用属性,每个字段都有自己的内置属性
```python 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类型 ...
|
自定义表单
编写自定义表单
1 2 3 4 5 6 7 8 9
| from django import forms from .models import Student
CLASSES_CHOICES = ('软件二班', '图像一班', '师范一班') class StudentForm(forms.Form): name = forms.CharField(min_length=4, required=True, label='姓名') age = forms.IntegerField() score = forms.DecimalField() classes = forms.ChoiceField(choices=CLASSES_CHOICES)
|
表单实例化
下面方法可以实例化一个空表单,但里面没有任何数据
用户提交的数据可以通过以下方法与表单结合,生成与数据结合过的表单(Bound forms
)。Django
只能对Bound forms
进行验证。
1
| form = StudentForm(data=request.POST, files=request.FILES)
|
我们暂时的视图代码:
1 2 3 4 5 6 7 8 9 10
| from django.shortcuts import render, HttpResponse from .forms import StudentForm
def index(request): form = StudentForm() if request.method == 'POST': form = StudentForm(request.POST) return render(request, 'index.html', {'form':form})
|
模板使用表单
模板文件中我们可以通过,
和
中渲染表单。如果使用则默认等于使用了
。
或
1 2 3 4 5 6 7 8 9
| {% extends 'layout.html' %} {% block main %} <div class="uk-container"> <form action="{% url 'wechat:index' %}" method="post"> {% csrf_token %} {{ form }} </form> </div> {% endblock %}
|
渲染后的页面为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.2.2/dist/css/uikit.min.css"/>
</head> <body> <div class="uk-container"> <form action="/wechat/index/" method="post"> <input type="hidden" name="csrfmiddlewaretoken" value="j4mMZkPNIjVGEwr4wc8u1iywfuIMFOu300iU1gmOjP8FJdJmMSiNHhxxPzPP6kKZ"> <tr><th><label for="id_name">姓名:</label></th><td><input type="text" name="name" minlength="4" required id="id_name"></td></tr> <tr><th><label for="id_age">Age:</label></th><td><input type="number" name="age" required id="id_age"></td></tr> <tr><th><label for="id_score">Score:</label></th><td><input type="number" name="score" step="any" required id="id_score"></td></tr> <tr><th><label for="id_classes">Classes:</label></th><td><select name="classes" id="id_classes"> <option value="软件二班">软件二班</option>
<option value="图像一班">图像一班</option>
<option value="师范一班">师范一班</option>
</select></td></tr> </form> </div>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.2.2/dist/js/uikit.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/uikit@3.2.2/dist/js/uikit-icons.min.js"></script> </body> </html>
|
##### 使用`{{ form.as_p }}`渲染
表单部分生成的结果为:
1 2 3 4 5 6 7 8 9 10 11
| <p><label for="id_name">姓名:</label> <input type="text" name="name" minlength="4" required id="id_name"></p> <p><label for="id_age">Age:</label> <input type="number" name="age" required id="id_age"></p> <p><label for="id_score">Score:</label> <input type="number" name="score" step="any" required id="id_score"></p> <p><label for="id_classes">Classes:</label> <select name="classes" id="id_classes"> <option value="软件二班">软件二班</option>
<option value="图像一班">图像一班</option>
<option value="师范一班">师范一班</option>
</select></p>
|
##### 使用`{{form.as_ul}}`渲染
表单部分生成的结果为:
1 2 3 4 5 6 7 8 9 10 11
| <li><label for="id_name">姓名:</label> <input type="text" name="name" minlength="4" required id="id_name"></li> <li><label for="id_age">Age:</label> <input type="number" name="age" required id="id_age"></li> <li><label for="id_score">Score:</label> <input type="number" name="score" step="any" required id="id_score"></li> <li><label for="id_classes">Classes:</label> <select name="classes" id="id_classes"> <option value="软件二班">软件二班</option>
<option value="图像一班">图像一班</option>
<option value="师范一班">师范一班</option>
</select></li>
|
如果你想详细控制每个field
的格式,你可以采取以下方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| {% extends 'layout.html' %}
{% block main %} <div class="uk-container"> <form action="{% url 'wechat:index' %}" method="post" novalidate> {% csrf_token %} {% for field in form %} <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">{{ field.label }}</label> <div class="uk-form-controls"> {{ field }} </div> {% if field.help_text %} <p class="help">{{ field.help_text|safe }}</p> {% endif %} {{ field.errors }} </div> {% endfor %} <div class="uk-margin"> <button class="uk-button uk-button-primary " type="submit">提交</button> </div> </form> </div> {% endblock %}
|
发起post
请求后渲染出的表单部分为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">姓名</label> <div class="uk-form-controls"> <input type="text" name="name" minlength="4" required id="id_name"> </div> <ul class="errorlist"><li>这个字段是必填项。</li></ul> </div> <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">Age</label> <div class="uk-form-controls"> <input type="number" name="age" value="23" required id="id_age"> </div> </div> <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">Score</label> <div class="uk-form-controls"> <input type="number" name="score" step="any" required id="id_score"> </div> <ul class="errorlist"><li>这个字段是必填项。</li></ul> </div> <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">Classes</label> <div class="uk-form-controls"> <select name="classes" id="id_classes"> <option value="软件二班" selected>软件二班</option>
<option value="图像一班">图像一班</option>
<option value="师范一班">师范一班</option>
</select> </div>
|
示例
我们通常将自定义表单写入到forms.py
中,当然你也可以不用新建forms.py
而直接在html
模板里写表单,但我并不建议这么做。用forms.py
的好处显而易见:
- 所有的表单在一个文件里,非常便于后期维护,比如增添或修订字段。
forms.py
可通过clean
方法自定义表单验证,非常便捷(见后文)。
继续刚才我们的视图,我们对用户提交的数据进行验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from django.shortcuts import render, HttpResponse from .forms import StudentForm
def index(request): form = StudentForm() if request.method == 'POST': form = StudentForm(request.POST) if form.is_valid(): name = form.cleaned_data['name'] age = form.cleaned_data['age'] score = form.cleaned_data['score'] classes = form.cleaned_data['classes'] stu_obj = Student.objects.create(name=name, age=age, score=score, classes=classes) return HttpResponse('学生《%s》创建成功' % name) return render(request, 'index.html', {'form':form})
|
我们来理下StudentForm
整个流程:
- 当用户通过
POST
方法提交表单,我们将提交的数据与StudentForm
结合,然后验证表单StudentForm
的数据是否有效。 - 如果表单数据有效,我们创建
student
对象。用户通过一张表单提交数据。 - 如果添加学员成功,我们通过
HttpResponse
返回页面学生添加成功提示 - 如果用户没有提交表单或不是通过POST方法提交表单,我们转到添加页面,生成一张空的
StudentForm
表单验证
每个forms
类可以通过clean
方法自定义表单验证。如果你只想对某些字段进行验证,你可以通过clean_字段名
方式自定义表单验证。如果用户提交的数据未通过验证,会返回ValidationError
,并呈现给用户。如果用户提交的数据有效form.is_valid()
,则会将数据存储在cleaned_data
里。
在上述添加学员的案例里,我们在StudentForm``通过``clean
方法添加了姓名验证,年龄验证和分数验证。代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| from django import forms from .models import Student
CLASSES_CHOICES = ( ('软件二班', '软件二班'), ('图像一班', '图像一班'), ('师范一班', '师范一班') )
class StudentForm(forms.Form): name = forms.CharField(label='姓名') age = forms.IntegerField() score = forms.DecimalField() classes = forms.ChoiceField(choices=CLASSES_CHOICES) def clean_name(self): name = self.cleaned_data.get('name')
if len(name)<2: raise forms.ValidationError('你的名字也太短了,蒙我的吧!') elif len(name)>10: raise forms.ValidationError('这么长的名字?滚犊子!') else: stu_obj = Student.objects.filter(name__exact=name) if len(stu_obj)>0: raise forms.ValidationError('这学生报过名了,你走开!') return name
def clean_age(self): age = self.cleaned_data.get('age')
if age<1 or age>120: raise forms.ValidationError('你这年龄绝了,拒绝!') return age def clean_score(self): score = self.cleaned_data.get('score')
if score<0 or score>120: raise forms.ValidationError('啥情况啊,分页能飞啊!') return score
|
Django forms
的每个字段你都可以选择你喜欢的输入widget
,比如多选,复选框。你还可以定义每个widget
的css
属性。如果你不指定,Django
会使用默认的widget
,有时比较丑。
比如下面这段代码定义了表单姓名字段的输入控件为Textarea
,还指定了其样式css
。
1 2 3 4 5 6
| class StudentForm(forms.Form): name = forms.CharField(label='姓名', widget=forms.Textarea( attrs={ 'class':'uk-textarea' } ))
|
模板渲染出来的结果为:
1
| <textarea name="name" cols="40" rows="10" class="uk-textarea" required id="id_name">
|
设置widget
可以是你的表单大大美化,方便用户选择输入。比如下面案例里对年份使用了SelectDateWidget
,颜色则使用了复选框CheckboxSelectMultiple
。单选可以用RadioSelect
和Select
。常见文本输入可以用TextInput
和TextArea
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| BIRTH_YEAR_CHOICES = ('1980', '1981', '1982') COLORS_CHOICES = ( ('blue', 'Blue'), ('green', 'Green'), ('black', 'Black'), ) GENDER_CHOICES = ( ('male', '男'), ('female', '女') ) class SimpleForm(forms.Form): birth_year = forms.DateField(widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICES)) favorite_colors = forms.MultipleChoiceField( required=False, widget=forms.CheckboxSelectMultiple( attrs={ 'class':'uk-checkbox' } ), choices=COLORS_CHOICES, ) gender = forms.CharField(widget=forms.RadioSelect( choices=GENDER_CHOICES, attrs={ 'class': 'uk-radio' } ))
|
渲染结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">Birth year</label> <div class="uk-form-controls"> <select name="birth_year_year" required id="id_birth_year_year"><option value="1980">1980</option><option value="1981">1981</option><option value="1982">1982</option></select><select name="birth_year_month" required id="id_birth_year_month"><option value="1">一月</option><option value="2">二月</option><option value="3">三月</option><option value="4">四月</option><option value="5">五月</option><option value="6">六月</option><option value="7">七月</option><option value="8">八月</option><option value="9">九月</option><option value="10">十月</option><option value="11">十一月</option><option value="12">十二月</option></select><select name="birth_year_day" required id="id_birth_year_day"><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="11">11</option><option value="12">12</option><option value="13">13</option><option value="14">14</option><option value="15">15</option><option value="16">16</option><option value="17">17</option><option value="18">18</option><option value="19">19</option><option value="20">20</option><option value="21">21</option><option value="22">22</option><option value="23">23</option><option value="24">24</option><option value="25">25</option><option value="26">26</option><option value="27">27</option><option value="28">28</option><option value="29">29</option><option value="30">30</option><option value="31">31</option></select> </div> </div> <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">Favorite colors</label> <div class="uk-form-controls"> <ul id="id_favorite_colors" class="uk-checkbox"> <li><label for="id_favorite_colors_0"><input type="checkbox" name="favorite_colors" value="blue" class="uk-checkbox" id="id_favorite_colors_0"> Blue</label>
</li> <li><label for="id_favorite_colors_1"><input type="checkbox" name="favorite_colors" value="green" class="uk-checkbox" id="id_favorite_colors_1"> Green</label>
</li> <li><label for="id_favorite_colors_2"><input type="checkbox" name="favorite_colors" value="black" class="uk-checkbox" id="id_favorite_colors_2"> Black</label>
</li> </ul> </div> </div> <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">Gender</label> <div class="uk-form-controls"> <ul id="id_gender" class="uk-radio"> <li><label for="id_gender_0"><input type="radio" name="gender" value="male" class="uk-radio" required id="id_gender_0"> 男</label>
</li> <li><label for="id_gender_1"><input type="radio" name="gender" value="female" class="uk-radio" required id="id_gender_1"> 女</label>
</li> </ul> </div>
|
自定义属性和错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from django import forms
class LoginForm(forms.Form): username = forms.CharField( required=True, max_length=20, min_length=6, error_messages={ 'required': '用户名不能为空', 'max_length': '用户名长度不得超过20个字符', 'min_length': '用户名长度不得少于6个字符', } ) password = forms.CharField( required=True, max_length=20, min_length=6, error_messages={ 'required': '密码不能为空', 'max_length': '密码长度不得超过20个字符', 'min_length': '密码长度不得少于6个字符', } )
|
表单数据初始化**
有时我们需要对表单设置一些初始数据,我们可以通过initial方法,如下所示。
1 2 3 4 5
| def index(request): form = StudentForm(initial={ 'name':'默认叫张三吧' })
|
渲染结果:
1 2
| <textarea name="name" cols="40" rows="10" class="uk-textarea" required id="id_name"> 默认叫张三吧</textarea>
|
模型表单
自定义表单时我们简单提了一下模型表单,跟自定义表单最大的区别就是定义表单的部分,视图及模板没有任何改变。
定义模型表单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from django import forms from .models import Student class StudentForm(forms.ModelForm): class Meta: model = Student fields = ('name', 'age', 'score', 'classes') widgets = { 'name':forms.TextInput(attrs={ 'class':'你好' }) } error_messages = { 'name':{ 'max_length':'太长了' } }
|
视图和模板我们不做任何改变,正常渲染结果
![img]()
我们 在class Meta 中使用了一些元数据项,比如说 exclude、labels 以及 fields,当然还有些其他的选项,在 Django 官方网站 ModelForm 的定义如下所示
1 2 3 4
| def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, localized_fields=None, labels=None, help_texts=None, error_messages=None, field_classes=None, *, apply_limit_choices_to=True):
|
1) fields
其为列表或元组类型,与 exclude 相反,它指定当前的表单应该包含哪些字段,如果要所有的 Model 字段都包含在表单中,可以设定 fields=’all‘。ModelForm 的定义中必须要包含 fields 或 exclude 选项,否则将会抛出异常,同时给出错误提示:
Creating a ModelForm without either the’fields’attribute or the’exclude’attribute is prohibited。
2) labels
其为字典类型,用于定义表单字段的名称(输入框左边显示的名称)。表单字段的名称首先会使用 Model 字段定义设置的 verbose_name,如果没有设置,则直接使用字段名。因此当没有定义 verbo se_name 时,就可以使用 labels 选项来指定字段名。例如:
1
| labels={'title':'标题', 'price':'价格'}
|
3) help_texts
其为字典类型,用于给表单字段添加帮助信息。目前页面中表单字段的帮助信息(输入框下方显示的内容)来自 Model字段的 help_texts 定义,如果没有定义则什么都不显示。help_texts 的定义方式与 labels 选项类似,例如:
1
| help_texts={"title":"书籍的名称", "price':"书籍价格"}
|
其为字典类型,用于定义表单字段选用的控件。默认情况下,ModelForm 会根据Model字段的类型映射表单 Field 类,因此会应用 Field 类中默认定义的 widgets。这个选项用于自定义控件类型,例如:
1 2 3 4 5
| widgets = { 'type': forms.SelectMultiple(attrs={ 'class': 'form-control' }) }
|
5) field_classes
字典类型,用于指定表单字段使用的 Field 类型。默认情况下,对于 title 字段,ModelForm 会将它映射为 fields.CharField 类型。可以根据需要改变这种默认行为,例如,将 title 设置为如下类型:
1
| field_calss={"title":forms.URLField}
|
6) error_messages
字典类型,用来指定表单字段校验规则,即验证失败时的报错信息。
1 2 3 4 5
| error_messages = { 'type': { 'required': '移民类型至少选一个' } }
|
表单数据初始化
除了跟自定义表单中视图示例初始化使用initial
外,我们还可以初始化一个学员对象,比如我们编辑的时候就经常会显示原来数据。
1 2 3 4 5 6 7 8
| from django.shortcuts import render, HttpResponse from .forms import StudentForm
def index(request): stu_obj = Student.objects.first() form = StudentForm(instance=stu_obj) return render(request, 'index.html', {'form':form})
|
模板渲染结果:
![img]()
Formset
(表单集)是多个表单的集合。Formset
在Web
开发中应用很普遍,它可以让用户在同一个页面上提交多张表单,一键添加多个数据,比如一个页面上添加多个学员。
Django
针对不同的formset
提供了3种方法: formset_factory
, modelformset_factory
和inlineformset_factory
。我们接下来分别看下如何使用它们。
对于继承forms.Form
的自定义表单,我们可以使用formset_factory
。我们可以通过设置extra
和max_num
属性来确定我们想要展示的表单数量。注意: max_num
优先级高于extra
。比如下例中,我们想要显示3个空表单(extra=3),但最后只会显示2个空表单,因为max_num=2
。
forms.py
自定义表单文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from django import forms from .models import Student
class StudentForm(forms.ModelForm): class Meta: model = Student fields = ('name',) widgets = { 'name':forms.TextInput(attrs={ 'class':'你好' }) } error_messages = { 'name':{ 'max_length':'太长了' } }
StudentFormSet = forms.formset_factory( form=StudentForm, extra=3, max_num=2 )
|
view视图文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| from django.shortcuts import render, HttpResponse
from .models import Student from .forms import StudentFormSet
def index(request): formset = StudentFormSet() if request.method == 'POST': formset = StudentFormSet(request.POST) if formset.is_valid(): for form_data in formset.cleaned_data: Student.objects.create(**form_data) return HttpResponse('都添加成功了') return render(request, 'index.html', {'formset':formset}) return render(request, 'index.html', {'formset':formset})
|
template模板文件:
方式一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| {% extends 'layout.html' %}
{% block main %} <div class="uk-container"> <form action="{% url 'wechat:index' %}" method="post" novalidate> {{ formset.management_form }} {% csrf_token %} {{ formset }} <div class="uk-margin"> <button class="uk-button uk-button-primary " type="submit">提交</button> </div> </form> </div> {% endblock %}
|
方式二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| {% extends 'layout.html' %}
{% block main %} <div class="uk-container"> <form action="{% url 'wechat:index' %}" method="post" novalidate> {{ formset.management_form }} {% csrf_token %} {% for form in formset %} {{ form }} <hr> {% endfor %} <div class="uk-margin"> <button class="uk-button uk-button-primary " type="submit">提交</button> </div> </form> </div> {% endblock %}
|
方式三:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| {% extends 'layout.html' %}
{% block main %} <div class="uk-container"> <form action="{% url 'wechat:index' %}" method="post" novalidate> {{ formset.management_form }} {% csrf_token %} {% for form in formset %}
{% for field in form %} <div class="uk-margin"> <label class="uk-form-label" for="form-horizontal-text">{{ field.label }}</label> <div class="uk-form-controls"> {{ field }} </div> {% if field.help_text %} <p class="help">{{ field.help_text|safe }}</p> {% endif %} {{ field.errors }} </div> {% endfor %} <hr> {% endfor %} <div class="uk-margin"> <button class="uk-button uk-button-primary " type="submit">提交</button> </div> </form> </div> {% endblock %}
|
提交后的渲染结果:
![img]()
Formset
也可以直接由模型model
创建,这时你需要使用modelformset_factory
。你可以指定需要显示的字段和表单数量。
1 2 3 4 5 6 7 8 9
| from django import forms from .models import Student
StudentFormSet = forms.modelformset_factory( model=Student, fields=('name', 'age'), extra=3, max_num=2 )
|
当然上面方法并不推荐,因为对单个表单添加验证方法非常不方便。我更喜欢的方式先创建自定义的ModelForm
,添加单个表单验证,然后再利用modelformset_factory
创建formset
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from django import forms from .models import Student
class StudentForm(forms.ModelForm): class Meta: model = Student fields = ('name',) widgets = { 'name':forms.TextInput(attrs={ 'class':'你好' }) } error_messages = { 'name':{ 'max_length':'太长了' } }
StudentFormSet = forms.modelformset_factory( model=Student, form = StudentForm, extra=3, max_num=2 )
|
试想我们有如下province
模型,province
与city
是单对多的关系。一般的formset
只允许我们一次性提交多个province
或多个city
。但如果我们希望同一个页面上添加一个省份(province
)和多个城市(city
),这时我们就需要用使用inlineformset
了。
模型models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Province(models.Model): id = models.AutoField(primary_key = True) name = models.CharField(max_length=10, verbose_name='省份') desc = models.CharField(max_length=255, verbose_name='介绍') def __str__(self): return self.name class Meta: verbose_name = '省份' verbose_name_plural = verbose_name
class City(models.Model): id = models.AutoField(primary_key = True) name = models.CharField(max_length=20, verbose_name='城市') desc = models.CharField(max_length=255, verbose_name='介绍') province = models.ForeignKey(to=Province, verbose_name='省份', on_delete=models.CASCADE) def __str__(self): return self.name class Meta: verbose_name = '城市' verbose_name_plural = verbose_name
|
利用inlineformset_factory
创建formset
的方法如下所示。该方法的第一个参数和第二个参数都是模型,其中第一个参数必需是ForeignKey
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from django import forms from .models import Province from .models import City
class ProvinceForm(forms.ModelForm): class Meta: model = Province fields = ('name', 'desc') widgets = { 'name':forms.TextInput(attrs={ 'class':'你好' }) } error_messages = { 'name':{ 'max_length':'太长了' } }
CityFormSet = forms.inlineformset_factory( parent_model=Province, model=City, fields=('name', ), extra=3, max_num=2, can_delete=False )
|
视图views.py
views.py
中使用formset
创建和更新province
的代码如下。在对IngredientFormSet
进行实例化的时候,必需指定province的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| from django.shortcuts import render, HttpResponse
from .models import Province from .models import City
from .forms import ProvinceForm from .forms import CityFormSet
def index(request): form = ProvinceForm() city_formset = CityFormSet() if request.method == 'POST': form = ProvinceForm(request.POST) if form.is_valid(): province = form.save() city_formset = CityFormSet(request.POST, instance = province) if city_formset.is_valid(): city_formset.save() return render(request, 'index.html', {'form':form, 'city_formset':city_formset}) return render(request, 'index.html', {'form':form, 'city_formset':city_formset})
def update(request): province_obj = Province.objects.get(pk=3) form = ProvinceForm(instance=province_obj) city_formset = CityFormSet(instance=province_obj) if request.method == 'POST': form = ProvinceForm(request.POST, instance=province_obj) if form.is_valid(): province = form.save() city_formset = CityFormSet(request.POST, instance = province) if city_formset.is_valid(): city_formset.save() return render(request, 'index.html', {'form':form, 'city_formset':city_formset}) return render(request, 'index.html', {'form':form, 'city_formset':city_formset})
|
模板templates
action
添加和更新请修改url
路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| {% extends 'layout.html' %}
{% block main %} <div class="uk-container"> <form action="{% url 'wechat:index' %}" method="post" novalidate> {% csrf_token %} {{ form.as_p }} <hr> <legend>添加城市</legend> {{ city_formset.management_form }} {{ city_formset.non_form_errors }} {% for city_form in city_formset %} {{ city_form }} <hr> {% endfor %} <div class="uk-margin"> <button class="uk-button uk-button-primary " type="submit">提交</button> </div> </form> </div> {% endblock %}
|
渲染效果:
![img]()