variable - Anotación Django con filtro anidado.
django templates examples (3)
¿Es posible filtrar dentro de una anotación?
En mi mente algo como esto (que en realidad no funciona)
Student.objects.all().annotate(Count(''attendance'').filter(type="Excused"))
La tabla resultante tendría a cada estudiante con el número de ausencias justificadas. Mirar a través de los filtros de documentación solo puede ser antes o después de la anotación que no arrojaría los resultados deseados.
Una solución es esta
for student in Student.objects.all():
student.num_excused_absence = Attendance.objects.filter(student=student, type="Excused").count()
Esto funciona, pero hace muchas consultas, en una aplicación real, esto puede llegar a ser demasiado largo. Creo que este tipo de declaración es posible en SQL pero preferiría permanecer con ORM si es posible. Incluso intenté hacer dos consultas separadas (una para todos los estudiantes, otra para obtener el total) y las combiné con |. La combinación cambió el total :(
Algunos pensamientos después de leer respuestas y comentarios.
Resolví el problema de asistencia usando sql extra here .
- La publicación del blog de Timmy fue útil. Mi respuesta se basa en eso.
- La respuesta de hash1baby funciona pero parece tan compleja como sql. También requiere ejecutar sql y luego agregar el resultado en un bucle for. Esto es malo para mí porque estoy apilando muchas de estas consultas de filtrado juntas. Mi solución crea un gran conjunto de consultas con muchos filtros y adicionales y lo ejecuta todo al mismo tiempo.
- Si el rendimiento no es un problema, sugiero el bucle for. Es, con mucho, el más fácil de entender.
A partir de Django 1.8 puede hacer esto directamente en el ORM:
students = Student.objects.all().annotate(num_excused_absences=models.Sum(
models.Case(
models.When(absence__type=''Excused'', then=1),
default=0,
output_field=models.IntegerField()
)))
Respuesta adaptada de otra pregunta SO sobre el mismo tema.
No he probado el ejemplo anterior, pero logré algo similar en mi propia aplicación.
Tal vez esto funcione para usted:
excused = Student.objects.filter(attendance__type=''Excused'').annotate(abs=Count(''attendance''))
Debe filtrar los Estudiantes que está buscando primero para aquellos con ausencias justificadas y luego anotar el recuento de ellos.
Aquí hay un enlace a los documentos de agregación de Django donde se analiza el orden de filtrado.
Usted tiene razón: django no le permite filtrar los objetos relacionados que se están contando, sin aplicar también el filtro a los objetos primarios y, por lo tanto, excluir aquellos objetos primarios con objetos no relacionados después del filtrado.
Pero, en un poco de fuga de abstracción, puede contar grupos mediante una consulta de valores.
Por lo tanto, recojo las ausencias en un diccionario y las uso en un bucle. Algo como esto:
# a query for students
students = Students.objects.all()
# a query to count the student attendances, grouped by type.
attendance_counts = Attendence(student__in=students).values(''student'', ''type'').annotate(abs=Count(''pk''))
# regroup that into a dictionary {student -> { type -> count }}
from itertools import groupby
attendance_s_t = dict((s, (dict(t, c) for (s, t, c) in g)) for s, g in groupby(attendance_counts, lambda (s, t, c): s))
# then use them efficiently:
for student in students:
student.absences = attendance_s_t.get(student.pk, {}).get(''Excused'', 0)