Formset(表单集)是多个表单的集合。Formset在Web开发中应用很普遍,它可以让用户在同一个页面上提交多张表单,一键添加多个数据,比如一个页面上添加多个用户信息。今天小编我就介绍下Django Formset的基础知识,Formset的分类以及如何使用Formset。

为什么要使用Django Formset

我们先来下看下Django中不使用Formset情况下是如何在同一页面上一键提交2张或多张表单的。我们在模板中给每个表单取不同的名字,如form1和form2(如下面代码所示)。注: form1和form2分别对应forms.py里的Form1()和Form2()。

  1. <form >
  2. {{ form1.as_p }}
  3. {{ form2.as_p }}
  4. </form>

用户点击提交后,我们就可以在视图里了对用户提交的数据分别处理。

  1. if request.method == 'POST':
  2. form1 = Form1( request.POST,prefix="form1")
  3. form2 = Form2( request.POST,prefix="form2")
  4. if form1.is_valid() or form2.is_valid():
  5. pass
  6. else:
  7. form1 = Form1(prefix="form1")
  8. form2 = Form2(prefix="form2")

这段代码看似并不复杂,然而当表单数量很多或不确定时,这个代码会非常冗长。我们希望能控制表单的数量,这是我们就可以用Formset了。

Formset的分类

Django针对不同的formset提供了3种方法: formset_factory, modelformset_factory和inlineformset_factory。我们接下来分别看下如何使用它们。

如何使用formset_factory

对于继承forms.Form的自定义表单,我们可以使用formset_factory。我们可以通过设置extra和max_num属性来确定我们想要展示的表单数量。注意: max_num优先级高于extra。比如下例中,我们想要显示3个空表单(extra=3),但最后只会显示2个空表单,因为max_num=2。

  1. from django import forms
  2. class BookForm(forms.Form):
  3. name = forms.CharField(max_length=100)
  4. title = forms.CharField()
  5. pub_date = forms.DateField(required=False)
  6. # forms.py - build a formset of books
  7. from django.forms import formset_factory
  8. from .forms import BookForm
  9. # extra: 想要显示空表单的数量
  10. # max_num: 表单显示最大数量,可选,默认1000
  11. BookFormSet = formset_factory(BookForm, extra=3, max_num=2)

在视图文件views.py里,我们可以像使用form一样使用formset。

  1. # views.py - formsets example.
  2. from .forms import BookFormSet
  3. from django.shortcuts import render
  4. def manage_books(request):
  5. if request.method == 'POST':
  6. formset = BookFormSet(request.POST, request.FILES)
  7. if formset.is_valid():
  8. # do something with the formset.cleaned_data
  9. pass
  10. else:
  11. formset = BookFormSet()
  12. return render(request, 'manage_books.html', {'formset': formset})

模板里可以这样使用formset。

  1. <form action=”.” method=”POST”>
  2. {{ formset }}
  3. </form>

也可以这样使用。

  1. <form method="post">
  2. {{ formset.management_form }}
  3. <table>
  4. {% for form in formset %}
  5. {{ form }}
  6. {% endfor %}
  7. </table>
  8. </form>

如何使用modelformset_factory

Formset也可以直接由模型model创建,这时你需要使用modelformset_factory。你可以指定需要显示的字段和表单数量。

  1. from django.forms import modelformset_factory
  2. from myapp.models import Author
  3. AuthorFormSet = modelformset_factory(
  4. Author, fields=('name', 'title'), extra = 3)

当然上面方法我并不推荐,因为对单个表单添加验证方法非常不方便。我更喜欢的方式先创建自定义的ModelForm,添加单个表单验证,然后再利用modelformset_factory创建formset。

  1. class AuthorForm(forms.ModelForm):
  2. class Meta:
  3. model = Author
  4. fields = ('name', 'title')
  5. def clean_name(self):
  6. # custom validation for the name field
  7. ...

由ModelForm创建formset:

  1. AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

在模板和视图里使用formset的方法与前面的例子是一样的。

如何使用inlineformset_factory

试想我们有如下recipe模型,Recipe与Ingredient是单对多的关系。一般的formset只允许我们一次性提交多个Recipe或多个Ingredient。但如果我们希望同一个页面上添加一个菜谱(Recipe)和多个原料(Ingredient),这时我们就需要用使用inlineformset了。

  1. from django.db import models
  2. class Recipe(models.Model):
  3. title = models.CharField(max_length=255)
  4. description = models.TextField()
  5. class Ingredient(models.Model):
  6. recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name='ingredient')
  7. name = models.CharField(max_length=255)

