预填充内联FormSet?

Fre*_*son 42 python django django-forms

我正在为一支乐队的出勤报名表工作.我的想法是让表格的一部分输入表演或排练的事件信息.这是事件表的模型:

class Event(models.Model):
    event_id = models.AutoField(primary_key=True)
    date = models.DateField()
    event_type = models.ForeignKey(EventType)
    description = models.TextField()
Run Code Online (Sandbox Code Playgroud)

然后我想要一个内联FormSet,将乐队成员链接到事件并记录它们是否存在,缺席或原谅:

class Attendance(models.Model):
    attendance_id = models.AutoField(primary_key=True)
    event_id = models.ForeignKey(Event)
    member_id = models.ForeignKey(Member)
    attendance_type = models.ForeignKey(AttendanceType)
    comment = models.TextField(blank=True)
Run Code Online (Sandbox Code Playgroud)

现在,我想做的是预先填充此内联FormSet,其中包含所有当前成员的条目,并默认它们存在(大约60个成员).不幸的是,Django 在这种情况下不允许初始值.

有什么建议?

Jam*_*ett 32

所以,你不会喜欢这个答案,部分是因为我还没有写完代码,部分原因是因为这是很多工作.

正如我自己遇到的那样,你需要做的是:

  1. 花了很多时间阅读formset和model-formset代码,以了解它是如何工作的(没有一些功能存在于formset类上,而其中一些功能存在于吐出的工厂函数中)他们出去).您将在后面的步骤中需要这些知识.
  2. 编写自己的formset类,其中包含子类BaseInlineFormSet和接受initial.这里的真正棘手的一点是,你必须重写__init__(),你必须确保它可以调出来BaseFormSet.__init__(),而不是使用直接的父母或祖父母__init__()(因为这些是BaseInlineFormSetBaseModelFormSet,分别和他们都没有能够处理原始数据).
  3. 编写适当的admin内联类的子类(在我的例子中TabularInline),并覆盖其get_formset方法以返回inlineformset_factory()使用自定义formset类的结果.
  4. ModelAdmin具有内联,覆盖add_view和模拟change_view大多数代码的模型的实际子类上,但有一个重大变化:构建formset所需的初始数据,并将其传递给您的自定义formset(将由您返回ModelAdminget_formsets()方法).

我与Brian和Joseph就未来的Django版本进行了一些有关改进的讨论.目前,模型表单的工作方式只会使它比通常值得更麻烦,但是通过一些API清理,我认为它可以变得非常简单.

  • 让人惊讶.我想我会通过两种不同的形式来避免这个问题.谢谢! (4认同)
  • 好答案.这实际上是django中一个非常混乱,复杂且记录严重的缺失功能. (2认同)
  • 这方面的现状如何?有我可以订阅的门票吗?你需要帮助吗? (2认同)

Eri*_*ulf 20

我花了相当多的时间尝试提出一个可以跨站点重用的解决方案.詹姆斯的帖子包含了扩展BaseInlineFormSet但战略性地援引呼叫的关键智慧BaseFormSet.

下面的解决方案分为两部分:a AdminInline和a BaseInlineFormSet.

  1. 所述InlineAdmin动态地生成基于暴露的请求对象的初始值.
  2. 它使用currying将初始值公开给BaseInlineFormSet传递给构造函数的自定义关键字参数.
  3. BaseInlineFormSet构造弹出关闭的关键字参数和结构正常列表中的初始值.
  4. 最后一部分是通过更改表单的最大总数并使用BaseFormSet._construct_formBaseFormSet._construct_forms方法来覆盖表单构造过程

以下是使用OP类的一些具体代码段.我已经针对Django 1.2.3进行了测试.我强烈建议在开发时保留formsetadmin文档.

admin.py

from django.utils.functional import curry
from django.contrib import admin
from example_app.forms import *
from example_app.models import *

class AttendanceInline(admin.TabularInline):
    model           = Attendance
    formset         = AttendanceFormSet
    extra           = 5

    def get_formset(self, request, obj=None, **kwargs):
        """
        Pre-populating formset using GET params
        """
        initial = []
        if request.method == "GET":
            #
            # Populate initial based on request
            #
            initial.append({
                'foo': 'bar',
            })
        formset = super(AttendanceInline, self).get_formset(request, obj, **kwargs)
        formset.__init__ = curry(formset.__init__, initial=initial)
        return formset
Run Code Online (Sandbox Code Playgroud)

forms.py

from django.forms import formsets
from django.forms.models import BaseInlineFormSet

class BaseAttendanceFormSet(BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        """
        Grabs the curried initial values and stores them into a 'private'
        variable. Note: the use of self.__initial is important, using
        self.initial or self._initial will be erased by a parent class
        """
        self.__initial = kwargs.pop('initial', [])
        super(BaseAttendanceFormSet, self).__init__(*args, **kwargs)

    def total_form_count(self):
        return len(self.__initial) + self.extra

    def _construct_forms(self):
        return formsets.BaseFormSet._construct_forms(self)

    def _construct_form(self, i, **kwargs):
        if self.__initial:
            try:
                kwargs['initial'] = self.__initial[i]
            except IndexError:
                pass
        return formsets.BaseFormSet._construct_form(self, i, **kwargs)

AttendanceFormSet = formsets.formset_factory(AttendanceForm, formset=BaseAttendanceFormSet)
Run Code Online (Sandbox Code Playgroud)


rui*_*eal 16

Django 1.4及更高版本支持提供初始值.

就原始问题而言,以下内容可行:

class AttendanceFormSet(models.BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        super(AttendanceFormSet, self).__init__(*args, **kwargs)
        # Check that the data doesn't already exist
        if not kwargs['instance'].member_id_set.filter(# some criteria):
            initial = []
            initial.append({}) # Fill in with some data
            self.initial = initial
            # Make enough extra formsets to hold initial forms
            self.extra += len(initial)
Run Code Online (Sandbox Code Playgroud)

如果您发现表单正在填充但未保存,那么您可能需要自定义模型表单.一种简单的方法是在初始数据中传递标记并以init形式查找它:

class AttendanceForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(AttendanceForm, self).__init__(*args, **kwargs)
        # If the form was prepopulated from default data (and has the
        # appropriate tag set), then manually set the changed data
        # so later model saving code is activated when calling
        # has_changed().
        initial = kwargs.get('initial')
        if initial:
            self._changed_data = initial.copy()

    class Meta:
        model = Attendance
Run Code Online (Sandbox Code Playgroud)