save manytomanyfield with modelformset
-
27-04-2021 - |
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.
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).