Django.aggregate() 方法给出错误的值

问题描述 投票:0回答:1
class ReportView(AdminOnlyMixin, ListView):
model = homecleaners
template_name = 'home_clean/report/store_list.html'
context_object_name = 'stores'
paginate_by = 20
ordering = ['-id']
valid_statuses = [2, 3, 5]

def get_queryset(self):
    queryset = super().get_queryset()
    search_text = self.request.GET.get('search_text')
    picked_on = self.request.GET.get('picked_on', None)

    if search_text:
        queryset = queryset.filter(store_name__icontains=search_text)
    if picked_on:
        date_range = picked_on.split(' to ')
        start_date = parse_date(date_range[0])
        end_date = parse_date(date_range[1]) if len(date_range) > 1 else None
        date_filter = {'orders__timeslot__date__range': [start_date, end_date]} if end_date else {'orders__timeslot__date': start_date}
        queryset = queryset.filter(**date_filter)

    status_filter = Q(orders__status__in=self.valid_statuses)

    queryset = queryset.prefetch_related('orders').annotate(
        orders_count=Count('orders__id', filter=status_filter),
        subtotal=Sum('orders__subtotal', filter=status_filter),
        store_discount=Sum(
            Case(
                When(Q(orders__promocode__is_store=True) & status_filter, then='orders__discount'),
                default=Value(0),
                output_field=FloatField()
            )
        ),
        admin_discount=Sum(
            Case(
                When(Q(orders__promocode__is_store=False) & status_filter, then='orders__discount'),
                default=Value(0),
                output_field=FloatField()
            )
        ),
        total_sales=Sum(
            F('orders__subtotal') - Case(
                When(Q(orders__promocode__is_store=True), then=F('orders__discount')),
                default=Value(0),
                output_field=FloatField()
            ),
            filter=status_filter
        ),
        commission=Sum(
            (F('orders__subtotal') - Case(
                When(Q(orders__promocode__is_store=True), then=F('orders__discount')),
                default=Value(0),
                output_field=FloatField()
            )) * F('earning_percentage') / 100,
            filter=status_filter
        )
    )

    return queryset.distinct()

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['count'] = context['paginator'].count

    # Calculate store-level aggregates
    status_filter = Q(orders__status__in=self.valid_statuses)
    store_totals = {}
    for store in self.object_list:
        total = store.orders.filter(status__in=self.valid_statuses).aggregate(
            subtotal=Sum('subtotal')
        )['subtotal'] or 0
        store_totals[store.store_name] = total

    print("Store Totals------:", store_totals)
    store_total_aggr = self.object_list.aggregate(all_orders=Sum('orders__subtotal', filter=status_filter, default=0))
    print("Store Total Aggregates---------:", store_total_aggr)
    
    return context




> Store Totals------: {'aminu1': 600.0, 'Golden Touch': 0, 'hm': 100.0,
> 'Silk Hospitality': 0, 'Test clean': 0, 'Razan Hospitality': 0,
> 'Enertech Cleaning': 0, 'Bait Al Karam Hospitality': 0, 'Clean pro':
> 0, 'Dr.Home': 0, 'Dust Away': 0, 'Al Kheesa General Cleaning': 0,
> 'test': 0, 'fresho': 0, 'justmop': 0, 'cleanpro': 0, 'Test Store':500.0}
>  Store Total Aggregates---------: {'all_orders': 300.0}

这里 all_orders 的 Store Total Aggregates 值应该是 1200,但我却得到 300 为什么,请帮我解决这个问题

我不使用聚合只是为了查找小计的总和,我还有其他标准,例如 net_amount 、佣金、利润等...我仅在此处显示查找小计以便理解