利用inlineformset_factory创建formset的方法如下所示。该方法的第一个参数和第二个参数都是模型,其中第一个参数必需是ForeignKey。

  1. # forms.py
  2. from django.forms import ModelForm
  3. from django.forms import inlineformset_factory
  4. from .models import Recipe, Ingredient, Instruction
  5. class RecipeForm(ModelForm):
  6. class Meta:
  7. model = Recipe
  8. fields = ("title", "description",)
  9. IngredientFormSet = inlineformset_factory(Recipe, Ingredient, fields=('name',),
  10. extra=3, can_delete=False, max_num=5)

views.py中使用formset创建和更新recipe的代码如下。在对IngredientFormSet进行实例化的时候,必需指定recipe的实例。

  1. def recipe_update(request, pk):
  2. recipe = get_object_or_404(Recipe, pk=pk)
  3. if request.method == "POST":
  4. form = RecipeForm(request.POST, instance=recipe)
  5. if form.is_valid():
  6. recipe = form.save()
  7. ingredient_formset = IngredientFormSet(request.POST, instance=recipe)
  8. if ingredient_formset.is_valid():
  9. ingredient_formset.save()
  10. return redirect('/recipe/')
  11. else:
  12. form = RecipeForm(instance=recipe)
  13. ingredient_formset = IngredientFormSet(instance=recipe)
  14. return render(request, 'recipe/recipe_update.html', {'form': form,
  15. 'ingredient_formset': ingredient_formset,
  16. })
  17. def recipe_add(request):
  18. if request.method == "POST":
  19. form = RecipeForm(request.POST)
  20. if form.is_valid():
  21. recipe = form.save()
  22. ingredient_formset = IngredientFormSet(request.POST, instance=recipe)
  23. if ingredient_formset.is_valid():
  24. ingredient_formset.save()
  25. return redirect('/recipe/')
  26. else:
  27. form = RecipeForm()
  28. ingredient_formset = IngredientFormSet()
  29. return render(request, 'recipe/recipe_add.html', {'form': form,
  30. 'ingredient_formset': ingredient_formset,
  31. })

模板recipe/recipe_add.html代码如下。

  1. <h1>Add Recipe</h1>
  2. <form action="." method="post">
  3. {% csrf_token %}
  4. {{ form.as_p }}
  5. <fieldset>
  6. <legend>Recipe Ingredient</legend>
  7. {{ ingredient_formset.management_form }}
  8. {{ ingredient_formset.non_form_errors }}
  9. {% for form in ingredient_formset %}
  10. {{ form.name.errors }}
  11. {{ form.name.label_tag }}
  12. {{ form.name }}
  13. </div>
  14. {% endfor %}
  15. </fieldset>
  16. <input type="submit" value="Add recipe" class="submit" />
  17. </form>

最后的效果如下图所示:

整个formset的验证

formset由多个表单组成,单个表单的验证可以通过自定义的clean方法来完成,然而有时我们需要对整个formset的数据进行验证。一个常见例子就是去重。

比如下面例子中用户一次性提交多篇文章标题后,我们需要检查title是否已重复。我们先定义一个BaseFormSet,然后使用formset=BaseArticleFormSet添加formset的验证。

  1. from django.forms import BaseFormSet
  2. from django.forms import formset_factory
  3. from myapp.forms import ArticleForm
  4. class BaseArticleFormSet(BaseFormSet):
  5. def clean(self):
  6. """Checks that no two articles have the same title."""
  7. if any(self.errors):
  8. return
  9. titles = []
  10. for form in self.forms:
  11. title = form.cleaned_data['title']
  12. if title in titles:
  13. raise forms.ValidationError("Articles in a set must have distinct titles.")
  14. titles.append(title)
  15. ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)

给Formset添加额外字段

在BaseFormSet里我们不仅可以添加formset的验证,而且可以添加额外的字段,如下所示:

  1. from django.forms import BaseFormSet
  2. from django.forms import formset_factory
  3. from myapp.forms import ArticleForm
  4. class BaseArticleFormSet(BaseFormSet):
  5. def add_fields(self, form, index):
  6. super().add_fields(form, index)
  7. form.fields["my_field"] = forms.CharField()
  8. ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)

小结

Formset真的非常有用,属于Django必备的基础知识之一。使用的时候先定义单个的form,然后利用factory生成formset。你需要根据不同应用场景选择不同的formset,并了解如何进行formset的验证。希望本文对你有所帮助。原创不易,欢迎点赞转发。

