Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Grokzen/a64321dd69339c42a184 to your computer and use it in GitHub Desktop.
Save Grokzen/a64321dd69339c42a184 to your computer and use it in GitHub Desktop.
Symmetrical ManyToMany Filter Horizontal in Django Admin
# Based on post from: https://snipt.net/chrisdpratt/symmetrical-manytomany-filter-horizontal-in-django-admin/#L-26
# Only reposting to avoid loosing it.
"""
When adding a many-to-many (m2m) relationship in Django, you can use a nice filter-style multiple select widget to manage entries. However, Django only lets you edit the m2m relationship this way on the forward model. The only built-in method in Django to edit the reverse relationship in the admin is through an InlineModelAdmin.
Below is an example of how to create a filtered multiple select for the reverse relationship, so that editing entries is as easy as in the forward direction.
IMPORTANT: I have no idea for what exact versions of Django this will work for, is compatible with or was intended for.
I am sure this have stopped working slightly for new:er versions of Django. I do not use this myself currently with any new code so i can't really tell for sure.
!! Use code at your own risk !!
"""
### pizza/models.py ###
from django.db import models
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping, related_name='pizzas')
class Topping(models.Model):
name = models.CharField(max_length=50)
### pizza/admin.py ###
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.widgets import FilteredSelectMultiple
from .models import Pizza, Topping
class PizzaAdmin(admin.ModelAdmin):
filter_horizonal = ('toppings',)
class ToppingAdminForm(forms.ModelForm):
pizzas = forms.ModelMultipleChoiceField(
queryset=Pizza.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name=_('Pizzas'),
is_stacked=False
)
)
class Meta:
model = Topping
def __init__(self, *args, **kwargs):
super(ToppingAdminForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
self.fields['pizzas'].initial = self.instance.pizzas.all()
def save(self, commit=True):
topping = super(ToppingAdminForm, self).save(commit=False)
if commit:
topping.save()
if topping.pk:
topping.pizzas = self.cleaned_data['pizzas']
self.save_m2m()
return topping
class ToppingAdmin(admin.ModelAdmin):
form = ToppingAdminForm
admin.site.register(Pizza, PizzaAdmin)
admin.site.register(Topping, ToppingAdmin)
@bgaudino
Copy link

Or better yet, just use RelatedFieldWidgetWrapper. That way you would get the plus icon to add new model instances. I was having issues at first, but I think something along these lines would work.

from django.contrib import admin
from django.contrib.admin.widgets import FilteredSelectMultiple, RelatedFieldWidgetWrapper
from django.db import ManyToManyRel, ManyToManyField

RelatedFieldWidgetWrapper(
    FilteredSelectMultiple(
        verbose_name='Calendar Events',
        is_stacked=False,
    ),
    rel=ManyToManyRel(
        field=ManyToManyField(Video),
        to=CalendarEvent,
        through=CalendarEvent.through,
    ),
    admin_site=admin.site,
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment