Opened 3 weeks ago

Closed 3 weeks ago

#35878 closed Uncategorized (duplicate)

Allow to add M2M also in related-model form in admin site

Reported by: David Owned by:
Component: contrib.admin Version:
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Given the following definition of models:

from django.db import models

class ModelA(models.Model):
    name = models.CharField(max_length=10)

class ModelB(models.Model):
    name = models.CharField(max_length=10)
    others = models.ManyToManyField(ModelA)

The admin site allows to add the field ModelB.others to that model's admin, but there is no easy way to add the same relation on ModelA admin with the same functionality .

It is possible to add the field by providing a custom form in which the related-field is defined manually, however the form field widget will not be wrapped in RelatedFieldWidgetWrapper thus the "add new entry" button will not be present. This can be forced in the ModelAdmin.form by turning the form attribute into a property which builds the form class providing the right parameters to the reverse-field.

It would be a great enhancement to allow for an easier way to add reverse-m2m to a model-admin or to write down a guide to obtain such result.

Change History (2)

comment:1 by David, 3 weeks ago

To make it work my "workaround" is

from django import forms
from django.contrib import admin
from django.contrib.admin import widgets
from .models import ModelA, ModelB

class ModelAForm(forms.ModelForm):
    others_set = forms.ModelMultipleChoiceField(
        queryset=ModelB.objects.all(),
        required=False,
        widget=widgets.FilteredSelectMultiple(verbose_name='ModelB', is_stacked=False)
    )
    def _save_m2m(self):
        super()._save_m2m()
        self.instance.others_set.set(self.cleaned_data['others_set'], clear=True)

@admin.register(ModelA)
class ModelAAdmin(admin.ModelAdmin):

    @property
    def form(self):
        class AdminM2MAdminForm(ModelAForm):
            """Helper to display the right widget in admin"""

        AdminM2MAdminForm.declared_fields['others_set'].widget = widgets.RelatedFieldWidgetWrapper(
            AdminM2MAdminForm.declared_fields['others_set'].widget,
            ModelB.others.through._meta.get_field('modelb').remote_field,
            self.admin_site,
        )
        return AdminM2MAdminForm

This way the admin displays the "+" button which points to ModelB form in ModelA admin. Not trivial and quite fragile (some APIs are not documented).

comment:2 by Sarah Boyce, 3 weeks ago

Resolution: duplicate
Status: newclosed

I believe this is a duplicate of #897

Note: See TracTickets for help on using tickets.
Back to Top