在Django中保存嵌套formset的正确方法

时间:2012-06-08 17:39:09

标签: python django django-forms

我有一个3级测试模型,我想作为嵌套的formsets呈现。每个测试都有多个结果,每个结果可以有多个行。我正在关注Yergler's method创建嵌套的formsets,以及this SO question更新Yergler的代码以获取更新的Django版本(我在1.4上)

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

这导致“缺少必填字段”验证错误,因为result字段始终填写(在add_fields中),但textseverity可能不会(如果用户选择)不要进入另一条线)。我不知道处理这种情况的正确方法。我认为我不需要在add_fields中包含初始result值,并且必须有更好的实际工作方式。

在此问题的底部更新

如有必要,我很乐意添加更多细节。

我的自定义formset的代码:

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)类:

def save(self, commit=True):
    saved_instance = None

    if not(len(self.changed_data) == 1 and 'result' in self.changed_data):
            saved_instance = super(LineForm, self).save(commit=commit)

    return saved_instance

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

1 个答案:

答案 0 :(得分:1)

当我在类似情况下在formset上使用extra时,我最终必须在表单中包含模型中的所有必填字段,如HiddenInputs。有点难看,但它很有用,好奇,如果有人有黑客攻击。

修改
我在上面写的时候感到很困惑,我只是使用extrainitial来制作表格集,以预先填写额外的表格,而且我还没有完全掌握你问题的所有细节。

如果我理解正确,您在LineFormSet中实例化add_fields的位置,每个Result将指向相同的result实例?

在这种情况下,由于您遇到的问题,您真的不想在initial中提供LineFormSet。相反,您可以完全从LineForm模型表单中删除该字段,并自定义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) 类,如:

add_fields

(这在Django 1.3和1.4中应该没问题,不确定其他版本)

所以 form.nested = [ LineFormSet( result = instance, queryset = q, #data=self.data, instance = instance, prefix = 'LINES_%s' % pk_value)] prefix = 'lines-%s' % pk_value, )] 方法的相关部分如下:

{{1}}