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. QT5笔记: 20. QStringListModel的使用

    主要为 :添加.插入.修改.删除.清空等操作 例子:本例子中QListView 没有做任何处理,只是拖放至ui文件,设置了布局 MainWindow.h #ifndef MAINWINDOW_H #d ...

  2. 论今日,Vue VSCode Snippets 不进行代码提示的问题 或 vetur Request textDocument/documentSymbol failed.

    这他喵的是因为 vetur 这个鬼东西升级了,然后和项目中某些包不匹配了, 降级就好了, 法克尤啊法克尤,我整了一天,大概是坏了吧 灵感来源:https://cxymm.net/article/a84 ...

  3. Docker 的基本概念和优势,以及在应用程序开发中的实际应用

    Docker是一个开源的容器化平台,它可以将应用程序及其所有依赖关系打包为一个独立的容器,从而实现应用程序的快速部署.可移植性和可扩展性. Docker的基本概念包括以下几个方面: 镜像(Image) ...

  4. 2025 年最值得尝试的几款 DevOps 平台工具推荐

    随着软件开发和运维的深度融合,DevOps 平台已成为现代企业加速数字化转型的核心引擎.在 2025 年,面对快速迭代的市场需求与复杂的技术架构,选择一款适配性强.功能完备的 DevOps 平台,不仅 ...

  5. LLM生成代码后,如何一键合并到源代码中(FastApply技术研究)

    背景 在大语言模型越来越火的今天,越来越多的应用场景开始使用大语言模型来解决实际问题.而辅助编程可以算是大语言模型应用得最成功的场景之一了.早先的时候,更多使用的还是代码补全的能力,但是现在,各家产品 ...

  6. 三分钟掌握音视频处理 | 在 Rust 中优雅地使用 FFmpeg

    前言 音视频处理看似高深莫测,但在开发中,我们或多或少都会遇到相关需求,比如视频格式转换.剪辑.添加水印.音频提取等. FFmpeg 作为行业标准,几乎无所不能,很多流行的软件(如 VLC.YouTu ...

  7. pytorch报错 No module named 'nn'

    问题描述 pytorch 报错 No module named 'nn' 如果你 import torch 没问题,而 import torch.nn时出现问题,你可能命名使用了一个名字为 torch ...

  8. 【Linux】shell 脚本 (.sh) 编写及执行

    shell脚本 shell脚本就是一些命令的集合 #!/bin/bash echo "文件开头代表:该文件使用的是bash语法" 一.运行.sh文件 方法一:当前文件执行.sh 文 ...

  9. Open diary(每天更新)

    .col-md-8 img { display: none } .comment img { display: unset } 这是一个open diary,就是公开日记. 为什么标题用英文呢?因为觉 ...

  10. 【WinForm】WinForm 生成单文件程序

    WinForm 生成单文件程序 零.解决 安装 Costura.Fody 安装好这个库后生成的就是单文件了. .Net 3.5 NuGet控制台 NuGet\Install-Package Costu ...