Python 在Django中保存嵌套表单集的正确方法

Python 在Django中保存嵌套表单集的正确方法,python,django,django-forms,Python,Django,Django Forms,我有一个三级测试模型,我想以嵌套的形式呈现。每个测试有多个结果,每个结果可以有多行。下面我将介绍如何创建嵌套的表单集,以及如何更新耶格勒的代码以获得更新的Django版本(我使用的是1.4) 我遇到了麻烦,因为我想使用FormSet的“extra”参数在FormSet中包含一行额外的内容。每一行的ForeignKey必须指向该行所属的结果,但用户不能更改,因此我使用HiddenInput字段将结果包含在表单集的每一行中 这会导致“缺少必填字段”验证错误,因为结果字段始终填写(在添加字段中),但文

我有一个三级测试模型,我想以嵌套的形式呈现。每个测试有多个结果,每个结果可以有多行。下面我将介绍如何创建嵌套的表单集,以及如何更新耶格勒的代码以获得更新的Django版本(我使用的是1.4)

我遇到了麻烦,因为我想使用FormSet的“extra”参数在FormSet中包含一行额外的内容。每一行的ForeignKey必须指向该行所属的结果,但用户不能更改,因此我使用HiddenInput字段将结果包含在表单集的每一行中

这会导致“缺少必填字段”验证错误,因为
结果
字段始终填写(在添加字段中),但
文本
严重性
可能不填写(如果用户选择不输入另一行)。我不知道处理这种情况的正确方法。我认为我不需要在add_字段中包含初始的
结果
值,而且必须有一种更好的实际工作方式

在下面这个问题的底部更新

如有必要,我将乐意补充更多细节

我的自定义表单集的代码:

LineFormSet = modelformset_factory(
    Line,  
    form=LineForm,
    formset=BaseLineFormSet,
    extra=1)

class BaseResultFormSet(BaseInlineFormSet):

    def __init__(self, *args, **kwargs):
        super(BaseResultFormSet, self).__init__(*args, **kwargs)

    def is_valid(self):
        result = super(BaseResultFormSet, self).is_valid()

        for form in self.forms:
            if hasattr(form, 'nested'):
                for n in form.nested:
                    n.data = form.data
                    if form.is_bound:
                        n.is_bound = True  
                    for nform in n:
                        nform.data = form.data
                        if form.is_bound:
                            nform.is_bound = True
                    # make sure each nested formset is valid as well
                    result = result and n.is_valid()
        return result

    def save_all(self, commit=True):
        objects = self.save(commit=False)

        if commit:
            for o in objects:
                o.save()

        if not commit:
            self.save_m2m()

        for form in set(self.initial_forms + self.saved_forms):
            for nested in form.nested:
                nested.save(commit=commit)

    def add_fields(self, form, index):
        # Call super's first
        super(BaseResultFormSet, self).add_fields(form, index)

        try:
            instance = self.get_queryset()[index]
            pk_value = instance.pk
        except IndexError:
            instance=None
            pk_value = hash(form.prefix)


        q = Line.objects.filter(result=pk_value)
        form.nested = [
            LineFormSet(
                queryset = q, #data=self.data, instance = instance, prefix = 'LINES_%s' % pk_value)]
                prefix = 'lines-%s' % pk_value,
                initial = [
                    {'result': instance,}
                ]
            )]
测试模型

class Test(models.Model):
    id = models.AutoField(primary_key=True, blank=False, null=False)

    attempt = models.ForeignKey(Attempt, blank=False, null=False)
    alarm = models.ForeignKey(Alarm, blank=False, null=False)

    trigger = models.CharField(max_length=64)
    tested = models.BooleanField(blank=False, default=True)
结果模型

class Result(models.Model):
    id = models.AutoField(primary_key=True)   
    test = models.ForeignKey(Test)

    location = models.CharField(max_length=16, choices=locations)
    was_audible = models.CharField('Audible?', max_length=8, choices=audible, default=None, blank=True)
线模型

class Line(models.Model):
    id = models.AutoField(primary_key=True)
    result = models.ForeignKey(Result, blank=False, null=False)

    text = models.CharField(max_length=64)
    severity = models.CharField(max_length=4, choices=severities, default=None)

更新

昨晚我将此添加到我的LineForm(ModelForm)类中:


如果只填写结果(HiddenInput),则忽略保存请求。这种方法还没有遇到任何问题,但我没有尝试添加新表单。

当我在类似情况下对表单集使用
extra
时,我不得不在表单中包含来自模型的所有必需字段,作为输入。有点难看,但它起作用了,好奇是否有人有黑客在附近

编辑
当我在上面写的时候我很困惑,我只是在用
extra
initial
来预先填写额外的表单,而且我还没有完全了解你问题的所有细节

如果我理解正确,在
add\u字段中实例化
LineFormSet
s的位置,每个字段都指向相同的
Result
实例

在这种情况下,由于您遇到的问题,您实际上不想在
初始
中提供
结果
。相反,您可以从线型模型表单中完全删除该字段,并自定义
LineFormSet
类,例如:

class LineFormSet(forms.BaseModelFormSet):
    # whatever other code you have in it already
    # ...
    # ...
    def __init__(self, result, *args, **kwargs):
        super(LineFormSet, self).__init__(*args, **kwargs)
        self.result = result

    def save_new(self, form, commit=True):
        instance = form.save(commit=False)
        instance.result = self.result
        if commit:
            instance.save()
        return instance

    def save_existing(self, form, instance, commit=True):
        return self.save_new(form, commit)
(这在Django 1.3和1.4中应该可以,但不确定其他版本)

因此,
add_fields
方法的相关部分如下所示:

   form.nested = [
        LineFormSet(
            result = instance,
            queryset = q, #data=self.data, instance = instance, prefix = 'LINES_%s' % pk_value)]
            prefix = 'lines-%s' % pk_value,
        )]

你能再解释一下它是如何工作的吗?我已经包含了一个必填字段作为HiddenInput,这是我问题的根源(这是我的第一个Django项目也没有帮助)。我没有足够仔细地阅读你的问题,它只是提醒我在我自己的代码中做了一些事情。我已经修改了我的答案,希望有更多的帮助。从Django的角度来看,这是有意义的,但是从模型代码中删除
result
字段将意味着Django不再将结果保存到正确的表中。我想。作为Django的新手,我不太确定。不是从模型本身。。。从ModelForm类来看,似乎您必须在某个地方有类似于
LineForm
类的东西?然后,为了将
结果
保存到模型实例中,我已经覆盖了ModelFormSetAh的save方法,我明白了。现在这对我来说更有意义了。您是对的,我有一个定制的
LineForm
类。我将重写有问题的代码,然后回来接受答案。
   form.nested = [
        LineFormSet(
            result = instance,
            queryset = q, #data=self.data, instance = instance, prefix = 'LINES_%s' % pk_value)]
            prefix = 'lines-%s' % pk_value,
        )]