在Django中保存嵌套表单的正确方法 [英] Correct way to save nested formsets in Django

查看:142
本文介绍了在Django中保存嵌套表单的正确方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个3级测试模型,我想作为嵌套的formets呈现。每个测试都有多个结果,每个结果可以有多个行。我正在关注创建嵌套表单的 Yergler的方法 ,以及这个SO问题更新Yergler的代码以获取更多的Django版本(我在1.4)



我遇到麻烦,因为我想使用FormSet的extra参数来包含一个额外的在表单集行。每行的ForeignKey必须指向Line属于的结果,但不能由用户更改,因此我使用HiddenInput字段在每个FormSet的行中包含结果。



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

更新下面这个问题的底部



我会很乐意添加更多的细节if



我的自定义表单的代码:

  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 self.forms中的表单:
如果hasattr(form,'nested'):
for n in form.nested:
n.data = form.data
如果form.is_boun d:
n.is_bound = True
n中的nform
nform.data = form.data
如果form.is_bound:
nform.is_bound = True
#确保每个嵌套表单都有效
result = result和n.is_valid()
返回结果

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

如果提交:
对象中的o:
o.save()

如果不提交:
self.save_m2m()

在表单中设置(self.initial_forms + self.saved_forms):
嵌套在form.nested中:$ b $


$ b def add_fields(self,form,index):
#调用super的第一个
super(BaseResultFormSet,self).add_fields(form, index)

尝试:
instance = self.get_queryset()[index]
pk_value = instance.pk
除了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'实例,
]
)]

测试模型

  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)

结果模型

 类结果(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 =选择=可听,默认=无,空白=真)

线型

  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保存(self,c ommit = True)
saved_instance = None

如果没有(len(self.changed_data)== 1和self.changed_data中的'result'):
saved_instance = super LineForm,self).save(commit = commit)

return saved_instance

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

解决方案

当我在类似情况下使用额外的,最终不得不将表单中所有必需的字段包含在HiddenInputs中。有点丑,但它有效,如果有人有一个黑客,好奇。



编辑

我很困惑我上面写了,我刚刚使用额外的初始一起使用formets来预先填写额外的表单,我也没有完全了解你的问题的所有细节。



如果我理解正确,你在哪里实例化$ code LineFormSet add_fields 中的每个都将指向相同的结果实例?



在这种情况下,您不希望在初始中提供 result 你遇到的问题相反,您可以从LineForm模型窗体中删除该字段,并自定义 LineFormSet 类,如下所示:

  class LineFormSet(forms.BaseModelFormSet):
#无论其他代码已经有
#...
#...
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
如果提交:
实例.save()
返回实例

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,
)]


I have a 3-level Test model I want to present as nested formsets. Each Test has multiple Results, and each Result can have multiple Lines. I am following Yergler's method for creating nested formsets, along with this SO question that updates Yergler's code for more recent Django version (I'm on 1.4)

I am running into trouble because I want to use FormSet's "extra" parameter to include an extra Line in the formset. The ForeignKey for each Line must point to the Result that the Line belongs to, but cannot be changed by the user, so I use a HiddenInput field to contain the Result in each of the FormSet's Lines.

This leads to "missing required field" validation errors because the result field is always filled out (in add_fields), but the text and severity may not (if the user chose not to enter another line). I do not know the correct way to handle this situation. I think that I don't need to include the initial result value in add_fields, and that there must be a better way that actually works.

Update below towards bottom of this question

I will gladly add more detail if necessary.

The code of my custom 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,}
                ]
            )]

Test Model

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)

Result Model

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)

Line Model

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)


Update

Last night I added this to my LineForm(ModelForm) class:

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

It ignores the requests to save if only the result (a HiddenInput) is filled out. I haven't run into any problems with this approach yet, but I haven't tried adding new forms.

解决方案

When I used extra on formsets in similar situation I ended up having to include all the required fields from the model in the form, as HiddenInputs. A bit ugly but it worked, curious if anyone has a hack-around.

edit
I was confused when I wrote above, I'd just been working on formsets using extra with initial to pre-fill the extra forms and also I hadn't fully got all the details of your questions.

If I understand correctly, where you instantiate the LineFormSets in add_fields each of those will point to the same Result instance?

In this case you don't really want to supply result in initial due to the problems you're having. Instead you could remove that field from the LineForm model-form altogether and customise the LineFormSet class something like:

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)

(this should be ok in Django 1.3 and 1.4, not sure other versions)

so the relevant part of your add_fields method would look like:

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

这篇关于在Django中保存嵌套表单的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