Django modelformset_factory使用

在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.']}]

.....

上一篇:Django中 页面与模态框数据传输

下一篇:Python 垃圾回收机制