如何自定义Django Filter实现筛选结果的求和统计?
问题描述
默认情况下,GET请求api/v1/analytics/需返回当前用户所有店铺的已售商品数、税费、应计金额的总和;若用户传入stores_ids=1,2这类指定店铺ID的参数,则需返回对应店铺的统计总和。现有filters.py代码存在问题,需编写正确的实现代码。
相关代码参考
模型代码
class StoreAnalytics(models.Model): class Meta: verbose_name_plural = "store's analytics" verbose_name = "store's analytics" store = models.ForeignKey(Store, on_delete=models.CASCADE, null=True, related_name="store_id") accruals_sum = models.DecimalField( max_digits=10, decimal_places=2, default=Decimal(0), verbose_name="Accruals sum" ) taxes_count = models.DecimalField(max_digits=7, decimal_places=2, default=Decimal(0), verbose_name="Taxes sum") sold_products = models.PositiveSmallIntegerField(default=0, verbose_name="Number of products sold") def __str__(self): return f"{self.store.pk}"
视图代码
class StoreAnalyticsApi(ListCreateAPIView): permission_classes = (IsAuthenticated,) http_method_names = ["get"] serializer_class = StoreAnalyticsSerializer filter_backends = (DjangoFilterBackend,) filterset_class = StoreAnalyticsFilter def get_queryset(self): queryset = StoreAnalytics.objects.filter(store__user_id=self.request.user.pk).aggregate(accruals_sum=Sum(F("accruals_sum")), sold_products=Sum(F("sold_products")), taxes_summ=Sum(F("taxes_count"))) #{'accruals_sum': Decimal('10045'), 'sold_products': 68, 'taxes_summ': Decimal('602.700000000000')} return queryset
现有问题的filters.py代码
from dashboard.models import StoreAnalytics from django_filters import rest_framework as filters from django.db.models import Sum, F class ListFilter(filters.Filter): def filter(self, qs, value): if not value: return qs self.lookup_expr = "in" values = value.split(",") return super(ListFilter, self).filter(qs, values) class StoreAnalyticsFilter(filters.FilterSet): stores_ids = ListFilter(field_name="store_id") class Meta: model = StoreAnalytics fields = [ "stores_ids", ] def filter(self, queryset, name, value): if value is not None: queryset = StoreAnalytics.objects.filter(store__user_id=self.request.user.pk, store_id__in=stores_ids) return queryset
问题分析
- 原视图中
get_queryset直接返回聚合字典,Django Filter无法作用于非QuerySet对象,导致过滤逻辑失效。 - 自定义
filter方法存在参数错误与未定义变量问题,逻辑实现不符合FilterSet规范。 - 未将用户权限过滤(仅能查看自己的店铺)与参数过滤整合,存在越权风险。
正确实现方案
调整后的filters.py
from dashboard.models import StoreAnalytics from django_filters import rest_framework as filters class StoreAnalyticsFilter(filters.FilterSet): stores_ids = filters.CharFilter(method='filter_stores_ids') class Meta: model = StoreAnalytics fields = ['stores_ids'] def filter_stores_ids(self, queryset, name, value): # 分割传入的店铺ID列表并转为整数 store_ids = [int(id.strip()) for id in value.split(',') if id.strip().isdigit()] return queryset.filter(store_id__in=store_ids) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 初始化时过滤当前用户的店铺数据,确保权限控制 self.queryset = self.queryset.filter(store__user_id=self.request.user.pk)
对应的视图修改
class StoreAnalyticsApi(ListCreateAPIView): permission_classes = (IsAuthenticated,) http_method_names = ["get"] serializer_class = StoreAnalyticsSerializer filter_backends = (DjangoFilterBackend,) filterset_class = StoreAnalyticsFilter def get_queryset(self): # 先获取经过过滤器处理的QuerySet filtered_qs = super().get_queryset() # 对过滤后的QuerySet执行聚合操作 aggregated_data = filtered_qs.aggregate( accruals_sum=Sum(F("accruals_sum")), sold_products=Sum(F("sold_products")), taxes_summ=Sum(F("taxes_count")) ) # 将聚合结果包装为列表,适配ListAPIView的返回要求 return [aggregated_data]
实现说明
- 过滤器通过
__init__方法提前过滤当前用户的店铺数据,确保用户只能访问自己的店铺,避免越权。 filter_stores_ids方法处理stores_ids参数,分割并校验ID格式后过滤对应店铺。- 视图先获取经过过滤的QuerySet,再执行聚合操作,最后将结果包装为列表以适配
ListCreateAPIView的返回要求。
内容的提问来源于stack exchange,提问作者Iren E
相关产品推荐
相关产品推荐

