Django对ManyToManyField中的成员集强制执行唯一约束

时间:2017-10-06 19:54:38

标签: python django python-3.x django-models

我在Django工作,而且在我的数据模型上执行特定约束时遇到了一些问题。我有一个由一组成员组成的模型。

class Member:
    name = models.CharField(max_length=100)

class Team:
    members = models.ManyToManyField(Member)

我想强制执行约束,以便对于任何给定的团队,成员集是唯一的。也就是说,允许以下团队:

team_a: {A, B, C}
team_b: {A, B, D}

但是不允许team_c: {C, B, A},因为成员集与team_a相同。

如果我尝试添加team_c(或具有相同成员集的任何其他团队),我如何强制执行此约束以便引发错误?

我考虑过为每个成员设置一个外键,然后使用unique_together,但这不起作用,因为我希望模型不知道在成员中排序(因为那里)不是数据中固有的排序)。我认为这可能是the m2m_changed signal的工作,但我还没有能够让它正常工作。

1 个答案:

答案 0 :(得分:1)

嗯,没有直接的方法,但最重要的是,这可能有效。

一组成员与其他团队成员发生冲突的唯一可能性是,当您要添加的成员存在于其他团队中时,首先我们需要获取已添加用户的团队:

# 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)

干运行

假设您有以下三支球队:

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

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

    Team C:
        3: member
        2: member

现在我们要将成员id: 1添加到团队C中。哪个应该避免,因为它会创建与团队A相同的用户集,让我们运行上面的代码来查看它是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

场景一次添加完整的团队

假设您要立即添加完整的团队C,因此您已经知道该团队中将拥有哪些成员,在这种情况下,流程几乎相似,但首先会创建team_c_members_set过滤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)