如何在Django中设计低改动、支持嵌套属性与业务规则的通用实体管理系统
如何在Django中设计适配多实体的动态实体管理系统(Entity Management System)
嘿,这个需求我之前做医疗患者管理和电商商品目录时都碰到过,刚好总结了一套灵活的设计方案,能让你几乎不用改核心代码就能适配各种实体,还能搞定嵌套属性和业务规则配置,分享给你参考~
1. 核心思路:用元数据驱动实体结构
别再为每个实体(产品、患者)写单独的Django Model了!我们要做的是用元数据来描述实体的属性、嵌套关系和业务规则,新增/修改实体属性时,只需要操作元数据,不用动核心代码。
元数据模型设计
先定义3个核心模型来支撑整个系统:
from django.db import models from django.contrib.postgres.fields import JSONField # 实体类型:比如"产品"、"患者"、"接诊医生" class EntityType(models.Model): name = models.CharField(max_length=100, unique=True) slug = models.SlugField(unique=True, help_text="用于接口/代码中唯一标识该实体") description = models.TextField(blank=True, help_text="实体类型的说明") def __str__(self): return self.name # 实体字段:描述某个实体的具体属性(比如患者的"姓名"、"年龄",或者嵌套的"接诊医生") class EntityField(models.Model): FIELD_TYPES = ( ('char', '字符串'), ('int', '整数'), ('date', '日期'), ('bool', '布尔值'), ('sub_entity', '嵌套实体'), # 可以扩展更多类型:浮点、文件、枚举等 ) entity_type = models.ForeignKey(EntityType, on_delete=models.CASCADE, related_name='fields') name = models.CharField(max_length=100, help_text="属性名,比如'姓名'") field_type = models.CharField(max_length=20, choices=FIELD_TYPES) is_required = models.BooleanField(default=False, help_text="是否为必填字段") max_length = models.IntegerField(null=True, blank=True, help_text="仅字符串类型有效") # 如果是嵌套实体,关联对应的子实体类型 sub_entity_type = models.ForeignKey(EntityType, on_delete=models.SET_NULL, null=True, blank=True, related_name='parent_fields') class Meta: unique_together = ('entity_type', 'name') # 同一实体下属性名唯一 def __str__(self): return f"{self.entity_type.name} - {self.name}" # 字段规则:给每个属性配置业务校验规则 class FieldRule(models.Model): RULE_TYPES = ( ('required', '必填校验'), ('regex', '格式校验'), ('max_value', '最大值限制'), ('min_value', '最小值限制'), ('custom', '自定义函数校验'), ) entity_field = models.ForeignKey(EntityField, on_delete=models.CASCADE, related_name='rules') rule_type = models.CharField(max_length=20, choices=RULE_TYPES) rule_config = JSONField(help_text="规则参数,比如正则存{\"pattern\":\"^\\d+$\"},最大值存{\"value\":100}") error_message = models.CharField(max_length=200, help_text="校验失败时的提示信息") def __str__(self): return f"{self.entity_field.name} - {self.rule_type}"
2. 动态实体数据存储:用JSONB存灵活数据
因为每个实体的属性结构不一样,我们用一个统一的模型来存储所有实体的具体数据:
class EntityData(models.Model): entity_type = models.ForeignKey(EntityType, on_delete=models.CASCADE, related_name='data') # 用PostgreSQL的JSONB字段,支持索引和高效查询 data = JSONField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"{self.entity_type.name} - ID:{self.id}"
- 嵌套实体直接在
data字段里存子实体的JSON结构,比如患者数据可以是:{"name":"张三", "age":35, "doctor":{"name":"李四", "department":"内科"}} - 如果需要单独查询子实体(比如统计某个医生的接诊人数),可以把子实体单独存为
EntityData,父实体的data里存子实体的ID即可。
3. 业务规则校验:动态加载并执行规则
写一个通用的校验函数,根据元数据自动校验实体数据:
import re import importlib from django.core.exceptions import ValidationError def validate_entity_data(entity_type_slug, data): """根据实体类型的元数据校验传入的数据""" entity_type = EntityType.objects.get(slug=entity_type_slug) errors = {} for field in entity_type.fields.all(): field_value = data.get(field.name) # 1. 必填校验 if field.is_required and field_value is None: errors[field.name] = "该字段不能为空" continue # 2. 字段类型校验 if field.field_type == 'int' and field_value is not None: try: int(field_value) except ValueError: errors[field.name] = "该字段必须是整数" # 3. 嵌套实体递归校验 if field.field_type == 'sub_entity' and field_value is not None: sub_errors = validate_entity_data(field.sub_entity_type.slug, field_value) if sub_errors: errors[field.name] = sub_errors # 4. 自定义规则校验 for rule in field.rules.all(): if rule.rule_type == 'regex': pattern = rule.rule_config.get('pattern') if not re.match(pattern, str(field_value)): errors[field.name] = rule.error_message elif rule.rule_type == 'max_value': max_val = rule.rule_config.get('value') if field_value > max_val: errors[field.name] = rule.error_message elif rule.rule_type == 'custom': # 支持导入自定义函数,比如rule_config存{"func_path":"myapp.utils.check_id_card"} module_path, func_name = rule.rule_config.get('func_path').rsplit('.', 1) module = importlib.import_module(module_path) check_func = getattr(module, func_name) if not check_func(field_value): errors[field.name] = rule.error_message if errors: raise ValidationError(errors) return True
4. 后台管理:动态生成表单
Django Admin默认是固定表单,我们自定义Admin类来根据元数据动态渲染表单:
from django.contrib import admin from django import forms class EntityDataForm(forms.ModelForm): class Meta: model = EntityData fields = ['entity_type', 'data'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 根据选中的实体类型动态生成表单字段 if self.instance.entity_type: entity_type = self.instance.entity_type for field in entity_type.fields.all(): if field.field_type == 'char': self.fields[field.name] = forms.CharField( required=field.is_required, max_length=field.max_length, label=field.name ) elif field.field_type == 'int': self.fields[field.name] = forms.IntegerField( required=field.is_required, label=field.name ) elif field.field_type == 'sub_entity': # 嵌套实体用文本域输入JSON,或者扩展为嵌套表单 self.fields[field.name] = forms.JSONField( required=field.is_required, label=field.name, widget=forms.Textarea(attrs={'rows':5}) ) # 隐藏原始的data字段,用动态字段填充 self.fields.pop('data') def clean(self): cleaned_data = super().clean() entity_type = cleaned_data.get('entity_type') or self.instance.entity_type # 把动态字段的值整理成JSON结构 data = {field.name: cleaned_data.get(field.name) for field in entity_type.fields.all()} # 执行校验 validate_entity_data(entity_type.slug, data) cleaned_data['data'] = data return cleaned_data @admin.register(EntityData) class EntityDataAdmin(admin.ModelAdmin): form = EntityDataForm list_display = ['entity_type', 'created_at', 'updated_at'] list_filter = ['entity_type']
这样在Admin里新增/编辑实体数据时,会自动根据选择的EntityType显示对应的表单字段,用户输入后自动转为JSON存储。
5. 性能优化小贴士
- 用PostgreSQL的
JSONB字段替代普通JSONField,支持GIN索引,能大幅提升JSON数据的查询效率。 - 缓存实体类型的元数据,比如用Django的
cache框架缓存EntityType和EntityField的查询结果,避免每次请求都查数据库。 - 如果嵌套实体需要频繁独立查询,建议把子实体单独存为
EntityData,父实体通过ID关联,而不是直接存JSON。
内容的提问来源于stack exchange,提问作者Yatna Verma
相关产品推荐
相关产品推荐

