3

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)?

moritz_h
  • 135
  • 7

2 Answers2

3

@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.

moritz_h
  • 135
  • 7
1

One possibility is to define the field on the "other" model. So instead of writing this:

class Store(models.Model):
    ...

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

You can write this:

class Brand(models.Model):
...

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

Or, if you have manually added the field to the form, you could populate its initial value in the form's __init__() method.

Jaap Joris Vens
  • 3,382
  • 2
  • 26
  • 42