queryset - models py python
Django: ¿cómo anotar queryset con el recuento del campo ForeignKey filtrado? (4)
Evite extra
y raw
siempre que sea posible. Los documentos de agregación tienen casi este caso de uso:
Directamente de los documentos:
# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count(''book''))
>>> pubs
[<Publisher BaloneyPress>, <Publisher SalamiPress>, ...]
>>> pubs[0].num_books
73
Por lo tanto, para modificar esto para su ejemplo particular:
depts = Department.objects.
filter(product__review__time__range=["2012-01-01", "2012-01-08"]).
annotate(num_products=Count(''product''))
La función llama a líneas separadas solo para facilitar la lectura y debe moverlas de forma acorde. No lo he probado, pero creo que debería funcionar.
Pregunta de principiante de Django :)
Tengo los siguientes modelos: cada revisión es para un producto y cada producto tiene un departamento:
class Department(models.Model):
code = models.CharField(max_length=16)
class Product(models.Model):
id = models.CharField(max_length=40, primary_key=True, db_index=True)
dept = models.ForeignKey(Department, null=True, blank=True, db_index=True)
class Review(models.Model):
review_id = models.CharField(max_length=32, primary_key=True, db_index=True)
product = models.ForeignKey(Product, db_index=True)
time = models.DateTimeField(db_index=True)
Me gustaría hacer una consulta de Django para un rango de fechas (01-01-2012 a 2012-01-08) y devolver una lista de todos los departamentos, anotados con la ID del departamento, y la cantidad de productos de ese departamento que fueron revisados durante ese rango de fechas.
Esto está friendo mi cerebro un poco :)
Puedo obtener todas las críticas para un rango de tiempo:
reviews = Review.filter(time__range=["2012-01-01", "2012-01-08"])
Entonces, supongo que cada revisión tiene un campo de producto y cada uno de esos productos tiene un código de departamento. Pero, ¿cómo puedo agruparlos por producto y código, con recuentos e ID de departamento?
Alternativamente, ¿es mejor solicitar los departamentos y, a continuación, anotarlos con recuentos de productos?
He tenido que hacer un par de consultas similares en los últimos días y la forma más fácil de usar la función de consulta extra
para anotar cada objeto en su queryset con un conteo filtrado de los productos:
start = .. # need to be formatted correctly
end = ...
departments = Departments.objects.all().extra(select = {
''product_count'' : """ SELECT COUNT(*) FROM appname_department
JOIN appname_product
ON appname_product.dept_id = appname_department.id
JOIN appname_review
ON appname_review.product_id = appname_product.id
WHERE appname_review.time BETWEEN %s AND %s
"""
}, params=[start, end])
y
{% for department in departments %}
{{ department.product_count }}
{% endfor %}
Los documentos para la agregación https://docs.djangoproject.com/en/dev/topics/db/aggregation/#cheat-sheet
Probablemente haya una forma de usar agregado o anotación, pero prefiero esto:
departments = Department.objects.all()
for dept in departments :
# Get the number of reviewed products for a given range and department
num_products = dept.product_set.filter(review__time__range=["2012-01-01", "2012-01-08"]).count()
si lo necesita absolutamente como una función del modelo:
class Department(models.Model) :
...
def num_products(self, start_date, end_date) :
return self.product_set.filter(review__time__range=[start_date, end_date]).count()
EDITAR
Creo que si hicieras una consulta en bruto (algo como esto)
sql = """SELECT COUNT(Product.*) as num_products, Department.*
FROM Department
LEFT OUTER JOIN Product ON Product.department = Department.id
LEFT OUTER JOIN Review ON Product.id = Review.product
WHERE Review.time BETWEEN "2012-01-01" AND "2012-01-08"
GROUP BY Department.id"""
Department.objects.raw(sql)
y luego num_products será un atributo en cada instancia de Dept en los resultados.
es posible que necesite jugar un poco con los nombres de campo + tabla
Tengo la misma situación con un modelo de datos similar.
Mi solución fue como:
Department.objects /
.extra(where=["<review_table_name.time_field> BETWEEN <time1> AND <time2> "])/
.annotate(num_products=Count(''product__review__product_id''))