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. 八米云-各种小主机x86系统-小白保姆式超详细刷机教程

    疑难解答加微信机器人,给它发:进群,会拉你进入八米交流群 机器人微信号:bamibot 简洁版教程访问:https://bbs.8miyun.cn 准备工作 说明: 1.小节点X86 单线500M以下 ...

  2. Week09_day05(Java API操作Hbase)

    package com.wyh.HbaseAPI; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbas ...

  3. Linux用户登录失败锁定策略

    1.账户锁定策略介绍 在Linux系统中,为了提高系统安全性,防止暴力破解攻击,我们可以通过配置PAM(Pluggable Authentication Modules)模块来限制登录失败次数并锁定用 ...

  4. JUC并发—15.红黑树详解

    目录 1.红黑树的定义性质和推论 2.红黑树的旋转操作 3.红黑树之添加结点的方法 4.红黑树之删除结点的方法一 5.红黑树之删除结点的方法二 1.红黑树的定义性质和推论 (1)红黑树的定义和性质 ( ...

  5. DeepSeek 不太稳定?那就搭建自己的 DeepSeek 服务

    概述 DeepSeek-R1 发布 DeepSeek 在 2025 年给我们送来一份惊喜,1 月 20 号正式发布第一代推理大模型 DeepSeek-R1.这个模型在数学推理.代码生成和复杂问题解决等 ...

  6. 百万架构师第四十六课:并发编程的原理(一)|JavaGuide

    百万架构师系列文章阅读体验感更佳 原文链接:https://javaguide.net 并发编程的原理 课程目标 JMM 内存模型 JMM 如何解决原子性.可见性.有序性的问题 Synchronize ...

  7. The selected directory is not a valid home for Go SDK

    前言 The selected directory is not a valid home for Go SDK 出现这个错误的原因是 idea 的 Go-plugin 插件,和 Go 的sdk版本不 ...

  8. HTTP/1.1、HTTP/2、HTTP/3

    HTTP/1.1 相比 HTTP/1.0 性能上的改进: 使用长连接的方式改善了 HTTP/1.0 短连接造成的性能开销. 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来, ...

  9. JAVA调用Python脚本执行

    SpringBoot-web环境 <dependency> <groupId>org.springframework.boot</groupId> <arti ...

  10. Oracle体系结构和用户管理

    本篇博客将对Oracle的体系结构.存储结构.内存结构和进程结构进行初步介绍,从而从宏观上把握它的物理组成.文件组成和各种进程,对于进一步的了解可以起到很好地作用 一.Oralce体系结构 1.概述 ...