[Django]-Django enforcing unique constraint on set of members in ManyToManyField

2👍

Hmm there is no direct way, but on top of my head this might work.

The only possibility of a set of members clash with another team members is only when the member which you are about to add exists in other teams, so first we need to fetch those teams in which user is already added:

# fetch teams where user is already added
teams = Team.objects.filter(members__id=user.id)

will_clash = False
if teams:
    # prepare list of tuples of sorted members id for each team
    members_set = [tuple(team.members.values_list('id', flat=True).order_by('id')) for team in teams]

    # we know already in which team we want to add lets say it is `team_c`
    team_c_members_set = tuple(sorted(list(team_c.members.values_list('id', flat=True)) + [user.id]))

    if team_c_members_set in members_set:
        # adding current `user` is going to create a clash
        will_clash = True

if not will_clash:
    # all good lets add the user in team
    team_c.members.add(user)

Dry Run:

Lets say you have following three teams:

    Team A:
        1: member
        2: member
        3: member

    Team B:
        1: member
        2: member
        4: member

    Team C:
        3: member
        2: member

Now we want to add member id: 1 into team C. Which should be avoided as that will create same set of users as in team A, lets run the above code to see if that is True:

# fetch teams where user is already added
teams = [A, B]  # user already there

# prepare list of tuples of sorted members id for each team
members_set = [(1, 2, 3), (1, 2, 4)]

# we know already in which team we want to add lets say it is `team_c` so it will become
team_c_members_set = (1, 2, 3)  # tuple(sorted([3, 2] + [1]))

team_c_members_set in members_set  # <-- True

Scenario adding full team at once

Lets say you are adding full team C at once, so you already know which members you will have in that team, in that case process is almost similar but team_c_members_set is created first to be used in filtering teams:

users_to_add = [user_a, user_b, user_c]
team_c_members_set = tuple(sorted([u.id for u in users_to_add])
teams = Team.objects.filter(members__id__in=team_c_members_set).distinct()
will_clash = False
if teams:
    # prepare list of tuples of sorted members id for each team
    members_set = [tuple(team.members.values_list('id', flat=True).order_by('id')) for team in teams]
    if team_c_members_set in members_set:
        will_clash = True

if not will_clash:
    # all good lets add the users in team
    team_c.members.add(*users_to_add)

Leave a comment