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

Django中如何为Project模型自动生成DDD-YY-SS格式的项目编码

实现方案优化建议

一、代码生成逻辑的放置位置

优先放在模型的save方法中,原因如下:

  • 项目编码是ProjectModel的固有属性规则,和模型强绑定,放在模型层可以保证所有创建场景(admin后台、视图创建、脚本批量导入)都会自动触发编码生成,避免多场景重复写逻辑
  • 放在视图层会导致逻辑分散,后续如果新增创建入口容易遗漏编码生成步骤
  • 模板标签仅用于前端渲染逻辑,完全不适合放数据生成类的业务逻辑

二、原代码存在的问题

  • 未区分新建/更新场景:更新已有项目时会重新生成编码,导致原有编码被覆盖
  • 排序逻辑缺失:直接调用last()如果模型没有默认排序规则,取到的可能不是最新的记录
  • 缺少空值校验:如果department、projectdate为空时会直接抛出异常
  • 并发风险:高并发场景下多个创建请求同时查询到相同的最新序列号,会生成重复编码触发unique约束报错
  • 序列号上限问题:两位序列号最大只能支持同部门同年99个项目,超过会报错

三、优化后代码实现

from django.db import models, transaction
from django.utils.translation import gettext_lazy as _

class ProjectModel(models.Model):
    # 原有字段保持不变
    name = models.CharField(_("project name"), max_length=50)
    sponsor = models.CharField(_("sponsor"), max_length=50)
    manager = models.ForeignKey(StaffModel, verbose_name=_("project manager"), null=True, on_delete=models.SET_NULL, related_name='project_manager')
    lead = models.ForeignKey(StaffModel, verbose_name=_("technical lead"), null=True, on_delete=models.SET_NULL, related_name='tech_lead')
    projectdate = models.DateField(_("start date"), auto_now=False, auto_now_add=False)
    department = models.ForeignKey(DepartmentModel, verbose_name=_("department"), null=True, on_delete=models.SET_NULL)
    sites = models.ManyToManyField(OfficeModel, verbose_name=_("sites"))
    code = models.CharField(_("project code"), max_length=50, unique=True)
    partner = models.ForeignKey(CompanyModel, verbose_name=_("partner"), null=True, on_delete=models.SET_NULL)
    status = models.ForeignKey(ProjectStatusModel, verbose_name=_("status"), null=True, on_delete=models.SET_NULL)

    def save(self, *args, **kwargs):
        # 仅新建对象时生成编码,更新时不修改原有编码
        if not self.pk and not self.code:
            # 空值前置校验,避免后续逻辑报错
            if not self.department or not self.projectdate:
                raise ValueError("项目所属部门和启动日期不能为空")
            
            dpt_code = str(self.department.code)
            year_suffix = str(self.projectdate.year)[-2:]
            prefix = f"{dpt_code}-{year_suffix}-"
            
            # 加行级锁避免并发问题,按编码倒序取最新的记录
            with transaction.atomic():
                last_project = ProjectModel.objects.select_for_update().filter(
                    code__startswith=prefix
                ).order_by("-code").first()
                
                if last_project:
                    last_seq = int(last_project.code.split("-")[-1])
                    new_seq = last_seq + 1
                else:
                    new_seq = 1
                # 如果需要支持超过99的项目,把02d改成03d,对应调整编码格式即可
                self.code = f"{prefix}{new_seq:02d}"
        
        super().save(*args, **kwargs)

如果业务场景同部门同年项目数确实可能超过99,直接把序列号的格式化规则从02d改成03d即可。如果并发量非常高,还可以在捕获到IntegrityError(编码重复报错)的时候加1-2次重试逻辑,进一步提升稳定性。

内容的提问来源于stack exchange,提问作者Victor Donoso

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 10:36:04