title: Correct way to save nested formsets in Django
body: <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>