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
相关产品推荐
相关产品推荐

