这应该会给你你想要的:
list(Item.objects.filter(surface_color__in=[1,3]).distinct().annotate(num_colors=Count('surface_color')).order_by('-num_colors')) + list(Item.objects.exclude(surface_color__in=[1,3]).distinct())
它需要两个查询,但您不需要为每个项目单独查询。
用 cmets 分解相同的逻辑:
# Make sure you import Count somewhere in the file first
from django.db.models import Count
# id of Color objects to match
color_ids_to_match = [1,3]
#-----------------------------------------------------------------------------------------------------------------------
# Item objects with Color objects matching `color_ids_to_match`
# - The `surface_color` field has **at least one** Color object with a matching id from `color_ids_to_match`
# - This matches Items #4, #1, #2 from your sample data
items_with_matching_color = Item.objects.filter(surface_color__in=color_ids_to_match).distinct()
# Adds a select field to the query to track the number of surface_color objects matched
# - **NOT the total** number of Color objects associated through `surface_color`
items_with_matching_color = items_with_matching_color.annotate(num_colors=Count('surface_color'))
# Order by that field in descending order
# - Note that the order is undetermined between Item objects with the same `num_colors` value
items_with_matching_color = items_with_matching_color.order_by('-num_colors')
#-----------------------------------------------------------------------------------------------------------------------
# Item objects **NOT** associated with any Color objects with a id in `color_ids_to_match`
# - This matches Item #3 from your sample data
items_without_matching_color = Item.objects.exclude(surface_color__in=color_ids_to_match).distinct()
# Optional - Sets the num_colors field to 0 for this queryset in case you need this information
from django.db.models.expressions import RawSQL
items_without_matching_color = items_without_matching_color.annotate(num_colors=RawSQL('0', ()))
#-----------------------------------------------------------------------------------------------------------------------
# Convert the two querysets to lists and concatenate them
# - This is necessary because a simple queryset union such as `items_with_matching_color | items_without_matching_color`
# does not maintain the order between the two querysets
ordered_items = list(items_with_matching_color) + list(items_without_matching_color)
使用您的样本数据输出:
>>> ordered_items
[<Item: 1: <QuerySet [<Color: 1>, <Color: 2>, <Color: 3>]>>, <Item: 4: <QuerySet [<Color: 1>, <Color: 3>]>>, <Item: 2: <QuerySet [<Color: 1>, <Color: 2>]>>, <Item: 3: <QuerySet [<Color: 2>]>>]
请注意,此处第 1 项在第 4 项之前。您提到两者之间的顺序无关紧要,因为它们都匹配相同数量的 Color 对象。您可以向order_by 添加另一个参数,以根据您的需要进一步优化排序。
获取匹配颜色对象的数量:
<Item: 1: <QuerySet [<Color: 1>, <Color: 2>, <Color: 3>]>>
>>> ordered_items[0].num_colors
2
>>> ordered_items[1]
<Item: 4: <QuerySet [<Color: 1>, <Color: 3>]>>
>>> ordered_items[1].num_colors
2
>>> ordered_items[2]
<Item: 2: <QuerySet [<Color: 1>, <Color: 2>]>>
>>> ordered_items[2].num_colors
1
>>> ordered_items[3]
<Item: 3: <QuerySet [<Color: 2>]>>
>>> ordered_items[3].num_colors
0