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. MybatisPlus - [01] 概述

    MybatisPlus可以节省我们大量工作时间,所有的CURD代码它都可以自动化完成! 一.是什么   MyBatis-Plus(简称MP)是一个基于MyBatis的增强工具,其设计目的是在不改变My ...

  2. MD5 - windows也可以查询某个文件的MD5码

    命令格式 certutil -hashfile 文件名称 md5 示例 Microsoft Windows [版本 10.0.22621.1702] (c) Microsoft Corporation ...

  3. CMD批处理脚本+VBScript脚本+Potplayer 实现文件夹内所有视频的截图任务(指定时间点)

    实现自动化视频截图,一般会直接借视频编解码如FFmpeg,动用相关函数来实现,直接从解码源头设计程序.然而我没有接触过FFmpeg,借助cmd批处理,以及vbs,还有现成的播放器potplayer,一 ...

  4. 分布式锁—2.Redisson的可重入锁

    大纲 1.Redisson可重入锁RedissonLock概述 2.可重入锁源码之创建RedissonClient实例 3.可重入锁源码之lua脚本加锁逻辑 4.可重入锁源码之WatchDog维持加锁 ...

  5. C# 域套接字通讯类

    public class UdsClient { public Socket _socket { get; set; } public UnixDomainSocketEndPoint endPoin ...

  6. C语言中标准输出的缓冲机制

    什么是缓冲区 缓存区是内存空间的一部分,再内存中,内存空间会预留一定的存储空间,这些存储空间是用来缓冲输入和输出的数据,预留的这部分空间就叫做缓冲区. 其中缓冲区还会根据对应的是输入设备还是输出设备分 ...

  7. Manus的开源复刻OpenManus初探

    OpenManus介绍 Manus需要邀请码才能体验,目前大部分人都体验不到. 有几个大佬花3个小时就复现了一个简单的原型OpenManus,让我们体验体验吧!! 截至目前,该项目已经获得了25.9k ...

  8. 【抓包】Fidder Script自动修改包

    Fiddler Script的本质是用JScript.NET编写的一个脚本文件CustomRules.js 但是它的语法很像C#但又有些不一样,比如不能使用@符号 通过修改CustomRules.js ...

  9. 正则表达式--java进阶day06

    1.正则表达式 2.正则表达式的规则.使用 3.字符类讲解 如图,单独一个a满足正则表达式的规则,所以返回true 当删去[]后,正则表达式中的规则就会变为必须是abc,否则不满足条件,即使有一个a ...

  10. 【C语言】Linux 飞翔的小鸟

    [C语言]Linux 飞翔的小鸟 零.环境部署 安装Ncurses库 sudo apt-get install libncurses5-dev 壹.编写代码 代码如下: bird.c #include ...