Django набор запросов для записей сумм каждый месяц - PullRequest
0 голосов
/ 25 апреля 2020

У меня есть следующая структура моделей:

 Product  |  Price | Quantity |  Total*  | Date of purchase
Product A |   10   |    1     |    10    | 1/01/2020
Product B |   10   |    2     |    20    | 1/02/2020

* итого создается с помощью функции менеджера в models.py.

Я хочу получить сумму за каждый месяц каждого типа продукта в другом приложении моего проекта. Примерно так:

Product   | Gen | Feb | Mar | Apr | May | Jun | ....
Product A | 10  |  0  |  0  |  0  |  0  | 0   | ....
Product A |  0  | 20  |  0  |  0  |  0  | 0   | ....

Это мой models.py

 class Product(models.Model):
        nome= models.CharField()

   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(
            (product, 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')
        )

    class Materiale(models.Model):
        product= models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
        quantity=models.DecimalField()
        price=models.DecimalField()
        date=models.DateField()
        obejcts=MaterialManager()

Но я пытаюсь понять следующий код, но он не работает:

views .py

def conto_economico(request):
    elements = Materiale.objects.all()
    context= {
        'elements':elements,
            }
    return render(request, 'conto_economico/conto_economico.html', context)

шаблон. html

{% for e in elements %}
              {{e.totale}}
{% endfor %}

1 Ответ

1 голос
/ 25 апреля 2020

Хорошо, я добавил год, но вы можете удалить его, если он вам не нужен.

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. Данные не сгруппированы по продукту
  2. Данные не имеют нулевых значений для месяцев, когда ничего не было продано.

Проблема 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>
...