首页 > 解决方案 > 每月为总和记录设置的 Django 查询集

问题描述

我有以下模型结构:

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

*totale 是使用 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()

但是我尝试使用以下代码来弄清楚但不起作用:

视图.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 %}

标签: djangodjango-modelsdjango-rest-frameworkdjango-formsdjango-views

解决方案


好的,我添加了年份,但如果你不想要它可以删除它。

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 触发分组依据,注释添加总和,最后再次使用 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)

所以现在我们可以专注于渲染元素。我们知道elements 是一个字典,其中key 是products,value 是总数列表。

<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>

推荐阅读