DRF Serializer UML
Posted November 20, 2020
上周要求对当前项目产出数据结构 UML 图及流程图, 为了完成流程图我花了一天的时间, 几近抓狂。 做完准备画数据结构关系图时, 心态炸裂。 于是上网搜索 Django 自动生成 Models UML 图的项目。
没想到还真有, 那就是著名的 django-extensions [1]。
具体的生成 Models Graph 的方式, 请参考 Graph Models.
由于项目 Models 不能完全提现项目的全部数据结构, 要求对 Django rest framework 的 Serializer 也通过图描述出来。 我在网上并没有找到对 Serializer 进行构图的工具。
于是查看 django-extensions [1] graph_models 源码, 做了一个关于生成 Serializer UML 构图的工具。
思路基本上是通过 Serializer 入口, 解析每一个字段类型, 然后通过模板生成 dot [2] 文件。 然后通过 Graphviz [3] 工具提供的 dot 命令进行生成 png 图形。
解析代码
import os
from django.core.management.base import BaseCommand, CommandError
from django.template import Template, Context, loader
from rest_framework.serializers import Serializer, Field, ListField, ListSerializer, ChoiceField
from xxxxx.xxxxx.describe import DescribeSerializer  # Serializer 入口文件
serializers = {}
def name(serializer):
    try:
        return str(serializer.__name__)
    except:
        return str(serializer.__class__.__name__)
def render(serializer, label=None):
    if not label:
        label = getattr(serializer, '__doc__', '')
    if isinstance(serializer, ChoiceField):
        data = { 'name': label, 'label': 'Enum' }
        fields = []
        for k, v in serializer.choices.items():
            fields.append({'key': k, 'ref': 'field', 'type': type(v).__name__, 'label': v})
        
        data["fields"] = fields
        serializers[label] = data
        return 
    data = { 'name': name(serializer), 'label': label }
    fields = []
    ref_fields = []
    if hasattr(serializer, 'Meta'):
        ref_fields  = getattr(serializer.Meta, 'ref_fields', [])
        
    for k, field in serializer._declared_fields.items():
        ref_fields.append({
            'key': k, 'field': field
        })
    for item in ref_fields:
        k, field = item["key"], item["field"]
        if isinstance(field, Serializer):
            render(field)
            fields.append({'key': k, 'ref': 'ref', 'type': name(field), 'label': field.label})
            continue
        if isinstance(field, ListField):
            render(field.child)
            fields.append({'key': k, 'ref': 'ref-[]', 'type': name(field.child), 'list': name(field), 'label': field.label})
            continue
        if isinstance(field, ListSerializer):
            render(field.child)
            fields.append({'key': k, 'ref': 'ref-[]', 'type': name(field.child), 'list': name(field), 'label': field.label})
            continue
        if isinstance(field, ChoiceField):
            label = f'{k.title()}Choices'
            render(field, label)
            fields.append({'key': k, 'ref': 'ref', 'type': label, 'label': name(field)})
            continue
        else:
            fields.append({'key': k, 'ref': 'field', 'type': name(field), 'label': field.label})
    data["fields"] = fields
    serializers[name(serializer)] = data
渲染 dot 文件
模板文件 (dot/serializers.dot), 可以放到 templates , 通过 loader.get_template 获取, 也可以读取单独的文件内容, 然后通过 Template 类实例化.
import os
if __name__ == '__main__':
    render(DescribeSerializer) # 传入入口文件, 您可以指定您的入口文件, 当然您也可以改造上面的脚本让它支持多个入口.
    t = loader.get_template('dot/serializers.dot')
    output_file = './doc/dot/serializers.dot'  # 输出文件.
    if not os.path.exists(os.path.dirname(output_file)):
        raise CommandError('./doc/dot 不存在, 请创建.')
    with open(output_file, 'w') as f:
        f.write(t.render({'serializers': serializers}))
生成图形
通过 Graphviz 提供的 dot 命令.
首先您要安装 Graphviz 软件。然后执行
dot ./doc/dot/serializers.dot -T png -o ./doc/dot/serializers.png通过在线转换网站
如果您不想安装这个过于重量级的应用程序, 幸运的是我发现一个良心的转换网站。它免费!
请点击这里进入在线转换页面 dot-to-png
上传文件就可以生成 png 图形了。

引用
[1] Django框架的全局自定义管理扩展: https://github.com/django-extensions/django-extensions
            
            [2] 图表描述语言: https://www.graphviz.org/pdf/dotguide.pdf
            
            [3]  一个图形可视化软件: https://www.graphviz.org/
            
            