Skip to main content

针对自动化给予前端配置文件的DRF封装

Project description

HAdmin后端对DRF封装

我想实现一个这样的理想国:

比xadmin漂亮;比xadmin强大;前后端分离;基于Django和DRF;智能化配置。

以上几点,是我们对HAdmin的设想,设想这样的后端能够相对自动化输出接口,让前端能够根据配置文件认识输出的标准数据结构,从而进行渲染。

于是,初步版本的HAdmin上线了。

特别说明

这个模块只是帮助构建给前端的配置信息,让前端方便进行对应渲染,并不是给前端输送代码!

这个请大家一定要理解清楚,这里仅仅是帮助规范了配置信息结构而已。

快速开始

安装

pip install hadmin

使用方式

from rest_framework import viewsets
from hadmin import mixins


class CustomConfigViewSet(mixins.PageConfigMixin, viewsets.GenericViewSet):
    list_class = CustomListSerializer
    filter_class = CustomFilter
    create_class = CustomCreateSerializer
    read_class = CustomReadSerializer
    extra = {}

其实,就是用HAdmin下的mixins替代rest_framework下的mixins

并且,增加了一个PageConfigMixin方法,这个方法包含了四种配置文件的输出方式,分别是:

list_class 列表的配置信息

filter_class 筛选器的配置信息

create_class 创建数据表单的配置信息

read_class 读取最终数据的配置信息

extra 是额外扩展的数据,可以是字典或者列表等任意可以转换成json的值,会原样递送到前端,用于前端渲染特殊情况下使用,非必填,根据需要使用

每个对应的都是序列化器的的类,序列化器建议每个方法用不同类,这样可以自由定义。

每个序列化器类的元数据可以增加一个custom字典,以便进行解析。

输出到前端的数据样例:

