Note that there are some explanatory texts on larger screens.

plurals
  1. POCorrect way to save nested formsets in Django
    primarykey
    data
    text
    <p>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 <a href="http://yergler.net/blog/2009/09/27/nested-formsets-with-django/" rel="nofollow noreferrer">Yergler's method</a> for creating nested formsets, along with <a href="https://stackoverflow.com/questions/4832672/django-inline-formsets-with-a-complex-model-for-the-nested-form">this SO question</a> that updates Yergler's code for more recent Django version (I'm on 1.4)</p> <p>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.</p> <p>This leads to "missing required field" validation errors because the <code>result</code> field is always filled out (in add_fields), but the <code>text</code> and <code>severity</code> may not (if the user chose not to enter another line). I do not know the correct way to handle this situation. I <em>think</em> that I don't need to include the initial <code>result</code> value in add_fields, and that there must be a better way that actually works.</p> <p><strong>Update below towards bottom of this question</strong></p> <p>I will gladly add more detail if necessary.</p> <p>The code of my custom formset:</p> <pre><code>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,} ] )] </code></pre> <p>Test Model</p> <pre><code>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) </code></pre> <p>Result Model</p> <pre><code>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) </code></pre> <p>Line Model</p> <pre><code>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) </code></pre> <hr> <p><strong>Update</strong></p> <p>Last night I added this to my LineForm(ModelForm) class:</p> <pre><code>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 </code></pre> <p>It ignores the requests to save if <em>only</em> 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.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload