21

I have a many to many relationship between 2 tables Users an Domains. I have defined this relationship in the Domains class. So in the admin interface I see the Users when I am viewing Domains. But I do not see Domains when I am viewing Users. How can I achieve this.

4 Answers4

16

I know that this is an older thread, but this was the first result that came up on google and I thought a better answer was necessary.

Via this django bug report I found the best way to have your ManyToManyField show up on both models:

class Test1(models.Model):
    tests2 = models.ManyToManyField('Test2', blank=True)

class Test2(models.Model):
    tests1 = models.ManyToManyField(Test1, through=Test1.tests2.through, blank=True)

I have tested it myself and was very pleased with the results.

steve-gregory
  • 7,396
  • 8
  • 39
  • 47
  • I get the error message "Test2" not defined which seems logic because Test2 is not defined when called. Your solution would be brillant if working though – Timo May 11 '14 at 08:02
  • That's why he added the quotes in Test2, if you add the quotes in 'Test2' will work – Pentux Feb 07 '20 at 15:35
11

The only built-in way is via an InlineModelAdmin, but you can use a custom ModelForm with your User ModelAdmin to create field for this purpose. See the code below for simplified setup (assumes users = ManyToManyField(related_name='domains')).

### yourapp/admin.py ###

from django import forms
from django.contrib import admin
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.widgets import FilteredSelectMultiple

from .models import Domain

class DomainAdmin(admin.ModelAdmin):
    filter_horizonal = ('users',)

class UserAdminForm(forms.ModelForm):
    domains = forms.ModelMultipleChoiceField(
        queryset=Domain.objects.all(), 
        required=False,
        widget=FilteredSelectMultiple(
            verbose_name=_('Domains'),
            is_stacked=False
        )
    )

    class Meta:
        model = User

    def __init__(self, *args, **kwargs):
        super(UserAdminForm, self).__init__(*args, **kwargs)

        if self.instance:
          self.fields['domains'].initial = self.instance.domains.all()

    def save(self, commit=True):
        user = super(UserAdminForm, self).save(commit=False)

        user.domains = self.cleaned_data['domains']

        if commit:
            user.save()
            user.save_m2m()

        return user

class UserAdmin(admin.ModelAdmin):
    form = UserAdminForm

admin.site.register(Domain, DomainAdmin)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    This didn't quite work for me when adding the 'user' (not a `User` in my case). I needed to check for `self.instance.id` in the `__init__` method, and I needed to `save()` the new object before assigning domains to it. – alalonde Feb 17 '12 at 23:37
5

I think what you're looking for is InlineModelAdmin.

bryan
  • 2,223
  • 1
  • 22
  • 19
  • Probably not what the OP was hoping for (a way to use the regular ManyToMany filter selector from either side of the relation), but this is the solution I would use too. – Carl Meyer Mar 19 '09 at 09:19
  • 3
    How do I refer to the relationship table (Domains_User in my case) I do not have an explicit intermediary table. In this case how do I use InlineModelAdmin for ManyToManyRelationship –  Mar 19 '09 at 14:14
0

For people who still bump into this, it might be worth checking ​https://github.com/kux/django-admin-extend

It provides a mechanism for injecting bidirectional many-to-many fields in ModelAdmins that have already been defined by other apps.

Ioan Alexandru Cucu
  • 11,981
  • 6
  • 37
  • 39