python - template - Cómo anotar Count con una condición en un conjunto de consultas de Django
templates dirs django (2)
Usando Django ORM, ¿se puede hacer algo como
queryset.objects.annotate(Count(''queryset_objects'', gte=VALUE))
.
¿Me entiendes?
Aquí hay un ejemplo rápido para ilustrar una posible respuesta:
En un sitio web de Django, los creadores de contenido envían artículos y los usuarios habituales ven (es decir, leen) dichos artículos. Los artículos pueden publicarse (es decir, están disponibles para que todos los lean) o en modo borrador. Los modelos que representan estos requisitos son:
class Article(models.Model):
author = models.ForeignKey(User)
published = models.BooleanField(default=False)
class Readership(models.Model):
reader = models.ForeignKey(User)
which_article = models.ForeignKey(Article)
what_time = models.DateTimeField(auto_now_add=True)
Mi pregunta es: ¿Cómo puedo obtener todos los artículos publicados, ordenados por lectores únicos de los últimos 30 minutos? Es decir, quiero contar cuántas vistas distintas (únicas) obtuvo cada artículo publicado en la última media hora, y luego producir una lista de artículos ordenados por estas vistas distintas.
Lo intenté:
date = datetime.now()-timedelta(minutes=30)
articles = Article.objects.filter(published=True).extra(select = {
"views" : """
SELECT COUNT(*)
FROM myapp_readership
JOIN myapp_article on myapp_readership.which_article_id = myapp_article.id
WHERE myapp_readership.reader_id = myapp_user.id
AND myapp_readership.what_time > %s """ % date,
}).order_by("-views")
Esto provocó el error: error de sintaxis en o cerca de "01" (donde "01" era el objeto datetime dentro de extra). No es mucho para seguir.
Para django> = 1.8
Usar agregación condicional :
from django.db.models import Count, Case, When, IntegerField
Article.objects.annotate(
numviews=Count(Case(
When(readership__what_time__lt=treshold, then=1),
output_field=IntegerField(),
))
)
Explicación:
la consulta normal a través de sus artículos se
numviews
con el campo
numviews
.
Ese campo se construirá como una expresión CASE / WHEN, envuelta por Count, que devolverá 1 para los criterios de coincidencia de lectores y
NULL
para los criterios de coincidencia de lectores.
Count ignorará los valores nulos y solo contará valores.
Obtendrá ceros en los artículos que no se han visto recientemente y puede usar ese campo de
numviews
para ordenar y filtrar.
La consulta detrás de esto para PostgreSQL será:
SELECT
"app_article"."id",
"app_article"."author",
"app_article"."published",
COUNT(
CASE WHEN "app_readership"."what_time" < 2015-11-18 11:04:00.000000+01:00 THEN 1
ELSE NULL END
) as "numviews"
FROM "app_article" LEFT OUTER JOIN "app_readership"
ON ("app_article"."id" = "app_readership"."which_article_id")
GROUP BY "app_article"."id", "app_article"."author", "app_article"."published"
Si queremos rastrear solo consultas únicas, podemos agregar distinción en
Count
y hacer que nuestra cláusula
When
devuelva valor, queremos distinguir en.
from django.db.models import Count, Case, When, CharField, F
Article.objects.annotate(
numviews=Count(Case(
When(readership__what_time__lt=treshold, then=F(''readership__reader'')), # it can be also `readership__reader_id`, it doesn''t matter
output_field=CharField(),
), distinct=True)
)
Eso producirá:
SELECT
"app_article"."id",
"app_article"."author",
"app_article"."published",
COUNT(
DISTINCT CASE WHEN "app_readership"."what_time" < 2015-11-18 11:04:00.000000+01:00 THEN "app_readership"."reader_id"
ELSE NULL END
) as "numviews"
FROM "app_article" LEFT OUTER JOIN "app_readership"
ON ("app_article"."id" = "app_readership"."which_article_id")
GROUP BY "app_article"."id", "app_article"."author", "app_article"."published"
Para django <1.8 y PostgreSQL
Puede usar
raw
para ejecutar la instrucción SQL creada por las versiones más recientes de django.
Aparentemente, no existe un método simple y optimizado para consultar esos datos sin usar
raw
(incluso con
extra
hay algunos problemas con la inyección de la cláusula
JOIN
requerida).
Articles.objects.raw(''SELECT''
'' "app_article"."id",''
'' "app_article"."author",''
'' "app_article"."published",''
'' COUNT(''
'' DISTINCT CASE WHEN "app_readership"."what_time" < 2015-11-18 11:04:00.000000+01:00 THEN "app_readership"."reader_id"''
'' ELSE NULL END''
'' ) as "numviews"''
''FROM "app_article" LEFT OUTER JOIN "app_readership"''
'' ON ("app_article"."id" = "app_readership"."which_article_id")''
''GROUP BY "app_article"."id", "app_article"."author", "app_article"."published"'')
Para django> = 2.0 puede usar la
agregación condicional con un argumento de
filter
en las funciones agregadas:
from datetime import timedelta
from django.utils import timezone
from django.db.models import Count, Q # need import
Article.objects.annotate(
numviews=Count(
''readership__reader__id'',
filter=Q(readership__what_time__gt=timezone.now() - timedelta(minutes=30)),
distinct=True
)
)