Django表单集合----Formset
概述:Formset(表单集)是多个表单的集合。Formset在Web开发中应用很普遍,它可以让用户在同一个页面上提交多张表单,一键添加多个数据,比如一个页面上添加多个用户信息,下面将会详细讲述如何使用Formset。
一、Formset的分类
Django针对不同的formset提供了三种方法:formset_factory,modelformset_factory和inlineformset_factory。
二、如何使用formset_factory
对于继承forms.Form的自定义表单,我们可以使用formset_factory。
#models.py from django import forms class BookForm(forms.Form): name = forms.CharField(max_length=100) title = forms.CharField() pub_date = forms.DateField(required=False) # forms.py - build a formset of books from django.forms import formset_factory from myapp.models import BookForm # extra: 想要显示空表单的数量 # max_num: 表单显示最大数量,可选,默认1000 BookFormSet = formset_factory(BookForm, extra=3, max_num=2)
在视图文件views.py里,我们可以像使用form一样使用formset
# views.py - formsets example. from .forms import BookFormSet from django.shortcuts import render def manage_books(request): if request.method == 'POST': formset = BookFormSet(request.POST) if formset.is_valid(): # do something with the formset.cleaned_data pass else: formset = BookFormSet() #如果想传入初始数据可设置initial = [{'name':'python','pub_date':'北京出版社'}] return render(request, 'manage_books.html', {'formset': formset})
注意:如果使用了 initial
来显示formset,那么您需要在处理formset提交时传入相同的 initial
,以便formset检测用户更改了哪些表单。例如,您可能有这样的: BookFormSet(request.POST, initial=[...])
。
模板里可以这样使用formset:
<form action=”.” method=”POST”> {{ formset }} </form>
也可以这样使用:
<form method="post"> {{ formset.management_form }} #一定要加这行代码 <table> {% for form in formset %} {{ form }} {% endfor %} </table> </form>
formset_factory()参数解释:
1、如果 max_num
的值大于初始数据现有数量,那空白表单可显示的数量取决于 extra
的数量,只要总表单数不超过 max_num
。例如, extra=2
, max_num=2
并且formset有一个 initial
初始化项,则会显示一张初始化表单和一张空白表单。
2、如果初始数据项的数量超过 max_num
,那么 max_num
的值会被无视,所有初始数据表单都会显示,并且也不会有额外的表单显示。例如,假设 extra=3
, max_num=1
并且formset有两个初始化项,那么只会显示两张有初始化数据的表单。
3、max_num
的值 None
(默认值),它限制最多显示(1000)张表单,其实这相当于没有限制。
三、如何使用modelformset_factory
Formset也可以直接由模型model创建,这时你需要使用modelformset_factory。你可以指定需要显示的字段和表单数量。
class StudentStudyRecordModelForm(forms.ModelForm): class Meta: model=StudentStudyRecord fields=["score","homework_note"]
由ModelForm创建formset:
model_formset_cls=modelformset_factory(model=StudentStudyRecord,form=StudentStudyRecordModelForm,extra=0)
views.py
class RecordScoreView(View): def get(self, request,class_study_record_id): model_formset_cls=modelformset_factory(model=StudentStudyRecord,form=StudentStudyRecordModelForm,extra=0) queryset = StudentStudyRecord.objects.filter(classstudyrecord=class_study_record_id) formset = model_formset_cls(queryset=queryset) return render(request,"student/record_score.html",locals()) def post(self, request,class_study_record_id): model_formset_cls = modelformset_factory(model=StudentStudyRecord, form=StudentStudyRecordModelForm, extra=0) formset=model_formset_cls(request.POST) if formset.is_valid(): formset.save() return redirect(request.path)
模板:
<form method="post" action=""> {% csrf_token %} {{ formset.management_form }} <table class="table table-bordered"> <thead> <tr> <th>姓名</th> <th>考勤</th> <th>作业成绩</th> <th>作业评语</th> </tr> </thead> <tbody> {% for form in formset %} <tr> {{ form.id }} <td>{{ form.instance.student }}</td> <td>{{ form.instance.get_record_display }} </td> <td>{{ form.score }} </td> <td>{{ form.homework_note }}</td> </tr> {% endfor %} </tbody> </table> <input type="submit" value="保存"> </form>
四、如何使用inlineformset_factory
试想我们有如下recipe(菜谱)模型,Recipe(菜谱)与Ingredient(原料)是一对多的关系。一般的formset只允许我们一次性提交多个Recipe或多个Ingredient。但如果我们希望同一个页面上添加一个菜谱(Recipe)和多个原料(Ingredient),这时我们就需要用使用inlineformset了。
from django.db import models class Recipe(models.Model): title = models.CharField(max_length=255) description = models.TextField() class Ingredient(models.Model): recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name='ingredient') name = models.CharField(max_length=255)
利用inlineformset_factory创建formset的方法如下所示。该方法的第一个参数和第二个参数都是模型,其中第一个参数必需是ForeignKey。
# forms.py from django.forms import ModelForm from django.forms import inlineformset_factory from .models import Recipe, Ingredient, Instruction class RecipeForm(ModelForm): class Meta: model = Recipe fields = ("title", "description",) IngredientFormSet = inlineformset_factory(Recipe, Ingredient, fields=('name',), extra=3, can_delete=False, max_num=5)
views.py中使用formset创建和更新recipe(菜谱)的代码如下。在对IngredientFormSet进行实例化的时候,必需指定recipe的实例。
def recipe_update(request, pk): #更新 recipe = get_object_or_404(Recipe, pk=pk) if request.method == "POST": form = RecipeForm(request.POST, instance=recipe) if form.is_valid(): recipe = form.save() ingredient_formset = IngredientFormSet(request.POST, instance=recipe) if ingredient_formset.is_valid(): ingredient_formset.save() return redirect('/recipe/') else: form = RecipeForm(instance=recipe) ingredient_formset = IngredientFormSet(instance=recipe) return render(request, 'recipe/recipe_update.html', {'form': form, 'ingredient_formset': ingredient_formset, }) def recipe_add(request): #创建 if request.method == "POST": form = RecipeForm(request.POST) if form.is_valid(): recipe = form.save() ingredient_formset = IngredientFormSet(request.POST, instance=recipe) if ingredient_formset.is_valid(): ingredient_formset.save() return redirect('/recipe/') else: form = RecipeForm() ingredient_formset = IngredientFormSet() return render(request, 'recipe/recipe_add.html', {'form': form, 'ingredient_formset': ingredient_formset, })
模板recipe/recipe_add.html代码如下:
<h1>Add Recipe</h1> <form action="." method="post"> {% csrf_token %} {{ form.as_p }} <fieldset> <legend>Recipe Ingredient</legend> {{ ingredient_formset.management_form }} {{ ingredient_formset.non_form_errors }} {% for form in ingredient_formset %} {{ form.name.errors }} {{ form.name.label_tag }} {{ form.name }} </div> {% endfor %} </fieldset> <input type="submit" value="Add recipe" class="submit" /> </form>