davide
davide

Reputation: 86

Django many-to-many relations

I'm trying to create a web app in which users can participate in some groups (every user can be part of multiple groups), and I want to be able to make both queries like

group.users_set()

and

user.groups_set()

I want to see all groups a user is participating to in the admin page of every user and vice versa. My last attempt was this:

class CustomUser(AbstractUser):
    groups = models.ManyToManyField('Group', through='Participation')

class Group(models.Model):
    group_name = models.CharField(max_length=200)    
    group_password = models.CharField(max_length=200)
    customusers = models.ManyToManyField('CustomUser', through='Participation')

class Participation(models.Model):
    customuser = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE) 

but I get

django.core.management.base.SystemCheckError: SystemCheckError: System check identified some issues:

ERRORS:
<class 'userreg.admin.CustomUserAdmin'>: (admin.E013) The value of 'fieldsets[2][1]["fields"]' cannot include the ManyToManyField 'groups', because that field manually specifies a relationship model.

Before, with just

users = models.ManyToManyField(CustomUser)

in the Group class and without the Participation class, I was able to get half of my goal, seeing the list of logged users in the admin page. What am I doing wrong?

Upvotes: 1

Views: 779

Answers (2)

davide
davide

Reputation: 86

The answer provided by @Willem Van Onsem was very useful, but I solved my problem doing this in models.py

class CustomUser(AbstractUser):
    groups = models.ManyToManyField('Group')

class Group(models.Model):
    name = models.CharField(max_length=200)
    password = models.CharField(max_length=200)
    users = models.ManyToManyField(CustomUsers)

and this in admin.py:

from django.contrib import admin
from .models import Group, CustomUser
from django.contrib.auth.admin import UserAdmin

class GroupAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['group_name', 'group_password']}),
        ('Users', {'fields': ['users']}),
    ]

class CustomUserAdmin(UserAdmin):
    fieldsets = [
        (None,               {'fields': ['username', 'password']}),
        ('Groups', {'fields': ['groups']}),
    ]
    

admin.site.register(CustomUser, CustomUserAdmin)

admin.site.register(Group, GroupAdmin)

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477230

You specify a ManyToManyField in one of the two models, so for example:

class CustomUser(AbstractUser):
    groups = models.ManyToManyField(
        'Group',
        related_name='users'
        through='Participation'
    )

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

class Participation(models.Model):
    customuser = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)

The related_name=… parameter [Django-doc] specifies the name of the relation in reverse, so from Group to CustomUser, and you thus can rename this to users as is here the case. If you do not specify one, the default is modelname_set with modelname the name of the model (here CustomUser) in lowercase (so customuser).

Since your through=… model [Django-doc] only has two ForeignKeys to the two models, you do not need to create one, and you can simplify this further to:

class CustomUser(AbstractUser):
    groups = models.ManyToManyField(
        'Group',
        related_name='users'
    )

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

Upvotes: 2

Related Questions