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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:46:29