{
	"filter": {
		"base": [{
			"label": "手机号",
			"method": "input",
			"default": "",
			"url": "",
			"limit": 1,
			"span": 6,
			"key": "mobile"
		}, {
			"label": "姓名",
			"method": "input",
			"default": "",
			"url": "",
			"limit": 1,
			"span": 6,
			"key": "name"
		}, {
			"label": "姓名",
			"method": "input",
			"default": "",
			"url": "",
			"limit": 1,
			"span": 6,
			"key": "desc"
		}, {
			"label": "新房客源",
			"method": "select",
			"default": "",
			"url": "",
			"limit": 1,
			"span": 6,
			"key": "new",
			"options": [{
				"id": "-1000",
				"title": "不限"
			}, {
				"id": "true",
				"title": "是"
			}, {
				"id": "false",
				"title": "否"
			}, {
				"id": "isnull",
				"title": "未填写"
			}]
		}, {
			"label": "二手房客源",
			"method": "select",
			"default": "",
			"url": "",
			"limit": 1,
			"span": 6,
			"key": "old",
			"options": [{
				"id": "-1000",
				"title": "不限"
			}, {
				"id": "true",
				"title": "是"
			}, {
				"id": "false",
				"title": "否"
			}, {
				"id": "isnull",
				"title": "未填写"
			}]
		}, {
			"label": "租房客源",
			"method": "select",
			"default": "",
			"url": "",
			"limit": 1,
			"span": 6,
			"key": "rent",
			"options": [{
				"id": "-1000",
				"title": "不限"
			}, {
				"id": "true",
				"title": "是"
			}, {
				"id": "false",
				"title": "否"
			}, {
				"id": "isnull",
				"title": "未填写"
			}]
		}],
		"adv": [],
		"height": 100,
		"fields": {
			"mobile": "",
			"name": "",
			"desc": "",
			"new": "",
			"old": "",
			"rent": ""
		}
	},
	"create": {
		"fields": {
			"master": null,
			"region": null,
			"admins": [],
			"mobile": "",
			"name": "",
			"new": false,
			"old": false,
			"rent": false,
			"desc": "",
			"custom": []
		},
		"inlines": {
			"custom": {
				"label": "跟踪记录",
				"create": {
					"fields": {
						"custom": null,
						"date": null,
						"body": "",
						"result": "",
						"image": null,
						"file": null
					},
					"detail": [{
						"label": "客源",
						"help_text": null,
						"field_name": "custom",
						"required": true,
						"type": "PrimaryKeyRelatedField",
						"rules": [{
							"required": true,
							"message": "该字段是必填项。"
						}],
						"choices": [{
							"value": 13,
							"label": "18888888885"
						}, {
							"value": 12,
							"label": "18888888884"
						}, {
							"value": 11,
							"label": "18888888883"
						}, {
							"value": 10,
							"label": "18888888882"
						}, {
							"value": 8,
							"label": "13999999990"
						}, {
							"value": 7,
							"label": "13999999994"
						}, {
							"value": 6,
							"label": "13999999995"
						}, {
							"value": 5,
							"label": "13999999996"
						}, {
							"value": 4,
							"label": "13999999997"
						}, {
							"value": 3,
							"label": "13999999998"
						}, {
							"value": 2,
							"label": "13999999999"
						}, {
							"value": 1,
							"label": "13888888888"
						}],
						"choices_apis": [],
						"method": "",
						"max_length": 0,
						"precision": null,
						"field": "ForeignKey"
					}, {
						"label": "时间",
						"help_text": null,
						"field_name": "date",
						"required": true,
						"type": "DateField",
						"rules": [{
							"required": true,
							"message": "该字段是必填项。"
						}],
						"choices": [],
						"choices_apis": [],
						"method": "",
						"max_length": 0,
						"precision": null,
						"field": "DateField"
					}, {
						"label": "内容",
						"help_text": null,
						"field_name": "body",
						"required": true,
						"type": "CharField",
						"rules": [{
							"required": true,
							"message": "该字段是必填项。"
						}],
						"choices": [],
						"choices_apis": [],
						"method": "",
						"max_length": 10000,
						"precision": null,
						"field": "TextField"
					}, {
						"label": "结果",
						"help_text": null,
						"field_name": "result",
						"required": false,
						"type": "CharField",
						"rules": [],
						"choices": [],
						"choices_apis": [],
						"method": "",
						"max_length": 10000,
						"precision": null,
						"field": "TextField"
					}, {
						"label": "图片",
						"help_text": null,
						"field_name": "image",
						"required": false,
						"type": "ImageField",
						"rules": [],
						"choices": [],
						"choices_apis": [],
						"method": "",
						"max_length": 100,
						"precision": null,
						"field": "ImageField"
					}, {
						"label": "附件",
						"help_text": null,
						"field_name": "file",
						"required": false,
						"type": "FileField",
						"rules": [],
						"choices": [],
						"choices_apis": [],
						"method": "",
						"max_length": 100,
						"precision": null,
						"field": "FileField"
					}],
					"inlines": {},
					"tabs": []
				},
				"limit": 10,
				"api": "/admin/user/track/data/"
			}
		},
		"detail": [{
			"label": "主理人",
			"help_text": null,
			"field_name": "master",
			"required": false,
			"type": "PrimaryKeyRelatedField",
			"rules": [],
			"choices": [{
				"value": 1,
				"label": "hofeng"
			}, {
				"value": 13,
				"label": "18961330033"
			}, {
				"value": 83,
				"label": "shanghai001"
			}, {
				"value": 84,
				"label": "shanghai002"
			}, {
				"value": 85,
				"label": "shanghai003"
			}, {
				"value": 86,
				"label": "suqian001"
			}],
			"choices_apis": [],
			"method": "",
			"max_length": 0,
			"precision": null,
			"field": "ForeignKey"
		}, {
			"label": "地区",
			"help_text": null,
			"field_name": "region",
			"required": true,
			"type": "PrimaryKeyRelatedField",
			"rules": [{
				"required": true,
				"message": "该字段是必填项。"
			}],
			"choices": [{
				"value": 1,
				"label": "上海【310000】"
			}, {
				"value": 21,
				"label": "宿迁【321300】"
			}, {
				"value": 34,
				"label": "盐城【320900】"
			}],
			"choices_apis": [],
			"method": "",
			"max_length": 0,
			"precision": null,
			"field": "ForeignKey"
		}, {
			"label": "协作人",
			"help_text": null,
			"field_name": "admins",
			"required": false,
			"type": "ManyRelatedField",
			"rules": [],
			"choices": [{
				"value": 1,
				"label": "hofeng"
			}, {
				"value": 13,
				"label": "18961330033"
			}, {
				"value": 83,
				"label": "shanghai001"
			}, {
				"value": 84,
				"label": "shanghai002"
			}, {
				"value": 85,
				"label": "shanghai003"
			}, {
				"value": 86,
				"label": "suqian001"
			}],
			"choices_apis": [],
			"method": "",
			"max_length": 0,
			"precision": null,
			"field": "ManyToManyField"
		}, {
			"label": "手机号",
			"help_text": "客源手机号",
			"field_name": "mobile",
			"required": true,
			"type": "CharField",
			"rules": [{
				"required": true,
				"message": "该字段是必填项。"
			}],
			"choices": [],
			"choices_apis": [],
			"method": "",
			"max_length": 11,
			"precision": null,
			"field": "CharField"
		}, {
			"label": "姓名",
			"help_text": null,
			"field_name": "name",
			"required": false,
			"type": "CharField",
			"rules": [],
			"choices": [],
			"choices_apis": [],
			"method": "",
			"max_length": 32,
			"precision": null,
			"field": "CharField"
		}, {
			"label": "新房客源",
			"help_text": null,
			"field_name": "new",
			"required": false,
			"type": "BooleanField",
			"rules": [],
			"choices": [],
			"choices_apis": [],
			"method": "",
			"max_length": 0,
			"precision": null,
			"field": "BooleanField"
		}, {
			"label": "二手房客源",
			"help_text": null,
			"field_name": "old",
			"required": false,
			"type": "BooleanField",
			"rules": [],
			"choices": [],
			"choices_apis": [],
			"method": "",
			"max_length": 0,
			"precision": null,
			"field": "BooleanField"
		}, {
			"label": "租房客源",
			"help_text": null,
			"field_name": "rent",
			"required": false,
			"type": "BooleanField",
			"rules": [],
			"choices": [],
			"choices_apis": [],
			"method": "",
			"max_length": 0,
			"precision": null,
			"field": "BooleanField"
		}, {
			"label": "简要说明",
			"help_text": null,
			"field_name": "desc",
			"required": false,
			"type": "CharField",
			"rules": [],
			"choices": [],
			"choices_apis": [],
			"method": "",
			"max_length": 2000,
			"precision": null,
			"field": "TextField"
		}],
		"tabs": []
	},
	"list": [{
		"label": "ID",
		"field_name": "id",
		"type": "IntegerField",
		"field": "AutoField",
		"width": "80"
	}, {
		"label": "手机号",
		"field_name": "mobile",
		"type": "CharField",
		"field": "CharField"
	}, {
		"label": "姓名",
		"field_name": "name",
		"type": "CharField",
		"field": "CharField"
	}, {
		"label": "主理人",
		"field_name": "master",
		"type": "StringRelatedField",
		"field": "ForeignKey"
	}, {
		"label": "新房客源",
		"field_name": "new",
		"type": "BooleanField",
		"field": "BooleanField"
	}, {
		"label": "二手房客源",
		"field_name": "old",
		"type": "BooleanField",
		"field": "BooleanField"
	}, {
		"label": "租房客源",
		"field_name": "rent",
		"type": "BooleanField",
		"field": "BooleanField"
	}, {
		"label": "添加时间",
		"field_name": "add_time",
		"type": "DateTimeField",
		"field": "DateTimeField"
	}],
	"read": [{
		"label": "主理人",
		"field_name": "master",
		"type": "StringRelatedField"
	}, {
		"label": "地区",
		"field_name": "region",
		"type": "StringRelatedField"
	}, {
		"label": "协作人",
		"field_name": "admins",
		"type": "ManyRelatedField"
	}, {
		"label": "添加时间",
		"field_name": "add_time",
		"type": "DateTimeField"
	}, {
		"label": "手机号",
		"field_name": "mobile",
		"type": "CharField"
	}, {
		"label": "姓名",
		"field_name": "name",
		"type": "CharField"
	}, {
		"label": "新房客源",
		"field_name": "new",
		"type": "BooleanField"
	}, {
		"label": "二手房客源",
		"field_name": "old",
		"type": "BooleanField"
	}, {
		"label": "租房客源",
		"field_name": "rent",
		"type": "BooleanField"
	}, {
		"label": "简要说明",
		"field_name": "desc",
		"type": "CharField"
	}],
	"extra": null
}

