Django如何设计模型存储关联对应工作流字段的项目元数据
解决方案
你完全不需要为每个工作流单独编写模型,使用通用的实体-属性-值(EAV)结构即可实现动态表单的元数据存储,后续新增工作流、调整元数据字段都不需要修改数据库表结构,维护成本极低。
第一步:补充缺失的关联模型(按需选择)
你当前的模型里没有存储下拉类型字段选项的结构,如果需要支持下拉框的预定义选项配置,先加一个选项关联模型:
class MetadataFieldOption(models.Model): id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) field = models.ForeignKey(MetadataField, on_delete=models.CASCADE, related_name="options") value = models.CharField(max_length=200) sort = models.IntegerField(default=0, verbose_name="排序值,控制下拉选项展示顺序") def __str__(self): return self.value class Meta: ordering = ["sort"]
第二步:编写核心的元数据值存储模型
这张表是核心,统一存储所有项目的元数据录入值,通过外键关联项目和对应的元数据字段即可:
class ProjectMetadataValue(models.Model): id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="metadata_values") field = models.ForeignKey(MetadataField, on_delete=models.CASCADE) # 统一用TextField存储值,兼容长文本、布尔值字符串、下拉选项值 value = models.TextField(blank=True, null=True) # 下拉类型可以直接关联选项实例,非下拉类型留空即可 selected_option = models.ForeignKey( MetadataFieldOption, on_delete=models.SET_NULL, null=True, blank=True ) created_on = models.DateTimeField(auto_now_add=True) updated_on = models.DateTimeField(auto_now=True) class Meta: # 强制同一个项目下,同一个元数据字段只能有一条值记录,避免重复脏数据 unique_together = ("project", "field")
必须做的业务层校验
模型层只负责存储结构,写入数据的时候必须在业务逻辑里加两层校验,避免非法数据:
- 字段归属校验:当前写入的
field必须属于该项目关联工作流绑定的元数据字段集合,禁止写入不属于当前工作流的字段 - 字段值合法性校验:
- 文本类型(TXT):校验文本长度、格式符合要求
- 复选框类型(CB):校验值为合法布尔值(
True/False或对应格式的字符串) - 下拉类型(DD):校验传入的选项属于该字段下配置的可选选项范围
使用示例
获取项目的元数据、渲染动态表单、查询值都非常简单:
# 1. 获取指定项目的动态表单结构(即该项目关联工作流下的所有元数据字段) project = Project.objects.select_related("workflow").get(id="你的项目ID") form_fields = MetadataField.objects.filter(workflow=project.workflow).prefetch_related("options") # 2. 获取该项目已经录入的所有元数据值 saved_values = { value.field_id: value for value in project.metadata_values.select_related("field", "selected_option") } # 3. 渲染表单时,循环form_fields,从saved_values里取对应已存值回显即可
这种方案的扩展性很强:后续新增字段类型(比如数字、日期、附件上传),只需要在MetadataField.FIELD_TYPE里加新的类型选项,补充对应的校验逻辑即可,不需要修改存储结构。
内容的提问来源于stack exchange,提问作者EdG
相关产品推荐
相关产品推荐

