Django Formsets总结
formset
是将多个表单用在同一个页面上的抽象层。
我们有:
from django import forms
class ArticleForm(forms.Form):
title=forms.CharField()
pub_date=forms.DateField()
为允许一次性创建几个articles
,可以创建一个ArticleForm
的formset类ArticleFormSet
:
>>>from django.forms import formset_factory
>>>ArticleFormSet=formset_factory(ArticleForm)
对ArticleFormSet
实例化,然后遍历其实例,就可以像一般的表单全部显示出来了:
>>>formset=ArticleFormSet()
>>>for form in formset::
print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
可以看到只有一个空表单,空表单的数量是由extra
参数控制的,默认地,formset_factory()
定义了一个extra form
。
对formset使用初始值
初始值的使用是formset的一大用法,对FormSet
类的实例使用参数initial
,其值为包含有字典的列表,字典的键就是form
种定义的字段名,字典的数量就是初始化的表单的数量。
>>>import datetime
>>>ArticleFormSet=formset_factory(ArticleForm,extra=2)
>>>formset=ArticleFormSet(initial=[{'title':'Django is now open source','pub_date':datetime.date.today()}])
>>>for form in formset:
print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
可以看到一共显示了3个表单,1个是初始化的,2个空表单。
限制表单的最大数量
formset_factory
的max_num
参数用来限制显示的表单数。
>>>ArticleFormSet=formset_factory(ArticleForm,extra=2,max_num=1)
>>>formset=ArticleFormSet()
>>>for form in formset:
print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
显示的数量其实是由initial
,max_num
,extra
参数共同决定的:
当
initial
和extra
之和(可以理解为应该显示的表单数量),小于或者等于max_num
时,全部显示当
initial
和extra
之和大于max_num
时,保证初始化的都显示,即:- 如果
initial
小于max_num
时,初始化的全部显示,显示max_num
与initial
之差数量的空表单 - 如果
initial
大于max_num
时,初始化的全部显示,没有空表单。
max_num
的值默认为None,即显示1000个表单,在实际应用种,这基本等于没有限制了。默认地,
max_num
仅影响有多少表单显示,而不影响校验。如果validate_max=True
,那么max_num
就影响校验。FormSet校验
formset
的校验与form
的校验基本相同。调用is_valid()
就可以校验formset的所有表单。>>> data={
... 'form-TOTAL_FORMS':'2',
... 'form-INITIAL_FORMS':'0',
... 'form-MAX_NUM_FORMS':'',
... 'form-0-title':'test',
... 'form-0-pub_date':'1904-06-06',
... 'form-1-title':'TEST',
... 'form-1-pub_date':'',
... }
>>> formset=ArticleFormSet(data)
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
>>> ss=ArticleFormSet(data)
>>> ss.errors
[{}, {'pub_date': ['This field is required.']}]
>>> ss.is_valid()
False
可以看到
formset.errors
是对应于formset表单的列表,列表中每个字典的键是出现异常的字段名,需要注意的是,不需要调用is_valid()
,就有errors
属性。与
Form
用法类似,formset
的每个form
可以包含maxlength
等的HTML使用的属性作为浏览器的校验。然而formset
的form
不能包含required属性,因为添加或者删除表单时,校验有可能出错。为了得到formset的校验错误数量,可以用
total_error_count
:>>> len(ss.errors)
2
>>> formset.total_error_count()
1
还可以用
has_changed()
来判断是否初始值被改变了。>>> data={
... 'form-TOTAL_FORMS':'1',
... 'form-INITIAL_FORMS':'0',
... 'form-MAX_NUM_FORMS':'',
... 'form-0-title':'',
... 'form-0-pub_date':''}
>>> k=ArticleFormSet(data)
>>> k.has_changed() #k的初始值是空字符,现在还是空字符
False
>>> ss.has_changed()
True
理解
ManagementForm
form-TOTAl_FORMS
,form-INITIAL_FORMS
,form-MAX_NUM_FORMS
就是ManagementForm
的字段。这个表单就在专门用来管理formset中的所有表单的,如果不提供这个管理数据,就会抛出错误。
它是用来追踪有多少显示的表单。如果通过JavaScript添加了新表单,那么也应该相应的增加
ManagementForm
中表单的数量。management_form
也是可以作为formset
本身的属性,在template中,可以通过{{ my_formset.management_form }}来包含其管理数据。所以,在写template时候,必须包含{{ my_formset.management }} 否则,
myform=formset(request.POST)
中就缺失ManagementForm
的数据,就会抛出异常:total_form_count,initial_form_count
对于
ManagementForm
,有2个方法与之密切相连:total_form_count
,intial_form_count
。>>> ss.initial_form_count()
0
>>> ss.total_form_count()
2
定制formset 校验
formset
有一个与Form
类似的clean
方法,这就是可以定制校验的地方。可以通过重载#forms.py
DRINKS=((None,'Please select a drink type'),(1,'Mocha'),(2,'Espresso'),(3,'Latte'))
SIZES=((None,'Please select a drink size'),('s','Small'),('m','Medium'),('1','Large')) class DrinkForm(forms.Form):
name=forms.ChoiceField(choices=DRINKS,initial=0)
size=forms.ChoiceField(choices=SIZES,initial=0)
amount=forms.ChoiceField(choices=[(None,'Amount of drinks')]+[(i,i) for i in range(1,10)]) from django.forms import BaseFormSet
class BaseDrinkFormSet(BaseFormSet):
def clean(self):
if any(self.errors):
return None
name_size_tuples=[]
for form in self.forms:
name_size=(form.cleaned_data['name'],form.cleaned_data['size'])
if name_size in name_size_tuples:
raise forms.ValidationError('UPs! You have multiple %s %s items in your order'%(dict(SIZES)[name_size[1]],dict(DRINKS)[int(name_size[0])]))
name_size_tuples.append(name_size)以上继承并重载了
BaseFormSet
类,在BaseDrinkFormSet
中,重载了类方法clean()
,在该方法中,若发现有某个表单有异常(即any(self.errors),直接结束,如果在所有表单都是正确的情况下,对所有表单的name
,size
字段进行核查,有这两个字段都相同的,抛出异常。>>> from testapp.forms import *
>>> from django.forms import formset_factory
>>> a=formset_factory(DrinkForm,formset=BaseDrinkFormSet)
>>> data={
... 'form-TOTAL_FORMS':'2',
... 'form-INITIAL_FORMS':'0',
... 'form-MAX_NUM_FORMS':'',
... 'form-0-name':1,
... 'form-0-size':'s',
... 'form-0-amount':1,
... 'form-1-name':1,
... 'form-1-size':'s',
... 'form-1-amount':2,}
>>> formset=a(data)
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['UPs! You have multiple Small Mocha items in your order']
formset的
clean()
在所有的Form.clean()
调用后,被调用(所有可以在formset的clean()
方法中遍历formset,并使用每个form的cleaned_data
,用non_form_errors()
来发现找到的错误。校验formset中的表单数量
validate_max
如果
validate_max=True
传入formset_factory()
,也会校验formset
中的表单数减去标记为删减的表单是否超过了max_num
。>>> from django.forms import formset_factory
>>> from testapp.forms import *
>>> drinkformset=formset_factory(DrinkForm,max_num=1,validate_max=True)
>>> data={
... 'form-TOTAL_FORMS':'2',
... 'form-INITIAL_FORMS':'0',
... 'form-MIN_NUM_FORMS':'',
... 'form-MAX_NUIM_FORMS':'',
... 'form-0-name':2,
... 'form-0-size':'s',
... 'form-0-amount':1,
... 'form-1-name':3,
... 'form-1-size':'m',
... 'form-1-amount':2}
>>> formset=drinkformset(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit 1 or fewer forms.']
validate_min
与
validate_max
类似。>>> drinkformset=formset_factory(DrinkForm,min_num=3,validate_min=True)
>>> data={
... 'form-TOTAL_FORMS':'2',
... 'form-INITIAL_FORMS':'0',
... 'form-MIN_NUM_FORMS':'',
... 'form-MAX_NUIM_FORMS':'',
... 'form-0-name':2,
... 'form-0-size':'s',
... 'form-0-amount':1,
... 'form-1-name':3,
... 'form-1-size':'m',
... 'form-1-amount':2}
>>> formset=drinkformset(data)
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Please submit 3 or more forms.']
>>> form.errors
Traceback (most recent call last):
File "<console>", line 1, in <module>
NameError: name 'form' is not defined
>>> formset.errors
[{}, {}]
>>> drinkformset=formset_factory(DrinkForm,min_num=3,validate_min=True)
>>> data={
... 'form-TOTAL_FORMS':'2',
... 'form-INITIAL_FORMS':'0',
... 'form-MIN_NUM_FORMS':'',
... 'form-MAX_NUIM_FORMS':'',
... 'form-0-name':2,
... 'form-0-size':'s',
... 'form-0-amount':1,
... 'form-1-name':3,
... 'form-1-size':'m',
... 'form-1-amount':2}
>>> formset=drinkformset(data)
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Please submit 3 or more forms.']
>>> formset.errors
[{}, {}]
表单序号(Ordering)和删除(Deletion)
can_order
默认是
False
.>>> articleFormset=formset_factory(ArticleForm,can_order=True)
>>> import datetime
>>> formset=articleFormset(initial=[
... {'title':'Article #1','pub_date':datetime.date(2008,5,10)},
... {'title':'Article #2','pub_date':datetime.date(2008,5,11)}
... ])
>>> for form in formset:
... print(form.as_table())
...
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title"
value="Article #1" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title"
value="Article #2" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title"
id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></td></tr>
可见,自动为每个form添加了
order
字段。can_delete
默认为
False
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(initial=[
... {'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
... {'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
... ])
>>> for form in formset:
... print(form.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></td></tr>
可见,
can_delete=True
为每个form添加了个删除校对框。如果在使用
ModelFormSet
,对于选中的删除的表单,在执行formset.save()
时,该表单的实例将会被删除。但是如果调用formset.save(commit=False)
,将不会删除,需要用formset.deleted_objects
来删除:>>> instances = formset.save(commit=False)
>>> for obj in formset.deleted_objects:
... obj.delete()
在formset里添加额外字段
添加额外自定义的字段,只需要继承
BaseFormSet
类,然后重载其函数add_fields(self.form,index)
即可。class ArticleForm(forms.Form):
title=forms.CharField()
pub_date=forms.DateField() class BaseArticleFormSet(BaseFormSet):
def add_fields(self,form,index):
super().add_fields(form,index)
form.fields['my_field']=forms.CharField()
>>> from django.forms import formset_factory
>>> from testapp.forms import *
>>> articleFormset=formset_factory(ArticleForm,formset=BaseArticleFormSet)
>>> formset=articleFormset()
>>> for form in formset:
... print(form.as_table())
...
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title"
id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field"></td></tr>
可以发现已经添加了自定义字段
my field
.自定义formset的前缀
默认前缀都是
form
,比如:<label for="id_form-0-title">Title:</label>
<input type="text" name="form-0-title" id="id_form-0-title">
>>> formset=formset_factory(ArticleForm)
>>> articleFormset=formset_factory(ArticleForm)
>>> formset=articleFormset(prefix='article')
>>> for form in formset:
... print(form.as_table())
...
<tr><th><label for="id_article-0-title">Title:</label></th><td><input type="text" name="article-0-title" id="id_article-0-title"></td></tr>
<tr><th><label for="id_article-0-pub_date">Pub date:</label></th><td><input type="text" name="article-0-pub_date" id="id_article-0-pub_date"></td></tr>
- 如果
Django Formsets总结的更多相关文章
- Multi-Object-Edit With Django FormSets
I had to write a multi-object edit table the other day for a Django project and as such I dove into ...
- Tutorial : Implementing Django Formsets
A step-by-step tutorial for setting up and testing a standard Django formset. I’ve noticed on #djang ...
- DJANGO MODEL FORMSETS IN DETAIL AND THEIR ADVANCED USAGE
Similar to the regular formsets, Django also provides model formset that makes it easy to work with ...
- django用户登录和注销
django版本:1.4.21. 一.准备工作 1.新建项目和app [root@yl-web-test srv]# django-admin.py startproject lxysite [roo ...
- django formset bug?
碰到了一个郁闷的问题,修改inlineformset时,全部删掉子表,再新增一行时,报错. 背景: 用django配合jq做动态表格,实现用js动态添加/删除行,并通过inlineformset更新到 ...
- django 添加动态表格的方法
传统方法(基于方法的视图):http://stellarchariot.com/blog/2011/02/dynamically-add-form-to-formset-using-javascrip ...
- django admin site (三)
1.自定义模板设置: ModelAdmin. add_form_template Path to a custom template, used by add_view(). ModelAdmin. ...
- Django admin site(二)ModelAdmin methods
ModelAdmin methods save_model(request, obj, form, change) 此方法为admin界面用户保存model实例时的行为.request为HttpReq ...
- [Django实战] 第4篇 - 用户认证(用户登录)
今天来实现用户登录模块 首先,我们创建一个表单(forms.py): from django import forms from django.contrib.auth.models import U ...
- 《Django By Example》第十章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:翻译本章过程中几次想放弃,但是既然 ...
随机推荐
- MybatisPlus - [01] 概述
MybatisPlus可以节省我们大量工作时间,所有的CURD代码它都可以自动化完成! 一.是什么 MyBatis-Plus(简称MP)是一个基于MyBatis的增强工具,其设计目的是在不改变My ...
- MD5 - windows也可以查询某个文件的MD5码
命令格式 certutil -hashfile 文件名称 md5 示例 Microsoft Windows [版本 10.0.22621.1702] (c) Microsoft Corporation ...
- CMD批处理脚本+VBScript脚本+Potplayer 实现文件夹内所有视频的截图任务(指定时间点)
实现自动化视频截图,一般会直接借视频编解码如FFmpeg,动用相关函数来实现,直接从解码源头设计程序.然而我没有接触过FFmpeg,借助cmd批处理,以及vbs,还有现成的播放器potplayer,一 ...
- 分布式锁—2.Redisson的可重入锁
大纲 1.Redisson可重入锁RedissonLock概述 2.可重入锁源码之创建RedissonClient实例 3.可重入锁源码之lua脚本加锁逻辑 4.可重入锁源码之WatchDog维持加锁 ...
- C# 域套接字通讯类
public class UdsClient { public Socket _socket { get; set; } public UnixDomainSocketEndPoint endPoin ...
- C语言中标准输出的缓冲机制
什么是缓冲区 缓存区是内存空间的一部分,再内存中,内存空间会预留一定的存储空间,这些存储空间是用来缓冲输入和输出的数据,预留的这部分空间就叫做缓冲区. 其中缓冲区还会根据对应的是输入设备还是输出设备分 ...
- Manus的开源复刻OpenManus初探
OpenManus介绍 Manus需要邀请码才能体验,目前大部分人都体验不到. 有几个大佬花3个小时就复现了一个简单的原型OpenManus,让我们体验体验吧!! 截至目前,该项目已经获得了25.9k ...
- 【抓包】Fidder Script自动修改包
Fiddler Script的本质是用JScript.NET编写的一个脚本文件CustomRules.js 但是它的语法很像C#但又有些不一样,比如不能使用@符号 通过修改CustomRules.js ...
- 正则表达式--java进阶day06
1.正则表达式 2.正则表达式的规则.使用 3.字符类讲解 如图,单独一个a满足正则表达式的规则,所以返回true 当删去[]后,正则表达式中的规则就会变为必须是abc,否则不满足条件,即使有一个a ...
- 【C语言】Linux 飞翔的小鸟
[C语言]Linux 飞翔的小鸟 零.环境部署 安装Ncurses库 sudo apt-get install libncurses5-dev 壹.编写代码 代码如下: bird.c #include ...