2017-06-06 67 views
0

我已阅读thisthis,但它们不能解决我的问题,因为它们会将最终“计数”与数字进行比较硬编码。我想比较一个数字,它是配方本身所有成分的总和。Django filter queryset __in for * every * item in list(2.0)

让我们想象一下,我的冰箱里有一些配料,有他们的id(= id的数组)。我想看看我能做什么。我有我的模式是这样的:

class Ingredient(models.Model): 
    label = models.CharField(max_length=200, null=True, blank=True, 
          default=None) 
    description = models.TextField(null=True, blank=True, default=None) 


class Unit(models.Model): 
    label = models.CharField(max_length=200, null=True, blank=True, 
          default=None) 
    abbr = models.CharField(max_length=20, null=True, blank=True, 
          default=None) 


class IngredientUnit(models.Model): 
    ingredient = models.ForeignKey(Ingredient, null=False, blank=True) 
    unit = models.ForeignKey(Unit, null=False, blank=True) 
    measurable = models.BooleanField(default=True, null=False, blank=True) 
    is_int = models.BooleanField(default=True, null=False, blank=True) 
    value = models.FloatField(null=True, blank=True, default=0.0)  


class Recipe(models.Model): 
    label = models.CharField(max_length=200, null=True, blank=True, 
          default=None) 
    description = models.TextField(null=True, blank=True, default=None) 
    ingredients = models.ManyToManyField(IngredientUnit)  

我想做到这一点:“选择具有所有所有配方”的成分的PK阵列的成分。例如:“经典香草蛋糕”有以下成分:鸡蛋,通用面粉,发酵粉,小苏打,黄油,糖,香草,酪乳。如果有人失踪,“经典香草蛋糕”不应出现在查询结果中。相反,如果有更多的成分比那些要求,“经典香草蛋糕”应始终在最终的查询。

到目前为止,我已经完成了这项工作,但它不工作。

ingredient_ids = self.request.POST.getlist('ingredient[]', []) 
    if len(ingredient_ids): 
     recipes = Recipe.objects\ 
      .filter(ingredients__in=ingredient_ids)\ 
      .annotate(nb_ingredients=Count('ingredients'))\ 
      .filter(nb_ingredients=len(ingredient_ids)) 
     print([a for a in recipes]) 

的问题是,nb_ingredients=len(ingredient_ids)应该nb_ingredients=the number of the ingredients of the current recipe

我怎样才能做到这一点?

+0

问题还没有扣我呢。你想得到一个配方r与所有成分(例如ingredient_ids)? – Sagar

+0

我想你只是想在过滤器之前加上注释,以便每个食谱都注明它包含的成分数量。 – Brobin

+0

@Sagar我更新了我的问题:我的冰箱里有一些食材,里面有他们的id(= id的数组)。我想看看我能做什么。我想比较一个数字,它是配方本身所有成分的总和。 –

回答

0

我发现了!无法避免重复查询,但它像魅力一样工作。这里的溶液:对是配方的一部分成分

  • 第一,过滤器,对于每个配方(= group by计数的总成分的发现
  • 然后对所有的现有配方中,如果成分总数==之前发现的成分总量,然后就可以了,保留它。

感觉就像用大锤牛刀(即使第一查询过滤器,并消除了很多食谱),但它的作品,如果你有更好的解决办法,我是你的男人!

recipes = Recipe.objects \ 
    .annotate(found=Count('*'))\ 
    .filter(ingredients__in=ingredient_ids) 
for recipe in recipes: 
    a = Recipe.objects.annotate(total=Count('ingredients')).filter(
     pk=recipe.pk, total=recipe.found) 
    print("Recipe found:", str(a)) 

再举例来说,如果成分的ID是[1, 2, 3, 4, 5]你会得到这两个疑问:

SELECT "app_recipe"."id", "app_recipe"."label", "app_recipe"."description", 
    COUNT(*) AS "found" FROM "app_recipe" 
INNER JOIN "app_recipe_ingredients" 
ON ("app_recipe"."id" = "app_recipe_ingredients"."recipe_id") 
WHERE "app_recipe_ingredients"."ingredientunit_id" IN (1, 2, 3, 4, 5) 
GROUP BY "app_recipe"."id", "app_recipe"."label", "app_recipe"."description"; 

和第二环路将根据发现这样的食谱进行查询:

SELECT "app_recipe"."id", "app_recipe"."label", "app_recipe"."description", 
    COUNT("app_recipe_ingredients"."ingredientunit_id") AS "total" 
FROM "app_recipe" 
LEFT OUTER JOIN "app_recipe_ingredients" 
ON ("app_recipe"."id" = "app_recipe_ingredients"."recipe_id") 
WHERE "app_recipe"."id" = 1 
GROUP BY "app_recipe"."id", "app_recipe"."label", "app_recipe"."description" 
HAVING COUNT("app_recipe_ingredients"."ingredientunit_id") = 5; 
0

我想你可以尝试排除食谱中缺少配料。

Recipe.objects.exclude(~Q(ingredients__in=ingredient_ids)).distinct() 

测试它,它不工作。

从理论上讲,你想要什么:

的问题是,nb_ingredients = LEN(ingredient_ids)应 nb_ingredients =应达到当前配方

的成分的数在过滤器之前有一个注释,过滤器之后有一个注释(因为the order of annotation and filter matters)。

I.e.是这样的:

recipes = (Recipe.objects 
    .annotate(num_ingredients=Count('ingredients')) 
    .filter(ingredients__in=ingredient_ids) 
    .annotate(found_ingredients=Count('ingredients')) 
    .filter(num_ingredients=F('found_ingredients')) 
) 

但是当我测试了这一点,这是行不通的,因为ORM没有重用正确的从过滤器联接。这可能是某种错误,我只是在邮件列表中打开question

如果你使用Django 1.11,另一种方法是使用新的Subquery-expressions,你可以试试这个。

recipes = (Recipe.objects 
       .filter(ingredients__in=ingredient_ids) 
       .annotate(found_ingredients=Count('ingredients', distinct=True)) 
       .annotate(num_ingredients=Count(Subquery(
        Ingredient.objects.filter(recipe=OuterRef('pk')).only('pk')), 
        distinct=True) 
       ) 
       .filter(num_ingredients=F('found_ingredients')) 
) 
+0

你的最新建议非常聪明,应该有效。它不起作用吗? –

+0

@OlivierPons我认为它不工作,因为一个错误。我现在做了一些测试,它可以与'ForeignKeys'一起使用,但是在'M2M'字段失败。 – Todor

+0

我刚刚在django用户上启动了一个新线程,您可以查看其进度[这里](https://groups.google.com/forum/#!topic/django-users/RPU-PnygbLg)。 – Todor