假设我有一个包含店面中产品的Product
模型,以及一个包含产品图像的ProductImages
表,该表可以有零个或多个图像。这里有一个简化的例子:
class Product(models.Model):
product_name = models.CharField(max_length=255)
# ...
class ProductImage(models.Model):
product = models.ForeignKey(Product, related_name='images')
image_file = models.CharField(max_length=255)
# ...
当显示产品的搜索结果时,我想优先考虑与图像相关联的产品。我可以很容易地获得图像的数量:
from django.db.models import Count
Product.objects.annotate(image_count=Count('images'))
但这并不是我真正想要的。我想用一个布尔字段have_images
来注释它,指示产品是否有一个或多个图像,这样我就可以按这个进行排序:
Product.objects.annotate(have_images=(?????)).order_by('-have_images', 'product_name')
我该怎么做?谢谢
我最终找到了一种使用django 1.8的新条件表达式来实现这一点的方法:
from django.db.models import Case, When, Value, IntegerField
q = (
Product.objects
.filter(...)
.annotate(image_count=Count('images'))
.annotate(
have_images=Case(
When(image_count__gt=0,
then=Value(1)),
default=Value(0),
output_field=IntegerField()))
.order_by('-have_images')
)
这就是为什么我最终找到了从1.7升级到1.8的动力。
与Django 1.11一样,可以使用Exists
。以下示例来自Exists文档:
>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef('pk'),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
使用条件表达式并将outputfield强制转换为BooleanField
Product.objects.annotate(image_count=Count('images')).annotate(has_image=Case(When(image_count=0, then=Value(False)), default=Value(True), output_field=BooleanField())).order_by('-has_image')
阅读有关extra的文档
qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
测试它的工作
但是这个字段的order_by
或where
(过滤器)不适合我(Django 1.8)0o:
如果您需要使用一些新的通过extra()包含的字段或表使用orderby参数转换为extra(),然后传入一系列字符串。这些字符串应该是模型字段(如上的普通order_by()方法querysets),形式为table_name.column_name或列,该列是您在select参数中指定给extra()的。
qs = qs.extra(order_by = ['-has_images'])
qs = qs.extra(where = ['has_images=1'])
FieldError:无法将关键字"has_images"解析到字段中。
我发现https://code.djangoproject.com/ticket/19434仍然打开。
所以,如果你像我一样有这样的麻烦,你可以使用原始
如果性能很重要,我的建议是添加hasPictures
布尔字段(作为editable=False
)
然后通过ProductImage
模型信号(或重写save
和delete
方法)保持正确值
优点:
- 索引友好
- 性能更好。避免联接
- 数据库不可知
- 编码它将把你的django技能提升到一个新的水平
当您必须用一些过滤器来注释存在时,可以使用Sum
注释。例如,如果images
中有任何GIF,则以下注释:
Product.objects.filter(
).annotate(
animated_images=Sum(
Case(
When(images__image_file__endswith='gif', then=Value(1)),
default=Value(0),
output_field=IntegerField()
)
)
)
这实际上会对它们进行计数,但任何Python if product.animated_images:
都将像布尔值一样工作。