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()
聚合查询的问题在于如何在查询链中应用过滤器和注释。具体来说,
annotate()
方法创建的分组在与过滤器结合使用时可能会影响结果,这就是聚合总数与各个商店总数不匹配的原因。
以下是该问题的说明以及解决该问题的步骤:
注释和聚合冲突:
annotate()
时,它会按模型中的字段对结果进行分组(在本例中为 homecleaners
)并计算每个组的聚合值。Sum
或
Count
这样的聚合适用于这些组。但是,如果您稍后在带注释的查询集上使用
aggregate()
,它可能只考虑第一级分组。
聚合中的过滤器:
订单级别显式聚合,而不依赖于应用于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---------:
将显示所有商店的正确总计。
status__in=self.valid_statuses
过滤器应用正确。
subtotal
值。