Search code examples
pythondjangodjango-modelsdjango-formsmany-to-many

Django Include ManyToManyField on "other" model in ModelForm


I would like to have a form with the preselected checkboxes of a ManyToManyField.

models.py

class Store(models.Model):
    ...

class Brand(models.Model):
   stores = models.ManyToManyField(Store, blank=True, related_name="brands")

forms.py

class StoreForm(ModelForm):

    class Meta:
        model = Store
        fields = ('brands',)

I get this exception:

django.core.exceptions.FieldError: Unknown field(s) (brands) specified for Store

I know that I can add the field manually to the class:

brands = forms.ModelMultipleChoiceField(
         queryset=Brand.objects.all(),
         widget=forms.CheckboxSelectMultiple,
    )

If I do this the checkboxes are not preselected.

How is it possible to include the ManyToMany field from "the other side" of the model (from Store)?


Solution

  • @hedgie To change the field in the other model is not a good option for me because I use it already.

    But the __init__() was a good hint. I come up with this solution and it seems to work.

    class StoreForm(ModelForm):
        def __init__(self, *args, **kwargs):
            if kwargs.get('instance'):
                brand_ids = [t.pk for t in kwargs['instance'].brands.all()]
    
                kwargs['initial'] = {
                    'brands': brand_ids,
                }
            super().__init__(*args, **kwargs)
    
        # https://stackoverflow.com/questions/49932426/save-many-to-many-field-django-forms
        def save(self, commit=True):
            # Get the unsaved Pizza instance
            instance = forms.ModelForm.save(self, False)
    
            # Prepare a 'save_m2m' method for the form,
            old_save_m2m = self.save_m2m
    
            def save_m2m():
                old_save_m2m()
                # This is where we actually link the pizza with toppings
                instance.brands.clear()
                for brand in self.cleaned_data['brands']:
                    instance.brands.add(brand)
    
            self.save_m2m = save_m2m
    
            # Do we need to save all changes now?
            # Just like this
            # if commit:
            instance.save()
            self.save_m2m()
    
            return instance
    
        brands = forms.ModelMultipleChoiceField(
             queryset=Brand.objects.all(),
             widget=forms.CheckboxSelectMultiple,
        )
    

    Though it seems to be not very elegant. I wonder why django does not support a better way.