我正在努力创建鸡尾酒食谱应用程序作为学习练习。
我试图通过Django的Rest Framework创建一个过滤器,该过滤器通过查询参数(?= ingredients_exclusive = 1,3,4)接受一串成分ID,然后搜索具有所有这些成分的所有配方。我想搜索“所有含有朗姆酒和石榴汁糖浆的鸡尾酒”,然后分别搜索“所有含有朗姆酒的鸡尾酒和所有含有龙胆糖的鸡尾酒。”
我的应用程序中的三个模型是Recipes,RecipeIngredients和IngredientTypes。食谱(老式)具有多种RecipeIngredients(2盎司威士忌),并且RecipeIngredients均为成分类型(威士忌)。最终,我将决定将RecipeIngredient更改为a through model,具体取决于我决定采取的程度。
列表的长度可以可变,因此我不能仅将过滤器功能链接在一起。我必须遍历id列表,然后构建一个Q()。
但是,我遇到了一些问题。通过Django Shell,我做到了:
>>> x = Recipe.objects.all()
>>> q = Q(ingredients__ingredient_type=3) & Q(ingredients__ingredient_type=7)
>>> x.filter(q)
<QuerySet []>
>>> x.filter(ingredients__ingredient_type=3).filter(ingredients__ingredient_type=7)
<QuerySet [<Recipe: Rum and Tonic>]>
所以这是我的问题:与两个查询进行“与”运算的Q对象为什么与同一对象的链接过滤器不同?
我已经阅读了Django文档中的“ Complex lookups with Q objects”,但似乎无济于事。
仅供参考,这是我在Filters.py中的过滤器。
此命令的“ OR”版本正常运行:
class RecipeFilterSet(FilterSet):
ingredients_inclusive = django_filters.CharFilter(method='filter_by_ingredients_inclusive')
ingredients_exclusive = django_filters.CharFilter(method='filter_by_ingredients_exclusive')
def filter_by_ingredients_inclusive(self, queryset, name, value):
ingredients = value.split(',')
q_object = Q()
for ingredient in ingredients:
q_object |= Q(ingredients__ingredient_type=ingredient)
return queryset.filter(q_object).distinct()
def filter_by_ingredients_exclusive(self, queryset, name, value):
ingredients = value.split(',')
q_object = Q()
for ingredient in ingredients:
q_object &= Q(ingredients__ingredient_type=ingredient)
return queryset.filter(q_object).distinct()
class Meta:
model = Recipe
fields = ()
我还在下面添加了我的模型:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
class IngredientType(models.Model):
name = models.CharField(max_length=256)
CATEGORY_CHOICES = (
('LIQUOR', 'Liquor'),
('SYRUP', 'Syrup'),
('MIXER', 'Mixer'),
)
category = models.CharField(
max_length=128, choices=CATEGORY_CHOICES, default='MIXER')
def __str__(self):
return self.name
class Recipe(models.Model):
name = models.CharField(max_length=256)
def __str__(self):
return self.name
class RecipeIngredient(models.Model):
ingredient_type = models.ForeignKey(IngredientType, on_delete=models.CASCADE, related_name="ingredients")
quantity = models.IntegerField(default=0)
quantity_type = models.CharField(max_length=256)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name="ingredients")
@property
def ingredient_type_name(self):
return self.ingredient_type.name
@property
def ingredient_type_category(self):
return self.ingredient_type.category
def __str__(self):
return f'{self.quantity}{self.quantity_type} of {self.ingredient_type}'
任何帮助将不胜感激!
答案 0 :(得分:1)
Spanning multi-valued relationships中描述了两种filter()
方法之间的区别:
单个
filter()
调用中的所有内容将同时应用,以过滤出符合所有这些要求的项目。...对于多值关系,它们适用于链接到主模型的任何对象,而不一定适用于那些是由先前的filter()
通话选择的。
文档中的示例更加清楚。我会根据您的问题进行重写:
要选择同时包含
3
类型和7
类型成分的所有食谱,我们将编写:Recipe.objects.filter(ingredients__ingredient_type=3, ingredients__ingredient_type=7)
在您的模型中这当然是不可能的,因此这将返回一个空的查询集,就像您的Q
示例与AND
一样。
要选择所有包含
3
类型的配料以及{strong>7
类型的配料的食谱,我们将编写:Recipe.objects.filter(ingredients__ingredient_type=3).filter(ingredients__ingredient_type=7)
这不是特别直观,但是他们需要一种区分这两种情况的方法,这就是他们提出的。
回到您的问题,可以使用OR
运算符来简化in
的情况:
Recipe.objects.filter(ingredients__ingredient_type__in=[3, 7]).distinct()
AND
情况很复杂,因为它是一个涉及多行的条件。一种简单的方法是仅采用上述OR
版本,然后在Python中对其进行进一步处理,以找到具有所有要素的子集。
一种可行的查询方法包括使用Count
进行注释。这未经测试,但是类似:
Recipe.objects.annotate(num_ingredients=Count("ingredients",
filter=Q(ingredients__ingredient_type__in=[3, 7]))
.filter(num_ingredients=2)
答案 1 :(得分:1)
针对Django 1.11+的AND案例的另一种方法是使用相对较新的QuerySet
intersection()方法。根据文档,此方法:
使用SQL的 INTERSECT 运算符返回两个或更多查询集的共享元素。
因此,给定IngredientType
主键的任意列表,您可以为每个pk创建一个filter()
查询(我们将其称为subqueries
),然后传播该列表({{1 }}运算符)添加到*
方法中。
像这样:
intersection()
我在其中添加了# the base `QuerySet` and `IngredientType` pks to filter on
queryset = Recipe.objects.all()
ingredient_type_pks = [3, 7]
# build the list of subqueries
subqueries = []
for pk in ingredient_type_pks:
subqueries.append(queryset.filter(ingredients__ingredient_type__pk=pk))
# spread the subqueries into the `intersection` method
return queryset.intersection(*subqueries).distinct()
只是为了安全起见并避免重复结果,但实际上我不确定是否有必要。以后必须测试和更新此帖子。