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_factorymax_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参数共同决定的:

  • initialextra之和(可以理解为应该显示的表单数量),小于或者等于max_num时,全部显示

  • initialextra之和大于max_num时,保证初始化的都显示,即:

    • 如果initial小于max_num时,初始化的全部显示,显示max_numinitial之差数量的空表单
    • 如果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使用的属性作为浏览器的校验。然而formsetform不能包含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_FORMSform-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总结的更多相关文章

  1. 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 ...

  2. Tutorial : Implementing Django Formsets

    A step-by-step tutorial for setting up and testing a standard Django formset. I’ve noticed on #djang ...

  3. 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 ...

  4. django用户登录和注销

    django版本:1.4.21. 一.准备工作 1.新建项目和app [root@yl-web-test srv]# django-admin.py startproject lxysite [roo ...

  5. django formset bug?

    碰到了一个郁闷的问题,修改inlineformset时,全部删掉子表,再新增一行时,报错. 背景: 用django配合jq做动态表格,实现用js动态添加/删除行,并通过inlineformset更新到 ...

  6. django 添加动态表格的方法

    传统方法(基于方法的视图):http://stellarchariot.com/blog/2011/02/dynamically-add-form-to-formset-using-javascrip ...

  7. django admin site (三)

    1.自定义模板设置: ModelAdmin. add_form_template Path to a custom template, used by add_view(). ModelAdmin. ...

  8. Django admin site(二)ModelAdmin methods

    ModelAdmin methods save_model(request, obj, form, change) 此方法为admin界面用户保存model实例时的行为.request为HttpReq ...

  9. [Django实战] 第4篇 - 用户认证(用户登录)

    今天来实现用户登录模块 首先,我们创建一个表单(forms.py): from django import forms from django.contrib.auth.models import U ...

  10. 《Django By Example》第十章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:翻译本章过程中几次想放弃,但是既然 ...

随机推荐

  1. Flink - [07] 容错机制

    题记部分 一.一致性检查点   Flink故障恢复机制的核心,就是应用状态的一致性检查点.有状态流应用的一致性检查点,其实就是所有任务的状态,在某个时间点的一份拷贝(一份快照):这个时间点,应该是所有 ...

  2. linux系统升级/更新OpenSSL版本操作流程记录

    问题描述:有时OpenSSL版本过老升级,或者需要更新OpenSSL版本 1.登录linux系统后输入openssl version 查看现在使用的版本 我的输入后版本信息为:OpenSSL 1.1. ...

  3. Windows 提权-PrintNightmare

    本文通过 Google 翻译 PrintNightmare – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充 ...

  4. 百万架构师第四十七课:并发编程的原理(二)|JavaGuide

    原文链接 JavaGuide <并发编程的艺术> 并发编程的实现原理 目标 上节课内容回顾 synchronized 原理分析 wait 和 notify Lock 同步锁 回顾 原子性 ...

  5. Postman 提示{"msg":"JSON parse error: Unexpected character (' ' (code 160))

    报错提示{"msg":"JSON parse error: Unexpected character (' ' (code 160)) 解决方案: json 格式选择be ...

  6. vue2 配置 mock.js 模拟后端数据

    安装 mockj 首先确保你有一个 vue 2 项目,如果没有,可以用 Vue CLI 创建一个: vue create vue-mock-demo 开始安装 Mock.js npm install ...

  7. goland无法识别包

    新建 Go 项目时,一定要通过 "File -> New -> Project..." 方式建立,千万不要通过 "File -> Open", ...

  8. 一款HTML转Markdown格式的工具

    Markdown格式不仅对写博客的人非常友好和方便,对AI也是如此. 目前AI大语言模型的输出基本都是Markdown格式,这就意味着AI是能充分理解Markdown格式的,这一点非常重要. Mark ...

  9. PIO----创建Excel表格复杂使用

    导出 @RequestMapping( name = "下载模板附件实现Model", value = {"/uploadFileModel"}, method ...

  10. [WPF]XAML中使用IMultiValueConverter实现Command的多参数传参

    问题 如何对ICommand传入多个参数? 背景 最近在做一个WPF的开发,有多个相近的功能写了不同的Command,因为要对应不同的对象.因为是CtrlCV,显得代码有点冗赘不够优雅,但是IComm ...