class HomecleanOrders(models.Model):
user = models.ForeignKey(Customer, on_delete=models.CASCADE, null=True, blank=True)
store = models.ForeignKey(homecleaners, on_delete=models.CASCADE, related_name='orders')
service_area = models.ForeignKey(HomeCleanServiceArea, on_delete=models.SET_NULL, null=True, blank=True)
timeslot = models.ForeignKey(HomecleanSlots, on_delete=models.SET_NULL, null=True)
address = models.ForeignKey(Address, on_delete=models.SET_NULL, null=True, blank=True)
duration = models.IntegerField(default=4)
ref_id = models.UUIDField(default=uuid.uuid4, editable=False, null=True, blank=True)
date = models.DateField(null=True, blank=True)
no_of_cleaners = models.IntegerField()
cleaner_type = models.CharField(max_length=4, choices=CLEANER_TYPES, null=True, blank=True)
material = models.BooleanField()
material_charges = models.FloatField(default=0)
credit_card = models.ForeignKey('customer.CustomerCard', on_delete=models.SET_NULL, null=True, blank=True)
payment_type = models.CharField(choices=PAYMENT_CHOICES, default='COD1', max_length=10)
wallet = models.BooleanField(default=False)
deducted_from_wallet = models.FloatField(default=0)
status = models.IntegerField(choices=STATUS_CHOICES, default=WAITING)
state = models.IntegerField(choices=STATE_CHOICES, null=True, blank=True)
subtotal = models.FloatField(default=0)
grand_total = models.FloatField(default=0)
extra_charges = models.FloatField(default=0)
discount = models.FloatField(default=0)
to_pay = models.FloatField(default=0)
is_paid = models.BooleanField(default=False)
is_refunded = models.BooleanField(default=False)
is_payment_tried = models.BooleanField(default=False)
vendor_confirmed = models.BooleanField(default=False)
promocode = models.ForeignKey(PromoCodeV2, on_delete=models.SET_NULL, null=True, blank=True)
vendor_settled = models.BooleanField(default=False)
cancel_reason = models.TextField(null=True, blank=True)
workers = models.ManyToManyField(HomeCleanWorker, blank=True)
created_on = models.DateTimeField(auto_now_add=True)
invoice_id = models.CharField(max_length=200, null=True, blank=True)
bill_id = models.CharField(max_length=200, null=True, blank=True)

instructions = models.CharField(max_length=255, null=True, blank=True)
voice_instructions = models.FileField(upload_to='voice_instructions', null=True, blank=True)
dont_call = models.BooleanField(default=False)
dont_ring = models.BooleanField(default=False)
leave_at_reception = models.BooleanField(default=False)

def save(self, *args, **kwargs):
    super(HomecleanOrders, self).save(*args, **kwargs)
    self.create_transaction_if_needed()

def create_transaction_if_needed(self):
    """Creates a transaction for the order if it is marked as DONE, payment type is COD, and is paid."""
    if self.is_paid and self.status == DONE and self.payment_type == 'COD1' and not hasattr(self, 'transaction'):
        amount = self.subtotal - (self.discount + self.deducted_from_wallet)
        HCTransaction.objects.create(order=self, amount=amount, store=self.store)

def __str__(self):
    return f'{self.id}-{self.store}-{self.user}'

def process_totals(self, auto_order=False, custom_price=0):
    hours_charge = self.service_area.hours_charge if self.service_area else self.store.hours_charge
    weekday = self.date.strftime("%w")
    price = HCWeekdayOffer.objects.get(vendor=self.store).get_weekday_price(week_num=int(weekday))
    price = hours_charge - price if price and price > 0 else hours_charge
    if auto_order:
        price = custom_price if int(custom_price) > 0 else self.store.recurring_price or hours_charge
    self.subtotal = (self.duration * price) * self.no_of_cleaners + self.extra_charges
    if self.material:
        total_material_charge = self.material_charges * self.no_of_cleaners
        self.subtotal += (self.duration * total_material_charge)
    offer_amount = 0
    if self.promocode:
        is_free_delivery, is_cash_back, offer_amount = self.promocode.get_offer_amount(self.subtotal)
        if is_cash_back:
            offer_amount = 0
    grand_total = self.subtotal - offer_amount
    self.grand_total = grand_total
    self.to_pay = grand_total
    self.discount = offer_amount
    self.save()

def get_cleaner_profit(self):
    a = self.subtotal
    result = a * 0.7
    result_round = round(result, 2)
    return result_round

@cached_property
def order_type(self):
    return 'home_clean'