下面是具体的使用方法

list_class

class CustomListSerializer(serializers.ModelSerializer):
    master = serializers.StringRelatedField()
    add_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S')

    class Meta:
        model = Custom
        fields = ["id", "mobile", "name", "master", "new", "old", "rent", "add_time"]
        custom = {
            "id": {'width': '80'}
        }

这是一个典型的列表读取的序列化器,元数据中规定的输出字段,新增的custom字典表明某个字段的特别约定。

字典的健名用字段名表示定义某个字段;

value值是一个字典,健名自己定义,反馈到前端后,前端方便进行解析。

建议保留width,label这类字段,总之,这里定义什么,前端就会收到什么,然后前端进行渲染。

输出到前端后,对应参数健名为:list

filter_class

作为筛选器,要定义每个筛选项,这是没办法避免的,例如:

class CustomFilter(django_filters.rest_framework.FilterSet):
    mobile = django_filters.CharFilter(method='keyword_filter', help_text="手机号")
    name = django_filters.CharFilter(method='keyword_filter', help_text="姓名")
    desc = django_filters.CharFilter(method='keyword_filter', help_text="说明")
    new = django_filters.BooleanFilter(method='common_filter', help_text="新房客源", label={'options': get_easy_bool()})
    old = django_filters.BooleanFilter(method='common_filter', help_text="二手房客源", label={'options': get_easy_bool()})
    rent = django_filters.BooleanFilter(method='common_filter', help_text="租房客源", label={'options': get_easy_bool()})

