店頭に製品があるProduct
モデルと、0個以上の画像を持つことができる製品の画像を持つ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'))
しかし、それは実際には私が望むものではありません。製品に1つ以上の画像があるかどうかを示すブールフィールド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にアップグレードするインセンティブをようやく見つけた方法です。
条件式を使用し、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')
Django 1.11から)Exists
を使用することができます。以下の例は Exists documentation からのものです。
>>> 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))
extra に関するドキュメントを読む
qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
動作確認済み
だが order_by
またはこのフィールドのwhere
(filter)は私には向かない(Django 1.8)0o:
Extra()でインクルードした新しいフィールドまたはテーブルの一部を使用して結果のクエリセットを順序付ける必要がある場合は、order_byパラメーターをextra()に使用して、一連の文字列を渡します。これらの文字列は、table_name.column_nameの形式のモデルフィールド(クエリセットの通常のorder_by()メソッドのように)、またはextra()のselectパラメーターで指定した列のエイリアスである必要があります。
qs = qs.extra(order_by = ['-has_images'])
qs = qs.extra(where = ['has_images=1'])
FieldError:キーワード「has_images」をフィールドに解決できません。
https://code.djangoproject.com/ticket/19434 がまだ開いていることがわかりました。
したがって、私のような問題が発生した場合は、 raw を使用できます
パフォーマンスが重要な場合は、hasPictures
ブールフィールドを追加することをお勧めします(editable=False
)
次に、ProductImage
モデル信号 (またはsave
およびdelete
メソッドを上書き)で正しい値を維持します
利点:
いくつかのフィルターで存在に注釈を付ける必要がある場合、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()
)
)
)
これは実際にそれらをカウントしますが、Pythonic if product.animated_images:
はブール値と同じように機能します。