在Django中保存嵌套表单的正确方法 [英] Correct way to save nested formsets in Django
问题描述
我遇到麻烦,因为我想使用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 $ c $ 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 LineFormSet
s 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屋!