Home > Software design >  Django many-to-many relations
Django many-to-many relations

Time:04-30

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?

CodePudding user response:

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)

CodePudding user response:

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)
  • Related