Search code examples
djangodjango-modelsdjango-adminm2mdjango-intermediate-table

Django M2M Relationship Issue with Admin Using Intermediate Table with Multiselect


I'm working with m2m relationships using through to set intermediate table. Issue is I need to show multiselection instead of a normal dorpdown but when I select multiple items and save I get an error.

ValueError: Cannot assign "<QuerySet [<Facility: facility1>, <Facility: facility2>]>": "Property.facility" must be a "Facility" instance.

Also I am showing this model in admin.TabularInline which allows me select only one item per row as tabular inline gives capability to insert multiple forms.

I have tried multiple solutions like custom save and many other things and some how I get able to save that but then issue appears on view. I need to show only one form with multiselect widget to perform this selection.

models.py


class Facility(models.Model):
    name = models.CharField(max_length=200)

class Property(models.Model):
    name = models.CharField(max_length=200)
    area = models.CharField(max_length=200)
    facility = models.ManyToManyField(Facility, through="PropertyFacility")

class PropertyFacility(models.Model):
    prop = models.ForeignKey(
        Property, related_name="facilities", on_delete=models.CASCADE
    )
    facility = models.ForeignKey(
        Facility, related_name="properties", on_delete=models.CASCADE
    )

admin.py


from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import ugettext_lazy as _

class PropertyFacilityForm(forms.ModelForm):
    facility = forms.ModelMultipleChoiceField(Facility.objects.all(), required=True, widget=FilteredSelectMultiple(_('ss'), False, attrs={'rows':'10'})

class PropertyFacilityInline(admin.TabularInline):
    model = Property.facility.through
    form = PropertyFacilityForm

class PropertyAdmin(TabbedModelAdmin):
    model = Property
    tab_facilities = (PropertyFacilityInline,)
    tab_property = (
        (
            "Property Details",
            {
                "fields": (
                    "name",
                    "area",
                )
            },
        ),
    )

    tabs = [
        ("Property", tab_property),
        ("Facilities", tab_facilities),
    ]

It let me show on admin like this Here you can see I am able to select multiple select but the form should be one here instead of multiple.

I want to show only one single form with multiselection widget which allows me multi select and save it and then on change_view it shows the selected one and the left unselected ones.


Solution

  • I had fixed the issue by adding some custom logic for saving m2m field.

    admin.py
    
    class PropertyForm(forms.ModelForm):
        facility = forms.ModelMultipleChoiceField(Facility.objects.all(), required=False, widget=forms.CheckboxSelectMultiple)
        class Meta:
            model = Property
            fields = ["id", "name", "area", "city"]
    
        def save(self, commit=True):
            prop_facilities = self.cleaned_data.pop('facility')
            instance = forms.ModelForm.save(self, commit=False)
            instance.facility.clear()
            for facility in prop_facilities:
                PropertyFacility.objects.create(facility=facility, prop=instance)
            return instance
    
    
    class PropertyAdmin(TabbedModelAdmin):
        model = Property
        tab_facilities = (("Facilities", {"fields": ("facility", )},),)
        tab_property = (
            (
                "Property Details",
                {
                    "fields": (
                        "name",
                        "area",
                    )
                },
            ),
        )
    
        tabs = [
            ("Property", tab_property),
            ("Facilities", tab_facilities),
        ]