Django表单集合Formset的高级用法的更多相关文章

  1. python 全栈开发,Day111(客户管理之 编辑权限(二),Django表单集合Formset,ORM之limit_choices_to,构造家族结构)

    昨日内容回顾 1. 权限系统的流程? 2. 权限的表有几个? 3. 技术点 中间件 session orm - 去重 - 去空 inclusion_tag filter 有序字典 settings配置 ...

  2. Django表单集合----Formset

    概述:Formset(表单集)是多个表单的集合.Formset在Web开发中应用很普遍,它可以让用户在同一个页面上提交多张表单,一键添加多个数据,比如一个页面上添加多个用户信息,下面将会详细讲述如何使 ...

  3. python3之Django表单(一)

    1.HTML中的表单 在HTML种,表单是在<form>...</form>种的元素,它允许用户输入文本,选择选项,操作对象等,然后发送这些数据到服务器 表单元素允许用户在表单 ...

  4. 【Python】django表单与提交

    参考:http://djangobook.py3k.cn/2.0/chapter07/ 本文的内容应属于django的表单模块,没有涉及到的后端request对象的处理方法可以单独深入学习表单. UR ...

  5. 第四章:Django表单 - 1:使用表单

    假设你想从表单接收用户名数据,一般情况下,你需要在HTML中手动编写一个如下的表单元素: <form action="/your-name/" method="po ...

  6. 在一般处理程序中,把Form Post过来的表单集合转换成对象 ,仿 MVC post,反射原理

    using System; using System.Collections.Generic; using System.Collections.Specialized; using System.L ...

  7. django表单的api

    django表单的api,参考文档:https://yiyibooks.cn/xx/Django_1.11.6/ref/forms/api.html 绑定与未绑定形式: Form要么是绑定的,要么是未 ...

  8. Django表单API详解

    声明:以下的Form.表单等术语都指的的广义的Django表单. Form要么是绑定了数据的,要么是未绑定数据的. 如果是绑定的,那么它能够验证数据,并渲染表单及其数据,然后生成HTML表单.如果未绑 ...

  9. 9:django 表单

    django自带表单系统,这个表单系统不仅可以定义属性名,还可以自己定义验证,更有自己自带的错误提示系统 这节我们仅仅粗略的来看一下django表单系统的入门运用(具体的实在太多东西,主要是我觉得有很 ...

随机推荐

  1. oracle表的列合并(group by)和行合并(union all)

    group by select a.dn,t.dn dnt,a.BEGIN_TIME,a.R032_001,t.R032_001,a.R032_002,a.R032_003,a.R032_004, a ...

  2. HDU - 6583 Typewriter (后缀自动机+dp)

    题目链接 题意:你要打印一段字符串,往尾部添加一个字符需要花费p元,复制一段字符到尾部需要花费q元,求打印完全部字符的最小花费. 一开始想的贪心,后来发现忘了考虑p<q的情况了,还纳闷怎么不对. ...

  3. Python语法汇总

    如果你之前学过任何一门编程语言,因为每种语言的基础语法要做的事情其实基本是相同的,只是表示方式或某些地方稍稍不同,因此在学Python的时候将它与其它你已经掌握的编程语言对比着学,这样学起来更快,效果 ...

  4. C# 扩展方法——去重(Distinct)

    其他扩展方法详见:https://www.cnblogs.com/zhuanjiao/p/12060937.html IEnumerable的Distinct扩展方法,当集合元素为对象时,可用于元素对 ...

  5. react -搭建服务

    import 'whatwg-fetch'; import 'es6-promise'; require('es6-promise').polyfill(); import * as common f ...

  6. [pwnable.kr]Dragon

    0x00: dragon 是一个UAF漏洞的利用. UseAfterFree 是堆的漏洞利用的一种 简单介绍 https://www.owasp.org/index.php/Using_freed_m ...

  7. CF1101D GCD Counting 点分治+质因数分解

    题意:求最长的树上路径点值的 $gcd$ 不为 $1$ 的长度. 由于只要求 $gcd$ 不为一,所以只要 $gcd$ 是一个大于等于 $2$ 的质数的倍数就可以了. 而我们发现 $2\times 1 ...

  8. 【BZOJ4552】排序(线段树,二分)

    题意:给定一个n个数的排列,有m次操作:op,l,r op=0时表示将位置[L,R]升序排序 op=1时表示将位置[L,R]降序排序 最后询问第q个位置上的数字 n,m,q<=1e5 思路:Fr ...

  9. 试用saucelabs进行浏览器兼容性测试

    Hi,all 跟大家分享下saucelabs,一个云测试平台,支持PC和手机(自带的)浏览器的兼容性测试,并且支持selenium/appium的自动化测试,不过是收费的,价格还挺贵,但是人工的测试是 ...

  10. RMQ的ST算法

    ·RMQ的ST算法    状态设计:        F[i, j]表示从第i个数起连续2^j个数中的最大值    状态转移方程(二进制思想):        F[i, j]=max(F[i,j-1], ...