Django ORM基于多对多字段获取对象

wn9m85ua  于 2023-05-30  发布在  Go
关注(0)|答案(1)|浏览(164)

我有一个m2m字段users的模型:

class SomeModel(models.Model):
    
    objects = SomeModelManager()

    users = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)

我的目标是得到这个模型的示例,其中示例用户集匹配给定的查询集(这意味着查询集中的每个用户都应该是m2m关系,没有其他用户)。
如果我做了

obj = SomeModel.objects.get(users=qs)

我明白

ValueError: The QuerySet value for an exact lookup must be limited to one result using slicing.

我完全理解这样的错误的原因,所以我做的下一件事是为这个模型创建一个自定义的Queryset类来覆盖.get()行为:

class SomeModelQueryset(QuerySet):

    def get(self, *args, **kwargs):
        qs = super()  # Prevent recursion
        if (users := kwargs.pop('users', None)) is not None:
            qs = qs.annotate(count=Count('users__id')).filter(users__in=users, count=users.count()**2)
        return qs.get(*args, **kwargs)

class SomeModelManager(models.Manager.from_queryset(SomeModelQueryset)):
    ...

因此,我尝试只过滤具有匹配用户的对象,并确保用户数量与queryset中的相同。
但我不喜欢当前版本的代码。users__in在每次找到匹配时都会将示例添加到查询集,因此它会导致每个对象出现n次(n-特定对象的m2m用户数)。.annotate()中的Count对每次出现的唯一用户ID进行计数,然后将所有计数合并生成单个对象。因此,对于每个对象,都有n次出现,计数为n,结果对象的计数为n**2
有没有办法重写这个annotate+filter来生成count=n,而不是n^2?

gopyfrb3

gopyfrb31#

您可以使用__in过滤器进行检查,然后确定用户数的计数:

from django.db.models import Q

my_users = User.objects.none()  # some queryset of Users
my_users = {user.pk for user in my_users}
obj = SomeModel.objects.alias(
    nusers=Count('users'), nmatch=Count('users', filter=Q(users__pk__in=my_users))
).filter(nusers=len(my_users), nmatch=len(my_users))

相关问题