以上就是对筛选器的定义,对项的定义,放在label进行定义,选择类的给options值,help_text是显示出来的名称

输出到前端后,对应参数健名为:filter

create_class

创建类的输出,是为了前端自动构造提交表单而定,序列化器写法没有特别,和list一样,可以在元数据中增加一个custom字典,对应把每个字段的特别要求输送到前端。

和其他不同的是,create的Meta下可以额外增加一个tabs,结构为:

tabs = [{
    'label': '基本信息',
    'fields': ['mobile', 'name', 'region', 'desc']
}, {
    'label': '权限配置',
    'fields': ['master', 'admins', 'new', 'old', 'rent']
}]

另外,还增加了一个inlines的数据,结构如下

inlines = {
    'custom': {
        'class': TrackCreateSerializer,
        'api': '/admin/user/track/data/',
        'limit': 10
    }
}

说明:健名是作为主键的字段名,也就是其他子数据的主字段名;class是这个子数据创建的序列化器;api是创建接口;limit是一次最多多少个

这样设定,前端就可以得到这个tabs,可以用于添加表单分步执行。

输出到前端后,对应参数健名为:create

read_class

读取详细内容的序列化器,要注意的是最好不要构建外键对象,而是要用serializers.StringRelatedField()方法把外键进行文本化,这样才能在前端正常显示。

输出到前端后,对应参数健名为:read

extra

这个用于额外给前端一个扩展的数据字段,可以设定一切能够转为json的数据,用于自己定义与前端的协议

输入到前端后,对应参数健名为:extra

版本历史

1.0.5 修复外键选择项过多的问题,添加choices_limit参数,默认超过30个选项就不在从choices里自动取选择项,请在custom里对字段检索方式进行配置

1.0.6 对隐藏字段支持屏蔽,同时修复了一下可能出现的bug;增加了数字字段的小数点长度判断

1.0.7 增加inline模式的配置数据输出,规则见create_class说明

1.0.8 优化了代码,聚类了函数;增加list和read方式下的参数,为patch字段级提供方便

1.0.9 修正inlines的bug

1.0.10 修正create的bug

1.0.11 增加default值的输出

1.0.12 修复bug

1.1.0 增加update

1.1.1 修改默认tabs

1.1.2 修复bug

1.1.3 修复bug

1.1.4 inlines内增加filter参数

1.2.0 新增工具包,详见hadmin.utils

1.2.1 修复list方法上的bug

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hadmin-1.2.1.tar.gz (14.0 kB view hashes)

Uploaded Source

Built Distribution

hadmin-1.2.1-py3-none-any.whl (10.7 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page