Хорошо, я добавил год, но вы можете удалить его, если он вам не нужен.
Materiale.objects
.values_list('product__nome', 'date__year', 'date__month')
.annotate(totale=Sum(F('quantity') * F('price')))
.values_list('product__nome', 'date__year', 'date__month', 'totale')
Итак, первый .values_list включает группу, аннотирование добавляет сумму и, наконец, список значений еще раз для получения результата.
Пример использования;
import datetime
from somewhere import Products
def show_monthly_data(request):
products = dict((p.id, p) for p in Products.objects.all())
defaults = dict((datetime.date(2020, m, 1), 0) for m in range(1, 13))
totals = {}
for product_id, year, month, totale in (
Materiale.objects
.values_list('product__nome', 'date__year', 'date__month')
.annotate(totale=Sum(F('quantity') * F('price')))
.values_list('product__nome', 'date__year', 'date__month', 'totale')
):
product = products[product_id]
if product not in totals:
totals[product] = dict(defaults) # this makes a copy
totals[product][datetime.date(year, month, 1)] = totale
# You could add something to map the products with the totals
return render(request, 'templates/template.html', {}) # etc
# Example to adding it to the Manager
class MaterialeManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).annotate(
total=F('quantity')*F('price'),
)
def get_monthly_totals(self):
products = dict((p.id, p) for p in Products.objects.all())
return list(
(products[product_id], datetime.date(year, month, 1), totale)
for product_id, year, month, totale in (
self.values_list('product__nome', 'date__year', 'date__month')
.annotate(totale=Sum(F('quantity') * F('price')))
.values_list('product__nome', 'date__year', 'date__month', 'totale')
)
Редактировать:
Итак, вы следовали методу и добавили метод в менеджер моделей. Теперь этот метод доступен на ваш взгляд. Следующий шаг - использовать этот метод, чтобы получить ваши элементы.
def conto_economico(request):
context= {
'elements': Materiale.objects.get_monthly_totals(),
}
return render(request, 'conto_economico/conto_economico.html', context)
Таким образом, метод возвращает список кортежей. Но есть две проблемы с данными.
- Данные не сгруппированы по продукту
- Данные не имеют нулевых значений для месяцев, когда ничего не было продано.
Проблема 1 может быть исправлена добавлением order_by, но это не решает вторую проблему. Таким образом, в представлении нам нужно обработать данные, чтобы они работали в шаблоне.
Итак, что работоспособно. Нам нужен один продукт со списком из 12 значений на каждый месяц. Таким образом, мы можем подготовить список нулей и обновить нули, где у нас есть данные.
def conto_economico(request):
defaults = list(0 for m in range(12))
elements = dict()
for product, date, totale in Materiale.objects.get_monthly_totals():
# First check if product is already part of our elements.
# If not, add the defaults for this product to your elements
if product not in elements:
elements[product] = list(defaults)
# Second, find the index where to update the totale
index = date.month - 1 # jan is one, but on index 0
# Update the value
elements[product][index] = totale
context= {'elements': elements}
return render(request, 'conto_economico/conto_economico.html', context)
Итак, теперь мы можем сосредоточиться на рендеринге элементов. Мы знаем, что элементы - это словарь, с ключом продуктов и в качестве значения списка итогов.
<table>
<tr>
<td>Product</td>
<td>Jan</td>
<td>...</td>
</tr>
{% for product, totals in elements.items %}
<tr>
<td>{{ product.name }}</td>
{% for total in totals %}
<td>{{ total }}</td>
{% endfor %}
<tr>
{% endfor %}
</table>