class homecleaners(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, limit_choices_to={'user_type': 5})
store_name = models.CharField(max_length=88)
store_name_arabic = models.CharField(max_length=88)
description = models.CharField(max_length=150, null=True, blank=True)
description_arabic = models.CharField(max_length=150, null=True, blank=True)
cleaner_type = models.CharField(max_length=4, choices=CLEANER_TYPES, default=BOTH)
earning_percentage = models.FloatField(default=30)
image = models.ImageField(upload_to='homecleaners/logos')
address = models.TextField()
django django-models django-aggregation
1个回答
0
投票

聚合查询的问题在于如何在查询链中应用过滤器和注释。具体来说,

annotate()
方法创建的分组在与过滤器结合使用时可能会影响结果,这就是聚合总数与各个商店总数不匹配的原因。

以下是该问题的说明以及解决该问题的步骤:


问题

  1. 注释和聚合冲突

    • 当您在查询集中使用
      annotate()
      时,它会按模型中的字段对结果进行分组(在本例中为
      homecleaners
      )并计算每个组的聚合值。
    • 然后像
    • Sum
      Count
       这样的聚合适用于这些组。但是,如果您稍后在带注释的查询集上使用
      aggregate()
      ,它可能只考虑第一级分组。
  2. 聚合中的过滤器

      注释中应用的过滤器如果与注释冲突,可能无法正确传播到最终的聚合查询中。

修复:使用干净的聚合查询来获取总计

要计算所有商店的正确总数,您需要在

订单级别显式聚合,而不依赖于应用于homecleaners

查询集的注释。

修改代码以与带注释的查询集分开计算总体总数:

正确

get_queryset
方法:

def get_queryset(self): queryset = super().get_queryset() search_text = self.request.GET.get('search_text') picked_on = self.request.GET.get('picked_on', None) if search_text: queryset = queryset.filter(store_name__icontains=search_text) if picked_on: date_range = picked_on.split(' to ') start_date = parse_date(date_range[0]) end_date = parse_date(date_range[1]) if len(date_range) > 1 else None date_filter = {'orders__timeslot__date__range': [start_date, end_date]} if end_date else {'orders__timeslot__date': start_date} queryset = queryset.filter(**date_filter) status_filter = Q(orders__status__in=self.valid_statuses) # Annotate the queryset with store-specific totals queryset = queryset.prefetch_related('orders').annotate( orders_count=Count('orders__id', filter=status_filter), subtotal=Sum('orders__subtotal', filter=status_filter), store_discount=Sum( Case( When(Q(orders__promocode__is_store=True) & status_filter, then='orders__discount'), default=Value(0), output_field=FloatField() ) ), admin_discount=Sum( Case( When(Q(orders__promocode__is_store=False) & status_filter, then='orders__discount'), default=Value(0), output_field=FloatField() ) ), total_sales=Sum( F('orders__subtotal') - Case( When(Q(orders__promocode__is_store=True), then=F('orders__discount')), default=Value(0), output_field=FloatField() ), filter=status_filter ), commission=Sum( (F('orders__subtotal') - Case( When(Q(orders__promocode__is_store=True), then=F('orders__discount')), default=Value(0), output_field=FloatField() )) * F('earning_percentage') / 100, filter=status_filter ) ) return queryset.distinct()
正确

get_context_data
方法:

def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['count'] = context['paginator'].count # Calculate individual store totals status_filter = Q(orders__status__in=self.valid_statuses) store_totals = {} for store in self.object_list: total = store.orders.filter(status__in=self.valid_statuses).aggregate( subtotal=Sum('subtotal') )['subtotal'] or 0 store_totals[store.store_name] = total print("Store Totals------:", store_totals) # Calculate the overall total directly at the order level store_total_aggr = HomecleanOrders.objects.filter( status__in=self.valid_statuses ).aggregate(all_orders=Sum('subtotal')) print("Store Total Aggregates---------:", store_total_aggr) context['store_totals'] = store_totals context['store_total_aggr'] = store_total_aggr['all_orders'] or 0 return context
预期产出

运行更新后的代码时:

  • Store Totals------:
     将显示每个商店的单独总计。
  • Store Total Aggregates---------:
     将显示所有商店的正确总计。
如果您的问题仍然存在,请确保:

  1. status__in=self.valid_statuses
     过滤器应用正确。
  2. 您的数据库包含匹配订单的有效
  3. subtotal
     值。
© www.soinside.com 2019 - 2024. All rights reserved.