Django的ManyToMany查询,查找在一组`categories`中的所有`things`。

huangapple go评论86阅读模式
英文:

Django Many2Many query to find all `things` in a group of `categories`

问题

Here's the translation of the provided content without code:

给定这些Django模型:

  • Thing(物品)模型:

    • 属性:name(名称)
  • Category(类别)模型:

    • 属性:name(名称)
    • 属性:things(与Thing模型的多对多关系,关联名称为“categories”)

如果要查找所有位于一组给定类别中的所有物品,可以使用以下方法:

假设有5个类别,其ID为1、2、3、4、5。

并且假设有一组类别子集:

  1. my_cats = Category.objects.filter(id__in=[2,3])

要查找位于类别2和3中的所有物品,可以使用以下查询:

  1. # 查找位于类别2或3中的所有物品
  2. things = Thing.objects.filter(categories__in=[2, 3])

上述查询将返回位于类别2或3中的所有物品,但不会限制为同时位于两个类别中的物品。

要查找同时位于类别2和3中的物品,可以尝试以下查询:

  1. # 查找同时位于类别2和3中的物品
  2. things = Thing.objects.filter(categories=2).filter(categories=3)

或者,您可以使用Q对象来实现相同的目标:

  1. from django.db.models import Q
  2. # 使用Q对象查找同时位于类别2和3中的物品
  3. things = Thing.objects.filter(Q(categories=2) & Q(categories=3))

上述查询将返回位于类别2和3中的物品。但是,请确保使用.distinct()来消除重复项。

希望这可以帮助您解决问题!如果需要更多帮助,请随时提问。

英文:

Given these Django models:

  1. from django.db import models
  2. class Thing(models.model):
  3. name = models.CharField('Name of the Thing')
  4. class Category(models.model):
  5. name = models.CharField('Name of the Category')
  6. things = models.ManyToManyField(Thing, verbose_name='Things', related_name='categories')

Note that all the categories a Thing is in can be found by:

  1. thing = Thing.objects.get(id=1) # for example
  2. cats = thing.categories.all() # A QuerySet

I'm really struggling to build a query set that returns all Things in all of a given set of Categories.

Let's say we have 5 categories, with IDs 1, 2, 3, 4, 5.

And say I have a subset of categories:

  1. my_cats = Category.objects.filter(id__in=[2,3])

I want to find all Things that are in say categories, 2 AND 3.

I can find all Things in category 2 OR 3 easily enough. For example this:

  1. Thing.objects.filter(categories__in=[2,3])

seems to return just that, Things in category 2 OR 3.

And something like:

  1. Thing.objects.filter(Q(categories=2)|Q(categories=3))

also, but this returns nothing:

  1. Thing.objects.filter(Q(categories=2)&Q(categories=3))

I might envisage something like:

  1. Thing.objects.filter(categories__contains=[2,3])

but of course that's a dream as contains operates on strings not ManyToMany sets.

Is there a standard trick here I'm missing?

I spun up a sandbox here to test and demonstrate:

https://codesandbox.io/p/sandbox/django-m2m-test-cizmud

It implements this simple pair of models and populates the database with a small set of things and categories and tests the queries, here's the latest state of it:

  1. print("Database contains:")
  2. for thing in Thing.objects.all():
  3. print(
  4. f"\t{thing.name} in categorties {[c.id for c in thing.categories.all()]}")
  5. print()
  6. # This works fine. Prints:
  7. # Cat1 OR Cat2: ['Thing 1', 'Thing 5', 'Thing 4']
  8. things = Thing.objects.filter(
  9. Q(categories=1) | Q(categories=2)).distinct()
  10. print(f"Cat1 OR Cat2: {[t.name for t in things]}")
  11. # We would love this to return Thing4 and thing5
  12. # The two things in the test data set that are in
  13. # Category 2 and in Category 3.
  14. # But this does not work. It prints:
  15. # Cat2 AND Cat3: []
  16. # because
  17. # What does yield ['Thing 4', 'Thing 5']?
  18. print("\nAiming to to get: ['Thing 4', 'Thing 5']")
  19. things = Thing.objects.filter(
  20. Q(categories=2) & Q(categories=3)).distinct()
  21. print(f"Try 1: Cat2 AND Cat3: {[t.name for t in things]}")
  22. # This also fails, producing an OR not AND
  23. things = Thing.objects.filter(categories__in=[2, 3]).distinct()
  24. print(f"Try 2: Cat2 AND Cat3: {[t.name for t in things]}")
  25. # Also fails
  26. things = Thing.objects.filter(categories__in=[2, 3])\
  27. .filter(categories=2).distinct()
  28. print(f"Try 3: Cat2 AND Cat3: {[t.name for t in things]}")
  29. # Also fails
  30. things = Thing.objects.filter(categories__in=[2, 3], categories=2)\
  31. .distinct()
  32. print(f"Try 4: Cat2 AND Cat3: {[t.name for t in things]}")

and it's output:

  1. Database contains:
  2. Thing 1 in categorties [1, 2]
  3. Thing 2 in categorties [3, 4]
  4. Thing 3 in categorties [5]
  5. Thing 4 in categorties [2, 3]
  6. Thing 5 in categorties [1, 2, 3]
  7. Cat1 OR Cat2: ['Thing 1', 'Thing 5', 'Thing 4']
  8. Aiming to to get: ['Thing 4', 'Thing 5']
  9. Try 1: Cat2 AND Cat3: []
  10. Try 2: Cat2 AND Cat3: ['Thing 1', 'Thing 4', 'Thing 5', 'Thing 2']
  11. Try 3: Cat2 AND Cat3: ['Thing 1', 'Thing 4', 'Thing 5']
  12. Try 4: Cat2 AND Cat3: ['Thing 1', 'Thing 4', 'Thing 5']

I guess if I can work it out in SQL, we can write us a custom lookup:

https://docs.djangoproject.com/en/4.2/howto/custom-lookups/

But why do I think this must already have been written? How this be such a unique and new use case?

答案1

得分: 1

Sure, here's the translated code part:

  1. Thing.objects.annotate(cat_count=Count('id', filter=Q(categories__in=[2, 3]))).\
  2. filter(cat_count__gte=2).values('id', 'cat_count')

The comment at the end remains in English.

英文:
  1. Thing.objects.annotate(cat_count=Count('id', filter=Q(categories__in=[2, 3]))).\
  2. filter(cat_count__gte=2).values('id', 'cat_count')

Try it. cat_count__gte=2 because the two numbers are 2, 3.

huangapple
  • 本文由 发表于 2023年5月7日 18:13:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76193289.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定