Django中views.py存放大量薪资计算逻辑,如何规范重构与优化?
Django薪资统计逻辑优化方案
一、逻辑分层合理性说明
你当前把所有薪资统计逻辑放在视图层确实不符合规范,Django的MTV架构中,业务域相关的计算逻辑应该下沉到数据模型层或者单独的服务层,避免视图臃肿、逻辑无法复用的问题。
你提到的@property装饰的属性适合单条Lesson记录的相关计算,全局聚合统计的逻辑适合封装到模型的自定义管理器中,两种方案结合使用可以同时覆盖单对象取值和全局统计的场景。
二、具体实现代码
1. 修改后的models.py
from django.db import models from django.db.models import Count, Case, When, IntegerField # 把常量统一提取到模型层管理,后续修改只需要改这里 BASIC_RATE = 300 TOP_RATE = 400 NATIVE_RATE = 700 TOP_HOUSE_RATE = 0.3 NATIVE_HOUSE_RATE = 0.25 class Lessons(models.Model): PRICE_BAND_BASIC = "BASIC" PRICE_BAND_TOP = "TOP" PRICE_BAND_NATIVE = "NATIVE" # 你原来的PAYMENT_TYPE_CHOICES可以直接用上面的常量定义,避免魔法字符串 student = models.ForeignKey(Students, on_delete=models.SET_NULL, null=True) headed_by = models.ForeignKey(Tutors, on_delete=models.SET_NULL, null=True) day = models.CharField(max_length=4, choices=DAY_CHOICES, null=True) start_time = models.TimeField(null=True, blank=True) type = models.CharField(max_length=7, choices=TYPE_CHOICES, null=True) price_band = models.CharField(max_length=7, choices=PAYMENT_TYPE_CHOICES, blank=True, null=True) created = models.DateTimeField(auto_now_add=True ) # 单节课对应课时费 @property def lesson_gross_earning(self): rate_map = { self.PRICE_BAND_BASIC: BASIC_RATE, self.PRICE_BAND_TOP: TOP_RATE, self.PRICE_BAND_NATIVE: NATIVE_RATE } return rate_map.get(self.price_band, 0) # 单节课平台抽成 @property def house_fee(self): if self.price_band == self.PRICE_BAND_TOP: return self.lesson_gross_earning * TOP_HOUSE_RATE elif self.price_band == self.PRICE_BAND_NATIVE: return self.lesson_gross_earning * NATIVE_HOUSE_RATE return 0 # 单节课教师实际收入 @property def teacher_net_earning(self): return self.lesson_gross_earning - self.house_fee # 自定义管理器,封装全局统计逻辑 class LessonManager(models.Manager): def get_salary_statistics(self): # 单次SQL查询完成所有分类统计,比原来的4次查询效率高很多 stats = self.aggregate( basic_count=Count(Case(When(price_band="BASIC", then=1), output_field=IntegerField())), top_count=Count(Case(When(price_band="TOP", then=1), output_field=IntegerField())), native_count=Count(Case(When(price_band="NATIVE", then=1), output_field=IntegerField())), ) basic_count = stats["basic_count"] or 0 top_count = stats["top_count"] or 0 native_count = stats["native_count"] or 0 # 计算各项指标 basic_earning = basic_count * BASIC_RATE top_earning = top_count * TOP_RATE native_earning = native_count * NATIVE_RATE top_house_fee = top_earning * TOP_HOUSE_RATE native_house_fee = native_earning * NATIVE_HOUSE_RATE total_top = top_earning - top_house_fee total_native = native_earning - native_house_fee return { "total_gross": top_earning + native_earning, "native_number": native_earning, "top_number": top_earning, "basic_number": basic_earning, "basic": basic_count, "top": top_count, "native": native_count, "native_fee": native_house_fee, "top_fee": top_house_fee, "top_rate": TOP_RATE, "native_rate": NATIVE_RATE, "basic_rate": BASIC_RATE, "total": total_top + total_native, "native_house_rate": NATIVE_HOUSE_RATE, "top_house_rate": TOP_HOUSE_RATE, "total_top": total_top, "total_native": total_native, "monthly_top": total_top * 4, "monthly_native": total_native * 4, "monthly_total": (total_top + total_native) * 4 } objects = LessonManager() def __str__(self): return str(self.student) + " / " + str(self.day) class Meta: ordering=['student',"headed_by",'day','start_time']
2. 修改后的views.py
def accounts(request): context = Lessons.objects.get_salary_statistics() return render(request, "accounts.html", context)
三、方案优势说明
- 逻辑复用:单节课的计算属性可以在任意场景调用,不需要重复写计算逻辑,常量统一管理修改时只需要修改一处
- 性能提升:原来的写法会执行4次SQL查询,重构后全局统计只需要执行1次SQL,数据量越大性能提升越明显
- 可维护性:所有薪资相关逻辑都在模型层,视图只负责接收请求、返回响应,符合分层设计规范
四、关于@property的必要性说明
如果你的业务场景中经常需要获取单节课的收入、抽成数据,那定义@property非常有必要;如果只需要全局统计,也可以只保留自定义管理器的逻辑,根据实际业务需求选择即可。
内容的提问来源于stack exchange,提问作者John Nathalang Sullivan
相关产品推荐
相关产品推荐

