在django后台,当指定了editabel字段后,我们可以批量修改字段的值。如果我们自己想实现这样的功能就需要借助modelformset_factory函数.对应的表单一定要是模型表单。
下面看下例子:
forms.py
class StudyRecordModelForm(forms.ModelForm):
class Meta:
model = models.StudyRecord
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs.update({'class': 'form-control'})
models.py
class StudyRecord(models.Model):
"""
学习记录
"""
attendance = models.CharField("考勤", choices=attendance_choices, default="checked", max_length=64)
score = models.IntegerField("本节成绩", choices=score_choices, default=-1)
homework_note = models.CharField(max_length=255, verbose_name='作业批语', blank=True, null=True)
date = models.DateTimeField(auto_now_add=True, verbose_name='日期')
note = models.CharField("备注", max_length=255, blank=True, null=True)
homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None)
course_record = models.ForeignKey('CourseRecord', verbose_name="某节课程")
student = models.ForeignKey('Customer', verbose_name="学员")
class Meta:
unique_together = ('course_record', 'student')
verbose_name_plural = verbose_name = '学习记录'
def __str__(self):
return self.student.name + ':' + str(self.course_record.day_num)
view.py
class StudyRecordView(View):
def get(self, request, course_id):
formset_obj = modelformset_factory(model=StudyRecord, form=StudyRecordModelForm, extra=0)
formset = formset_obj(queryset=StudyRecord.objects.filter(course_record_id=course_id))
return render(request, 'study_record/study_record.html', {'formset': formset})
def post(self, request, course_id):
formset_obj = modelformset_factory(model=StudyRecord, form=StudyRecordModelForm, extra=0)
formset = formset_obj(request.POST)
if formset.is_valid():
formset.save()
return redirect(request.path)
else:
return render(request, 'study_record/study_record.html', {'formset': formset})
在前端进行显示 :
study_record.html
<form method="post" action="" class="form-inline">
{{ formset.management_form }}
<select name="action" class="form-control">
{% show_pri_pub_option request %}
<option value="bulk_delete">批量删除</option>
</select>
{% csrf_token %}
<input type="submit" class="btn btn-primary btn-sm" value="应用">
<table class="table table-bordered table-striped table-hover">
<!-- On rows -->
<thead>
<tr>
<td class="">
<input type="button" class=" btn-success select_id" id="select_all" value="全选">
</td>
<td class="success">序号</td>
<td class="info">考勤</td>
<td class="success">成绩</td>
<td class="info">批语</td>
<td class="success">日期</td>
<td class="info">课程</td>
<td class="info">学员</td>
<td class="success">操作</td>
</tr>
</thead>
<tbody>
{% for field in formset %}
<tr>
{{ field.id }}
<td><input type="checkbox" class="select_pk" name="pk" value="{{ study_record.pk }}"></td>
<td>{{ forloop.counter }}</td>
<td>
{{ field.attendance }}
</td>
<td>{{ field.score }}</td>
<td>{{ field.homework_note }}</td>
<td>{{ field.date|date:'Y-m-d' }}</td>
<td>{{ field.instance.course_record }}</td>
<td class="hidden">{{ field.course_record }}</td>
<td>{{ field.instance.student }}</td>
<td class="hidden">{{ field.student }}</td>
<td>
<a class="fa fa-2x fa-edit"
href="">
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button class="btn btn-success">提交</button>
</form>
这里有几点需要注意:
我们要加上 {{ field.id }}
默认情况下{{ field.course_record }}会把所有值都拿到,因此会是一个下拉框,所以我们用<td>{{ field.instance.course_record }},这样它渲染后是一个字符串,但这样提交表单会出错。所以我们加上正常的表单内容,但不在前端显示:</td><td class="hidden">{{ field.course_record }}</td>
要同时提交多条数据,需要加上:{{ formset.management_form }}
这块内容实际比较多,这只是一个简单的例子。后面用到再更新。
update: 因为modelformset_factory使用的是formset_factory,只是扩展了其功能,下面更新下formset_factory
表单集:formset_factory,是一页面显示多个表单的抽象,类似于一个数据表格:
>>> from django import forms
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
当需要一次创建多个Article时,那么我们可以根据ArticleForm创建一个表单集
>>> from django.forms.formsets import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)
对其迭代就可以显示了:
>>> 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参数决定显示个数,下面将显示两个
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
迭代时默认以字段创建顺序来显示,可以通过__iter__()方法改变顺序。
为表单集提供初始数据:
当提供初始数据时,extra将决定在提供的数据的基础上还额外创建多少个空的表单,
>>> import datetime
>>> from django.forms.formsets import formset_factory
>>> from myapp.forms import ArticleForm
>>> 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>
注意:这里是formset_factory,它与modelFormset_factory初始化是有区别的:
forset_factory初始化时是通过initial,而modelformset国为与model关联,它需要指定queryset.
max_num参数会限制显示表单的个数,且与extra有关
分几种情况:
当max_num大于初始数据个数时与extra的和时,初始数据和空表都会显示,而当不足以显示所有的空表时,空表数量将被限制。
当初始数据大于max_num时,所有数据都会显示,而忽略max_num,但不会有空表单显示,即extra会被忽略
当max_num为None,相当于没有限制
formset验证
formset验证与form表单几乎一样:
>>> from django.forms.formsets import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> data = {
... 'form-TOTAL_FORMS': '1',
... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '',
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
True
当没有传入数据时,就会invalid,错误信息也在相应的列表中:
>>> data = {
... 'form-TOTAL_FORMS': '2',
... 'form-INITIAL_FORMS': '0',
... 'form-MAX_NUM_FORMS': '',
... 'form-0-title': 'Test',
... 'form-0-pub_date': '1904-06-16',
... 'form-1-title': 'Test',
... 'form-1-pub_date': '', # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
.....