Supposons que j'ai un modèle Product
avec des produits dans une vitrine et un tableau ProductImages
avec des images du produit, qui peuvent avoir zéro ou plusieurs images. Voici un exemple simplifié:
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)
# ...
Lors de l'affichage des résultats de recherche de produits, je souhaite hiérarchiser les produits auxquels des images sont associées. Je peux facilement obtenir le nombre d'images:
from Django.db.models import Count
Product.objects.annotate(image_count=Count('images'))
Mais ce n'est pas vraiment ce que je veux. Je voudrais l'annoter avec un champ booléen, have_images
, indiquant si le produit a une ou plusieurs images, afin que je puisse trier par ce biais:
Product.objects.annotate(have_images=(?????)).order_by('-have_images', 'product_name')
Comment puis je faire ça? Merci!
J'ai finalement trouvé un moyen de faire cela en utilisant le nouveau expressions conditionnelles de 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')
)
Et c'est comme ça que j'ai finalement trouvé une incitation à passer de 1,7 à 1,8.
Lire la documentation sur extra
qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
Testé cela fonctionne
Mais order_by
ou where
(filtre) de ce champ ne me convient pas (Django 1.8) 0o:
Si vous avez besoin de commander le jeu de requêtes résultant en utilisant certains des nouveaux les champs ou les tables que vous avez inclus via extra () utilisent order_by paramétrez sur extra () et transmettez une séquence de chaînes. Ces cordes doivent être des champs de modèle (comme dans la méthode normale order_by () sur querysets), de la forme nom_table.nom_colonne ou un alias pour un colonne que vous avez spécifiée dans le paramètre select à extra ().
qs = qs.extra(order_by = ['-has_images'])
qs = qs.extra(where = ['has_images=1'])
FieldError: impossible de résoudre le mot clé 'has_images' dans le champ.
J'ai trouvé https://code.djangoproject.com/ticket/19434 toujours ouvert.
Donc, si vous avez des problèmes comme moi, vous pouvez utiliser raw
Si les performances comptent, ma suggestion est d'ajouter le champ hasPictures
boolean (sous la forme editable=False
)
Puis conservez la valeur correcte dans ProductImage
signaux du modèle (ou en écrasant les méthodes save
et delete
Avantages:
Utilisez des expressions conditionnelles et convertissez outputfield en 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')
A partir de Django 1.11, il est possible d'utiliser Exists
. L'exemple ci-dessous provient de 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))
Lorsque vous devez annoter l'existence avec certains filtres, vous pouvez utiliser l'annotation Sum
. Par exemple, après annote s'il existe des GIF dans images
:
Product.objects.filter(
).annotate(
animated_images=Sum(
Case(
When(images__image_file__endswith='gif', then=Value(1)),
default=Value(0),
output_field=IntegerField()
)
)
)
Cela les comptera en réalité, mais tout if product.animated_images:
Pythonic fonctionnera comme il était booléen.