You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 00:06:00