英文:
Django ORM get object based on many-to-many field
问题
I have model with m2m field users
:
class SomeModel(models.Model):
objects = SomeModelManager()
users = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
My goal is to get instance of this model where set of instance users matches given queryset (it means that every user from queryset should be in m2m relation and no other users).
If I do
obj = SomeModel.objects.get(users=qs)
I get
ValueError: The QuerySet value for an exact lookup must be limited to one result using slicing.
And I totally understand the reason for such error. So the next thing I did was creating a custom Queryset class for this model to override .get()
behavior:
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())
return qs.get(*args, **kwargs)
class SomeModelManager(models.Manager.from_queryset(SomeModelQueryset)):
...
So what I try to do is to filter only objects with matching users and make sure that the number of users is the same as in the queryset.
But I don't like the current version of code. users__in
adds an instance to the queryset each time it finds a match, so it results in n
occurrences for each object (n
- number of m2m users for a specific object). Count
in .annotate()
counts unique user IDs for each occurrence and then produces a single object with all counts combined. So for each object, there are n
occurrences with a count of n
, and the resulting object will have a count of n**2
.
Is there a way to rewrite this annotate+filter to produce a count=n, not n^2?
英文:
I have model with m2m field users
:
class SomeModel(models.Model):
objects = SomeModelManager()
users = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
My goal is to get instance of this model where set of instance users matches given queryset (it means that every user from queryset should be in m2m relation and no other users).
If I do
obj = SomeModel.objects.get(users=qs)
I get
ValueError: The QuerySet value for an exact lookup must be limited to one result using slicing.
And I totaly understand the reason of such error, so the next thing I did was creating a custom Queryset class for this model to override .get()
behavior:
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)):
...
So what I try to do is to filter only objects with matching users and make sure that amount of users is the same as in queryset.
But I don't like current version of code. users__in
adds instance to queryset each time it finds match, so it results in n
occurrences for each object (n
- number of m2m users for specific object). Count
in .annotate()
counts unique users ids for each occurrence and then produces single object with all counts combined. So for each object there are n
occurrences with count n
, and the resulting object will have count n**2
.
Is there a way to rewrite this annotate+filter to produce count=n, not n^2 ?
答案1
得分: 1
你可以使用 __in
过滤器来检查,然后确定用户数量的计数:
from django.db.models import Q
my_users = User.objects.none() # 一些用户的查询集
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))
请注意,这部分代码中的注释和代码块并没有进行翻译。
英文:
You can check with an __in
filter and then determine the count of the number of users:
<pre><code>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(
<b>nusers=Count('users'), nmatch=Count('users', filter=Q(users__pk__in=my_users))</b>
).filter(<b>nusers=len(my_users), nmatch=len(my_users)</b>)</code></pre>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论