Question

I have a model w/ a manytomany relation to another model. They both have forms associated with them, the latter has a formset.

 class Foo(models.Model):
   name = models.CharField(max_length=20)
   bars = models.ManyToManyField("Bar",blank=True,null=True)

 class Bar(models.Model):
   name = models.CharField(max_length=20)

 class FooForm(ModelForm):
   class Meta:
     model = Foo

 class BarForm(ModelForm):
   class Meta:
     model = Bar

 BarFormSet = modelformset_factory(Bar,form=BarForm,extra=2)

In my view/template the standard ManyToManyField widget is replaced with the formset. Thus, I have to manually associate the instances of Bar specified in that formset with Foo's ManyToManyField. I am doing this in Foo's clean method:

 def clean(self,*arg,**kwargs):
   cleaned_data = self.cleaned_data
   # barSubFormInstance is the BarSubForm that is displayed in my view
   if barFormSetInstance.is_valid(): 
     barInstances = barFormSetInstance.save()
     cleaned_data["bars"] = barInstances
   return cleaned_data

This almost works. The problem is that it sets Foo.bars to the set of changed forms within the formset. Thus if I add one bar to my foo, then reload the form and add a second bar, the foo winds up only having that second bar.

According to the Django documentation:

The save() method returns the instances that have been saved to the database. If a given instance's data didn't change in the bound data, the instance won't be saved to the database and won't be included in the return value...

So I understand why my code is failing. I just don't know what to do about it. What can I pass to cleaned_data["bars"] that will add the newly modified forms but not remove the existing ones?

Many thanks for your help.

Was it helpful?

Solution

For what it's worth, there was an error in the above comment. And some other details I left out.

The appropriate code looks like this:

def clean(self,*arg,**kwargs):
   cleaned_data = self.cleaned_data
   # barFormSetInstance is the BarFormSet that is displayed in my view
   # it's already been validated by the time this function is called
   barInstances = [form.save() for form in barFormSetInstance if form.cleaned_data]
   cleaned_data["bars"] = barInstances
   return cleaned_data

I also had to ensure that "blank" and "null" were both set to True for the ManyToManyField. (I'm not sure why I had to do that instead of overriding that field's is_valid() method to just return True).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top