mirror of https://github.com/jumpserver/jumpserver
reactor: 增加DynamicMappingSerializer类,实现Serializer中的字段可以动态改变的功能 (#5379)
* reactor: 增加DynamicMappingSerializer类,实现Serializer中的字段可以动态改变的功能 * reactor: 增加DynamicMappingSerializer类,实现Serializer中的字段可以动态改变的功能 (2) * reactor: 增加DynamicMappingSerializer类,实现Serializer中的字段可以动态改变的功能 (3) Co-authored-by: Bai <bugatti_it@163.com>pull/5382/head
parent
3188692691
commit
17a01a12db
|
@ -5,13 +5,12 @@ from orgs.mixins.api import OrgBulkModelViewSet
|
|||
|
||||
from ..hands import IsOrgAdminOrAppUser
|
||||
from .. import models, serializers
|
||||
from .mixin import ApplicationViewMixin
|
||||
|
||||
|
||||
__all__ = ['ApplicationViewSet']
|
||||
|
||||
|
||||
class ApplicationViewSet(ApplicationViewMixin, OrgBulkModelViewSet):
|
||||
class ApplicationViewSet(OrgBulkModelViewSet):
|
||||
model = models.Application
|
||||
filter_fields = ('name', 'type', 'category')
|
||||
search_fields = filter_fields
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
from orgs.models import Organization
|
||||
from ..serializers.utils import get_dynamic_mapping_fields_mapping_rule_by_view
|
||||
|
||||
|
||||
__all__ = ['ApplicationViewMixin', 'SerializeApplicationToTreeNodeMixin']
|
||||
|
||||
|
||||
class ApplicationViewMixin:
|
||||
""" 实现 `get_dynamic_mapping_fields_mapping_rule` 方法, 供其他和 Application 相关的 View 继承使用"""
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule(self):
|
||||
fields_mapping_rule = get_dynamic_mapping_fields_mapping_rule_by_view(view=self)
|
||||
return fields_mapping_rule
|
||||
__all__ = ['SerializeApplicationToTreeNodeMixin']
|
||||
|
||||
|
||||
class SerializeApplicationToTreeNodeMixin:
|
||||
|
|
|
@ -4,20 +4,37 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.drf.fields import DynamicMappingField
|
||||
from .attrs import get_attrs_field_dynamic_mapping_rules
|
||||
from common.drf.serializers import DynamicMappingSerializer
|
||||
from .attrs import attrs_field_dynamic_mapping_serializers
|
||||
|
||||
from .. import models
|
||||
|
||||
__all__ = [
|
||||
'ApplicationSerializer',
|
||||
'IncludeDynamicMappingSerializerFieldApplicationSerializerMixin',
|
||||
]
|
||||
|
||||
|
||||
class ApplicationSerializer(BulkOrgResourceModelSerializer):
|
||||
class IncludeDynamicMappingSerializerFieldApplicationSerializerMixin(serializers.Serializer):
|
||||
attrs = DynamicMappingSerializer(mapping_serializers=attrs_field_dynamic_mapping_serializers)
|
||||
|
||||
def get_attrs_mapping_path(self, mapping_serializers):
|
||||
request = self.context['request']
|
||||
query_type = request.query_params.get('type')
|
||||
query_category = request.query_params.get('category')
|
||||
if query_type:
|
||||
mapping_path = ['type', query_type]
|
||||
elif query_category:
|
||||
mapping_path = ['category', query_category]
|
||||
else:
|
||||
mapping_path = ['default']
|
||||
return mapping_path
|
||||
|
||||
|
||||
class ApplicationSerializer(IncludeDynamicMappingSerializerFieldApplicationSerializerMixin,
|
||||
BulkOrgResourceModelSerializer):
|
||||
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||
attrs = DynamicMappingField(mapping_rules=get_attrs_field_dynamic_mapping_rules())
|
||||
|
||||
class Meta:
|
||||
model = models.Application
|
||||
|
@ -33,3 +50,4 @@ class ApplicationSerializer(BulkOrgResourceModelSerializer):
|
|||
_attrs = self.instance.attrs if self.instance else {}
|
||||
_attrs.update(attrs)
|
||||
return _attrs
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..category import RemoteAppSerializer
|
||||
from ..application_category import RemoteAppSerializer
|
||||
|
||||
|
||||
__all__ = ['ChromeSerializer']
|
|
@ -2,7 +2,7 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..category import RemoteAppSerializer
|
||||
from ..application_category import RemoteAppSerializer
|
||||
|
||||
|
||||
__all__ = ['CustomSerializer']
|
|
@ -1,4 +1,4 @@
|
|||
from ..category import CloudSerializer
|
||||
from ..application_category import CloudSerializer
|
||||
|
||||
|
||||
__all__ = ['K8SSerializer']
|
|
@ -1,7 +1,7 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..category import DBSerializer
|
||||
from ..application_category import DBSerializer
|
||||
|
||||
|
||||
__all__ = ['MySQLSerializer']
|
|
@ -1,7 +1,7 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..category import RemoteAppSerializer
|
||||
from ..application_category import RemoteAppSerializer
|
||||
|
||||
|
||||
__all__ = ['MySQLWorkbenchSerializer']
|
|
@ -1,7 +1,7 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..category import DBSerializer
|
||||
from ..application_category import DBSerializer
|
||||
|
||||
|
||||
__all__ = ['OracleSerializer']
|
|
@ -1,7 +1,7 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..category import DBSerializer
|
||||
from ..application_category import DBSerializer
|
||||
|
||||
|
||||
__all__ = ['PostgreSerializer']
|
|
@ -1,7 +1,7 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..category import RemoteAppSerializer
|
||||
from ..application_category import RemoteAppSerializer
|
||||
|
||||
|
||||
__all__ = ['VMwareClientSerializer']
|
|
@ -1,12 +1,11 @@
|
|||
import copy
|
||||
from rest_framework import serializers
|
||||
from applications import const
|
||||
from common.drf.fields import IgnoreSensitiveInfoReadOnlyJSONField
|
||||
from . import category, type as application_type
|
||||
from . import application_category, application_type
|
||||
|
||||
|
||||
__all__ = [
|
||||
'get_attrs_field_dynamic_mapping_rules', 'get_attrs_field_mapping_rule_by_view',
|
||||
'get_serializer_by_application_type',
|
||||
'attrs_field_dynamic_mapping_serializers',
|
||||
'get_serializer_class_by_application_type',
|
||||
]
|
||||
|
||||
|
||||
|
@ -35,16 +34,15 @@ type_custom = const.ApplicationTypeChoices.custom.value
|
|||
type_k8s = const.ApplicationTypeChoices.k8s.value
|
||||
|
||||
|
||||
# define `attrs` field `DynamicMappingField` mapping_rules
|
||||
# -----------------------------------------------------
|
||||
# define `attrs` field `dynamic mapping serializers`
|
||||
# --------------------------------------------------
|
||||
|
||||
|
||||
__ATTRS_FIELD_DYNAMIC_MAPPING_RULES = {
|
||||
'default': IgnoreSensitiveInfoReadOnlyJSONField,
|
||||
attrs_field_dynamic_mapping_serializers = {
|
||||
'category': {
|
||||
category_db: category.DBSerializer,
|
||||
category_remote_app: category.RemoteAppSerializer,
|
||||
category_cloud: category.CloudSerializer,
|
||||
category_db: application_category.DBSerializer,
|
||||
category_remote_app: application_category.RemoteAppSerializer,
|
||||
category_cloud: application_category.CloudSerializer,
|
||||
},
|
||||
'type': {
|
||||
# db
|
||||
|
@ -63,32 +61,5 @@ __ATTRS_FIELD_DYNAMIC_MAPPING_RULES = {
|
|||
}
|
||||
|
||||
|
||||
# Note:
|
||||
# The dynamic mapping rules of `attrs` field is obtained
|
||||
# through call method `get_attrs_field_dynamic_mapping_rules`
|
||||
|
||||
def get_attrs_field_dynamic_mapping_rules():
|
||||
return copy.deepcopy(__ATTRS_FIELD_DYNAMIC_MAPPING_RULES)
|
||||
|
||||
|
||||
# get `attrs dynamic field` mapping rule by `view object`
|
||||
# ----------------------------------------------------
|
||||
|
||||
|
||||
def get_attrs_field_mapping_rule_by_view(view):
|
||||
query_type = view.request.query_params.get('type')
|
||||
query_category = view.request.query_params.get('category')
|
||||
if query_type:
|
||||
mapping_rule = ['type', query_type]
|
||||
elif query_category:
|
||||
mapping_rule = ['category', query_category]
|
||||
else:
|
||||
mapping_rule = ['default']
|
||||
return mapping_rule
|
||||
|
||||
|
||||
# get `category` mapping `serializer`
|
||||
# -----------------------------------
|
||||
|
||||
def get_serializer_by_application_type(app_tp):
|
||||
return __ATTRS_FIELD_DYNAMIC_MAPPING_RULES['type'].get(app_tp)
|
||||
def get_serializer_class_by_application_type(_application_type):
|
||||
return attrs_field_dynamic_mapping_serializers['type'].get(_application_type)
|
||||
|
|
|
@ -31,8 +31,8 @@ class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
|
|||
"""
|
||||
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
|
||||
"""
|
||||
from .attrs import get_serializer_by_application_type
|
||||
serializer_class = get_serializer_by_application_type(obj.type)
|
||||
from .attrs import get_serializer_class_by_application_type
|
||||
serializer_class = get_serializer_class_by_application_type(obj.type)
|
||||
fields = serializer_class().get_fields()
|
||||
|
||||
parameters = [obj.type]
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
from .attrs import get_attrs_field_mapping_rule_by_view
|
||||
|
||||
__all__ = [
|
||||
'get_dynamic_mapping_fields_mapping_rule_by_view'
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# get `dynamic fields` mapping rule by `view object`
|
||||
# ----------------------------------------------------
|
||||
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule_by_view(view):
|
||||
return {
|
||||
'attrs': get_attrs_field_mapping_rule_by_view(view=view),
|
||||
}
|
|
@ -6,88 +6,10 @@ from rest_framework import serializers
|
|||
|
||||
|
||||
__all__ = [
|
||||
'DynamicMappingField', 'ReadableHiddenField',
|
||||
'CustomMetaDictField', 'IgnoreSensitiveInfoReadOnlyJSONField',
|
||||
'ReadableHiddenField', 'CustomMetaDictField',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# DynamicMappingField
|
||||
# -------------------
|
||||
|
||||
|
||||
class DynamicMappingField(serializers.Field):
|
||||
"""
|
||||
一个可以根据用户行为而动态改变的字段
|
||||
|
||||
For example, Define attribute `mapping_rules`
|
||||
|
||||
field_name = meta
|
||||
|
||||
mapping_rules = {
|
||||
'default': serializers.JSONField(),
|
||||
'type': {
|
||||
'apply_asset': {
|
||||
'default': serializers.CharField(label='default'),
|
||||
'get': ApplyAssetSerializer,
|
||||
'post': ApproveAssetSerializer,
|
||||
},
|
||||
'apply_application': ApplyApplicationSerializer,
|
||||
'login_confirm': LoginConfirmSerializer,
|
||||
'login_times': LoginTimesSerializer
|
||||
},
|
||||
'category': {
|
||||
'apply': ApplySerializer,
|
||||
'login': LoginSerializer
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, mapping_rules, *args, **kwargs):
|
||||
assert isinstance(mapping_rules, dict), (
|
||||
'`mapping_rule` argument expect type `dict`, gut get `{}`'
|
||||
''.format(type(mapping_rules))
|
||||
)
|
||||
|
||||
self.__mapping_rules = mapping_rules
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def mapping_rules(self):
|
||||
return copy.deepcopy(self.__mapping_rules)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
""" 实际是一个虚拟字段所以不返回任何值 """
|
||||
pass
|
||||
|
||||
def to_representation(self, value):
|
||||
""" 实际是一个虚拟字段所以不返回任何值 """
|
||||
pass
|
||||
|
||||
|
||||
# A Ignore read-only fields for sensitive information
|
||||
# ----------------------------------------------------------
|
||||
|
||||
|
||||
class IgnoreSensitiveInfoReadOnlyJSONField(serializers.JSONField):
|
||||
""" A ignore read-only fields for sensitive information """
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs['read_only'] = True
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
sensitive_ignored_value = {}
|
||||
sensitive_names = ['password']
|
||||
for field_name, field_value in value.items():
|
||||
for sensitive_name in sensitive_names:
|
||||
if sensitive_name in field_name.lower():
|
||||
continue
|
||||
sensitive_ignored_value[field_name] = field_value
|
||||
return super().to_representation(sensitive_ignored_value)
|
||||
|
||||
|
||||
#
|
||||
# ReadableHiddenField
|
||||
# -------------------
|
||||
|
||||
|
|
|
@ -5,133 +5,98 @@ from rest_framework.serializers import ModelSerializer
|
|||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
|
||||
from common.mixins import BulkListSerializerMixin
|
||||
from common.drf.fields import DynamicMappingField
|
||||
from django.utils.functional import cached_property
|
||||
from rest_framework.utils.serializer_helpers import BindingDict
|
||||
from common.mixins.serializers import BulkSerializerMixin
|
||||
from common.utils import QuickLookupDict
|
||||
|
||||
__all__ = [
|
||||
'IncludeDynamicMappingFieldSerializerMetaClass',
|
||||
'DynamicMappingSerializer',
|
||||
'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer'
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# IncludeDynamicMappingFieldSerializerMetaClass
|
||||
# ---------------------------------------------
|
||||
# DynamicMappingSerializer
|
||||
# ------------------------
|
||||
|
||||
class IncludeDynamicMappingFieldSerializerMetaClass(serializers.SerializerMetaclass, type):
|
||||
"""
|
||||
SerializerMetaClass: 动态创建包含 `common.drf.fields.DynamicMappingField` 字段的 `SerializerClass`
|
||||
|
||||
* Process only fields of type `DynamicMappingField` in `_declared_fields`
|
||||
* 只处理 `_declared_fields` 中类型为 `DynamicMappingField` 的字段
|
||||
class DynamicMappingSerializer(serializers.Serializer):
|
||||
data_type_error_messages = 'Expect get instance of type `{}`, but got instance type of `{}`'
|
||||
|
||||
根据 `attrs['dynamic_mapping_fields_mapping_rule']` 中指定的 `fields_mapping_rule`,
|
||||
从 `DynamicMappingField` 中匹配出满足给定规则的字段, 并使用匹配到的字段替换自身的 `DynamicMappingField`
|
||||
def __init__(self, mapping_serializers=None, get_mapping_serializers_method_name=None,
|
||||
get_mapping_path_method_name=None, default_serializer=None, **kwargs):
|
||||
self.mapping_serializers = mapping_serializers
|
||||
self.get_mapping_serializers_method_name = get_mapping_serializers_method_name
|
||||
self.get_mapping_path_method_name = get_mapping_path_method_name
|
||||
self.default_serializer = default_serializer or serializers.Serializer
|
||||
super().__init__(**kwargs)
|
||||
|
||||
* 注意: 如果未能根据给定的匹配规则获取到对应的字段,先获取与给定规则同级的 `default` 字段,
|
||||
如果仍未获取到,则再获取 `DynamicMappingField`中定义的最外层的 `default` 字段
|
||||
def bind(self, field_name, parent):
|
||||
# The get mapping serializers method name defaults to `get_{field_name}_mapping_serializers`
|
||||
if self.get_mapping_serializers_method_name is None:
|
||||
method_name = 'get_{field_name}_mapping_serializers'.format(field_name=field_name)
|
||||
self.get_mapping_serializers_method_name = method_name
|
||||
|
||||
* 说明: 如果获取到的不是 `serializers.Field` 类型, 则返回 `DynamicMappingField()`
|
||||
# The get mapping rule method name defaults to `get_{field_name}_mapping_path`.
|
||||
if self.get_mapping_path_method_name is None:
|
||||
method_name = 'get_{field_name}_mapping_path'.format(field_name=field_name)
|
||||
self.get_mapping_path_method_name = method_name
|
||||
|
||||
For example, define attrs['dynamic_mapping_fields_mapping_rule']:
|
||||
super().bind(field_name, parent)
|
||||
|
||||
mapping_rules = {
|
||||
'default': serializer.JSONField,
|
||||
'type': {
|
||||
'apply_asset': {
|
||||
'default': serializer.ChoiceField(),
|
||||
'get': serializer.CharField()
|
||||
}
|
||||
}
|
||||
}
|
||||
meta = DynamicMappingField(mapping_rules=mapping_rules)
|
||||
def get_mapping_serializers(self):
|
||||
if self.mapping_serializers is not None:
|
||||
return self.mapping_serializers
|
||||
method = getattr(self.parent, self.get_mapping_serializers_method_name)
|
||||
return method()
|
||||
|
||||
dynamic_mapping_fields_mapping_rule = {'meta': ['type', 'apply_asset', 'get'],}
|
||||
=> Got `serializer.CharField()`
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset.get',}}
|
||||
=> Got `serializer.CharField()`
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset.',}}
|
||||
=> Got serializer.ChoiceField(),
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset.xxx',}}
|
||||
=> Got `serializer.ChoiceField()`
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset.get.xxx',}}
|
||||
=> Got `serializer.JSONField()`
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset',}}
|
||||
=> Got `{'get': {}}`, type is not `serializers.Field`, So `meta` is `DynamicMappingField()`
|
||||
"""
|
||||
def get_mapping_path(self, mapping_serializers):
|
||||
method = getattr(self.parent, self.get_mapping_path_method_name)
|
||||
return method(mapping_serializers)
|
||||
|
||||
@classmethod
|
||||
def get_dynamic_mapping_fields(mcs, bases, attrs):
|
||||
fields = {}
|
||||
@staticmethod
|
||||
def mapping(mapping_serializers, mapping_path):
|
||||
quick_lookup_dict = QuickLookupDict(data=mapping_serializers)
|
||||
serializer = quick_lookup_dict.get(key_path=mapping_path)
|
||||
return serializer
|
||||
|
||||
# get `fields mapping rule` from attrs `dynamic_mapping_fields_mapping_rule`
|
||||
fields_mapping_rule = attrs.get('dynamic_mapping_fields_mapping_rule')
|
||||
|
||||
# check `fields_mapping_rule` type
|
||||
assert isinstance(fields_mapping_rule, dict), (
|
||||
'`dynamic_mapping_fields_mapping_rule` must be `dict` type , but get `{}`'
|
||||
''.format(type(fields_mapping_rule))
|
||||
def get_mapped_serializer(self):
|
||||
mapping_serializers = self.get_mapping_serializers()
|
||||
assert isinstance(mapping_serializers, dict), (
|
||||
self.data_type_error_messages.format('dict', type(mapping_serializers))
|
||||
)
|
||||
mapping_path = self.get_mapping_path(mapping_serializers)
|
||||
assert isinstance(mapping_path, list), (
|
||||
self.data_type_error_messages.format('list', type(mapping_path))
|
||||
)
|
||||
serializer = self.mapping(mapping_serializers, mapping_path)
|
||||
return serializer
|
||||
|
||||
# get `serializer class` declared fields
|
||||
declared_fields = mcs._get_declared_fields(bases, attrs)
|
||||
declared_fields_names = list(declared_fields.keys())
|
||||
|
||||
fields_mapping_rule = copy.deepcopy(fields_mapping_rule)
|
||||
|
||||
for field_name, field_mapping_rule in fields_mapping_rule.items():
|
||||
|
||||
if field_name not in declared_fields_names:
|
||||
continue
|
||||
|
||||
declared_field = declared_fields[field_name]
|
||||
if not isinstance(declared_field, DynamicMappingField):
|
||||
continue
|
||||
|
||||
assert isinstance(field_mapping_rule, (list, str)), (
|
||||
'`dynamic_mapping_fields_mapping_rule.field_mapping_rule` '
|
||||
'- can be either a list of keys, or a delimited string. '
|
||||
'Such as: `["type", "apply_asset", "get"]` or `type.apply_asset.get` '
|
||||
'but, get type is `{}`, `{}`'
|
||||
''.format(type(field_mapping_rule), field_mapping_rule)
|
||||
)
|
||||
|
||||
if isinstance(field_mapping_rule, str):
|
||||
field_mapping_rule = field_mapping_rule.split('.')
|
||||
|
||||
# construct `field mapping rules` sequence list
|
||||
field_mapping_rules = [
|
||||
field_mapping_rule,
|
||||
copy.deepcopy(field_mapping_rule)[:-1] + ['default'],
|
||||
['default']
|
||||
]
|
||||
|
||||
dynamic_field = declared_field
|
||||
|
||||
field_finder = QuickLookupDict(dynamic_field.mapping_rules)
|
||||
|
||||
field = field_finder.find_one(key_paths=field_mapping_rules)
|
||||
|
||||
if isinstance(field, type):
|
||||
field = field()
|
||||
|
||||
if not isinstance(field, serializers.Field):
|
||||
continue
|
||||
|
||||
fields[field_name] = field
|
||||
@cached_property
|
||||
def mapped_serializer(self):
|
||||
serializer = self.get_mapped_serializer()
|
||||
if serializer is None:
|
||||
serializer = self.default_serializer
|
||||
if isinstance(serializer, type):
|
||||
serializer = serializer()
|
||||
return serializer
|
||||
|
||||
def get_fields(self):
|
||||
fields = self.mapped_serializer.get_fields()
|
||||
return fields
|
||||
|
||||
@cached_property
|
||||
def fields(self):
|
||||
"""
|
||||
重写此方法因为在 BindingDict 中要设置每一个 field 的 parent 为 `mapped_serializer`,
|
||||
这样在调用 field.parent 时, 才会达到预期的结果,
|
||||
比如: serializers.SerializerMethodField
|
||||
"""
|
||||
fields = BindingDict(self.mapped_serializer)
|
||||
for key, value in self.get_fields().items():
|
||||
fields[key] = value
|
||||
return fields
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
dynamic_mapping_fields = mcs.get_dynamic_mapping_fields(bases, attrs)
|
||||
attrs.update(dynamic_mapping_fields)
|
||||
return super().__new__(mcs, name, bases, attrs)
|
||||
|
||||
#
|
||||
# Other Serializer
|
||||
|
|
|
@ -13,7 +13,6 @@ from rest_framework.response import Response
|
|||
from rest_framework.settings import api_settings
|
||||
|
||||
from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
|
||||
from common.drf.serializers import IncludeDynamicMappingFieldSerializerMetaClass
|
||||
from ..utils import lazyproperty
|
||||
|
||||
__all__ = [
|
||||
|
@ -29,12 +28,11 @@ class JSONResponseMixin(object):
|
|||
return JsonResponse(context)
|
||||
|
||||
|
||||
#
|
||||
# GenericSerializerMixin
|
||||
# SerializerMixin
|
||||
# ----------------------
|
||||
|
||||
|
||||
class GenericSerializerMixin:
|
||||
class SerializerMixin:
|
||||
""" 根据用户请求动作的不同,获取不同的 `serializer_class `"""
|
||||
|
||||
serializer_classes = None
|
||||
|
@ -65,65 +63,6 @@ class GenericSerializerMixin:
|
|||
return serializer_class
|
||||
|
||||
|
||||
#
|
||||
# IncludeDynamicMappingFieldSerializerViewMixin
|
||||
# ---------------------------------------------
|
||||
|
||||
|
||||
class IncludeDynamicMappingFieldSerializerViewMixin(GenericSerializerMixin):
|
||||
"""
|
||||
动态创建 `view` 使用的 `serializer_class`,
|
||||
|
||||
根据用户请求行为的不同, 构造出获取 `serializer_class` 中 `common.drf.fields.DynamicMappingField` 字段
|
||||
的映射规则, 并通过 `IncludeDynamicMappingFieldSerializerMetaClass` 元类,
|
||||
基于父类的 `serializer_class` 和 构造出的映射规则 `dynamic_mapping_fields_mapping_rule`
|
||||
创建出满足要求的新的 `serializer_class`
|
||||
|
||||
* 重写 get_dynamic_mapping_fields_mapping_rule 方法:
|
||||
|
||||
For example,
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule(self):
|
||||
return {'meta': ['type', 'apply_asset', 'get']
|
||||
|
||||
"""
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule(self):
|
||||
"""
|
||||
return:
|
||||
{
|
||||
'meta': ['type', 'apply_asset', 'get'],
|
||||
'meta2': 'category.login'
|
||||
}
|
||||
"""
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _create_serializer_class(base, attrs):
|
||||
serializer_class = IncludeDynamicMappingFieldSerializerMetaClass(
|
||||
base.__name__, (base, ), attrs
|
||||
)
|
||||
return serializer_class
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = super().get_serializer_class()
|
||||
|
||||
if getattr(self, 'swagger_fake_view', False):
|
||||
return serializer_class
|
||||
|
||||
fields_mapping_rule = self.get_dynamic_mapping_fields_mapping_rule()
|
||||
if not fields_mapping_rule:
|
||||
return serializer_class
|
||||
|
||||
attrs = {'dynamic_mapping_fields_mapping_rule': fields_mapping_rule}
|
||||
serializer_class = self._create_serializer_class(base=serializer_class, attrs=attrs)
|
||||
return serializer_class
|
||||
|
||||
|
||||
class SerializerMixin(IncludeDynamicMappingFieldSerializerViewMixin):
|
||||
pass
|
||||
|
||||
|
||||
class ExtraFilterFieldsMixin:
|
||||
"""
|
||||
额外的 api filter
|
||||
|
|
|
@ -7,7 +7,6 @@ from rest_framework.generics import ListAPIView
|
|||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from common.mixins.api import CommonApiMixin
|
||||
from applications.models import Application
|
||||
from applications.api.mixin import ApplicationViewMixin
|
||||
from perms import serializers
|
||||
|
||||
__all__ = [
|
||||
|
@ -15,7 +14,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class UserGroupGrantedApplicationsApi(ApplicationViewMixin, CommonApiMixin, ListAPIView):
|
||||
class UserGroupGrantedApplicationsApi(CommonApiMixin, ListAPIView):
|
||||
"""
|
||||
获取用户组直接授权的应用
|
||||
"""
|
||||
|
|
|
@ -5,7 +5,7 @@ from rest_framework.response import Response
|
|||
|
||||
from common.mixins.api import CommonApiMixin
|
||||
from applications.api.mixin import (
|
||||
SerializeApplicationToTreeNodeMixin, ApplicationViewMixin
|
||||
SerializeApplicationToTreeNodeMixin
|
||||
)
|
||||
from perms import serializers
|
||||
from perms.api.asset.user_permission.mixin import ForAdminMixin, ForUserMixin
|
||||
|
@ -22,7 +22,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class AllGrantedApplicationsMixin(ApplicationViewMixin, CommonApiMixin, ListAPIView):
|
||||
class AllGrantedApplicationsMixin(CommonApiMixin, ListAPIView):
|
||||
only_fields = serializers.ApplicationGrantedSerializer.Meta.only_fields
|
||||
serializer_class = serializers.ApplicationGrantedSerializer
|
||||
filter_fields = ['id', 'name', 'category', 'type', 'comment']
|
||||
|
|
|
@ -6,8 +6,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from assets.models import SystemUser
|
||||
from applications.models import Application
|
||||
from applications.serializers.attrs import get_attrs_field_dynamic_mapping_rules
|
||||
from common.drf.fields import DynamicMappingField
|
||||
from applications.serializers import IncludeDynamicMappingSerializerFieldApplicationSerializerMixin
|
||||
|
||||
__all__ = [
|
||||
'ApplicationGrantedSerializer', 'ApplicationSystemUserSerializer'
|
||||
|
@ -27,13 +26,13 @@ class ApplicationSystemUserSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = fields
|
||||
|
||||
|
||||
class ApplicationGrantedSerializer(serializers.ModelSerializer):
|
||||
class ApplicationGrantedSerializer(IncludeDynamicMappingSerializerFieldApplicationSerializerMixin,
|
||||
serializers.ModelSerializer):
|
||||
"""
|
||||
被授权应用的数据结构
|
||||
"""
|
||||
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||
attrs = DynamicMappingField(mapping_rules=get_attrs_field_dynamic_mapping_rules())
|
||||
|
||||
class Meta:
|
||||
model = Application
|
||||
|
|
|
@ -12,7 +12,6 @@ from common.permissions import IsValidUser, IsOrgAdmin
|
|||
from tickets import serializers
|
||||
from tickets.models import Ticket
|
||||
from tickets.permissions.ticket import IsAssignee, NotClosed
|
||||
from tickets.serializers.ticket.utils import get_dynamic_mapping_fields_mapping_rule_by_view
|
||||
|
||||
|
||||
__all__ = ['TicketViewSet']
|
||||
|
@ -66,6 +65,3 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
|||
def close(self, request, *args, **kwargs):
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule(self):
|
||||
fields_mapping_rule = get_dynamic_mapping_fields_mapping_rule_by_view(view=self)
|
||||
return fields_mapping_rule
|
|
@ -1 +0,0 @@
|
|||
from .ticket import *
|
|
@ -4,7 +4,7 @@ from applications.models import Application
|
|||
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
|
||||
from assets.models import SystemUser
|
||||
from perms.models import ApplicationPermission
|
||||
from tickets.utils import convert_model_data_field_name_to_verbose_name
|
||||
from tickets.utils import convert_model_instance_data_field_name_to_verbose_name
|
||||
|
||||
|
||||
class ConstructDisplayFieldMixin:
|
||||
|
@ -67,11 +67,11 @@ class ConstructBodyMixin:
|
|||
def construct_apply_application_approved_body(self):
|
||||
# 审批信息
|
||||
approve_applications_snapshot = self.meta['approve_applications_snapshot']
|
||||
approve_applications_snapshot_display = convert_model_data_field_name_to_verbose_name(
|
||||
approve_applications_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
|
||||
Application, approve_applications_snapshot
|
||||
)
|
||||
approve_system_users_snapshot = self.meta['approve_system_users_snapshot']
|
||||
approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name(
|
||||
approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
|
||||
SystemUser, approve_system_users_snapshot
|
||||
)
|
||||
approve_date_start = self.meta['approve_date_start']
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.utils.translation import ugettext as __
|
|||
from perms.models import AssetPermission, Action
|
||||
from assets.models import Asset, SystemUser
|
||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
from tickets.utils import convert_model_data_field_name_to_verbose_name
|
||||
from tickets.utils import convert_model_instance_data_field_name_to_verbose_name
|
||||
|
||||
|
||||
class ConstructDisplayFieldMixin:
|
||||
|
@ -69,11 +69,11 @@ class ConstructBodyMixin:
|
|||
|
||||
def construct_apply_asset_approved_body(self):
|
||||
approve_assets_snapshot = self.meta['approve_assets_snapshot']
|
||||
approve_assets_snapshot_display = convert_model_data_field_name_to_verbose_name(
|
||||
approve_assets_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
|
||||
Asset, approve_assets_snapshot
|
||||
)
|
||||
approve_system_users_snapshot = self.meta['approve_system_users_snapshot']
|
||||
approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name(
|
||||
approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
|
||||
SystemUser, approve_system_users_snapshot
|
||||
)
|
||||
approve_actions_display = self.meta['approve_actions_display']
|
||||
|
|
|
@ -19,7 +19,7 @@ class TicketCreatePermissionMixin(meta.CreatePermissionMixin, base.CreatePermiss
|
|||
|
||||
|
||||
class TicketCreateCommentMixin(base.CreateCommentMixin):
|
||||
""" 创建 ticket 评论"""
|
||||
""" 创建 ticket 备注"""
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
from .ticket import *
|
||||
from .meta import *
|
||||
from .utils import *
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import copy
|
||||
from common.drf.fields import IgnoreSensitiveInfoReadOnlyJSONField
|
||||
from tickets import const
|
||||
from . import apply_asset, apply_application, login_confirm
|
||||
from .ticket_type import apply_asset, apply_application, login_confirm
|
||||
|
||||
__all__ = [
|
||||
'get_meta_field_dynamic_mapping_rules',
|
||||
'get_meta_field_mapping_rule_by_view',
|
||||
'meta_field_dynamic_mapping_serializers',
|
||||
]
|
||||
|
||||
#
|
||||
# ticket type
|
||||
# -----------
|
||||
|
||||
|
@ -17,7 +13,6 @@ type_apply_asset = const.TicketTypeChoices.apply_asset.value
|
|||
type_apply_application = const.TicketTypeChoices.apply_application.value
|
||||
type_login_confirm = const.TicketTypeChoices.login_confirm.value
|
||||
|
||||
#
|
||||
# ticket action
|
||||
# -------------
|
||||
|
||||
|
@ -29,13 +24,11 @@ action_reject = const.TicketActionChoices.reject.value
|
|||
action_close = const.TicketActionChoices.close.value
|
||||
|
||||
|
||||
#
|
||||
# defines the dynamic mapping rules for the DynamicMappingField `meta`
|
||||
# --------------------------------------------------------------------
|
||||
# defines `meta` field dynamic mapping serializers
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
__META_FIELD_DYNAMIC_MAPPING_RULES = {
|
||||
'default': IgnoreSensitiveInfoReadOnlyJSONField,
|
||||
meta_field_dynamic_mapping_serializers = {
|
||||
'type': {
|
||||
type_apply_asset: {
|
||||
action_open: apply_asset.ApplySerializer,
|
||||
|
@ -52,22 +45,3 @@ __META_FIELD_DYNAMIC_MAPPING_RULES = {
|
|||
}
|
||||
|
||||
|
||||
# Note:
|
||||
# The dynamic mapping rules of `meta` field is obtained
|
||||
# through call method `get_meta_field_dynamic_mapping_rules`
|
||||
|
||||
def get_meta_field_dynamic_mapping_rules():
|
||||
return copy.deepcopy(__META_FIELD_DYNAMIC_MAPPING_RULES)
|
||||
|
||||
|
||||
#
|
||||
# get `meta dynamic field` mapping rule by `view object`
|
||||
# ------------------------------------------------------
|
||||
|
||||
|
||||
def get_meta_field_mapping_rule_by_view(view):
|
||||
query_type = view.request.query_params.get('type')
|
||||
query_action = view.request.query_params.get('action')
|
||||
action = query_action if query_action else view.action
|
||||
mapping_rule = ['type', query_type, action]
|
||||
return mapping_rule
|
||||
|
|
|
@ -2,12 +2,11 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from rest_framework import serializers
|
||||
from perms.serializers import ActionsField
|
||||
from assets.models import Asset, SystemUser
|
||||
from tickets.models import Ticket
|
||||
from .mixin import BaseApproveSerializerMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ApplyAssetTypeSerializer', 'ApplySerializer', 'ApproveSerializer',
|
||||
'ApplyAssetSerializer', 'ApplySerializer', 'ApproveSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -84,5 +83,5 @@ class ApproveSerializer(BaseApproveSerializerMixin, serializers.Serializer):
|
|||
return system_users_id
|
||||
|
||||
|
||||
class ApplyAssetTypeSerializer(ApplySerializer, ApproveSerializer):
|
||||
class ApplyAssetSerializer(ApplySerializer, ApproveSerializer):
|
||||
pass
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from tickets.models import Ticket
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ApplySerializer',
|
|
@ -2,13 +2,14 @@
|
|||
#
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from common.drf.fields import ReadableHiddenField, DynamicMappingField
|
||||
from common.drf.fields import ReadableHiddenField
|
||||
from common.drf.serializers import DynamicMappingSerializer
|
||||
from orgs.utils import get_org_by_id
|
||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
||||
from users.models import User
|
||||
from tickets import const
|
||||
from tickets.models import Ticket
|
||||
from .meta import get_meta_field_dynamic_mapping_rules
|
||||
from .meta import meta_field_dynamic_mapping_serializers
|
||||
|
||||
__all__ = [
|
||||
'TicketSerializer', 'TicketDisplaySerializer',
|
||||
|
@ -21,7 +22,7 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
|
|||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status'))
|
||||
meta = DynamicMappingField(mapping_rules=get_meta_field_dynamic_mapping_rules())
|
||||
meta = DynamicMappingSerializer(mapping_serializers=meta_field_dynamic_mapping_serializers)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
|
@ -35,6 +36,15 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
|
|||
'body'
|
||||
]
|
||||
|
||||
def get_meta_mapping_path(self, mapping_serializers):
|
||||
view = self.context['view']
|
||||
request = self.context['request']
|
||||
query_type = request.query_params.get('type')
|
||||
query_action = request.query_params.get('action')
|
||||
action = query_action if query_action else view.action
|
||||
mapping_path = ['type', query_type, action]
|
||||
return mapping_path
|
||||
|
||||
|
||||
class TicketDisplaySerializer(TicketSerializer):
|
||||
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
from .meta import get_meta_field_mapping_rule_by_view
|
||||
|
||||
__all__ = [
|
||||
'get_dynamic_mapping_fields_mapping_rule_by_view'
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# get `dynamic fields` mapping rule by `view object`
|
||||
# ----------------------------------------------------
|
||||
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule_by_view(view):
|
||||
return {
|
||||
'meta': get_meta_field_mapping_rule_by_view(view=view)
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
"""
|
||||
说明:
|
||||
View 获取 serializer_class 的架构设计
|
||||
|
||||
问题:
|
||||
View 所需的 Serializer Class 中有一个字段,字段的类型并不固定,
|
||||
而是由 View 的行为 (比如 type, action) 来决定的.
|
||||
使用 View 默认的 get_serializer_class 方法不能实现,因为序列类在被定义的时候,其字段及类型已经固定,
|
||||
所以需要一种机制来动态修改序列类中的字段及其类型,即,MetaClass 元类
|
||||
|
||||
|
||||
例如:
|
||||
class MetaASerializer(serializers.Serializer):
|
||||
name = serializers.CharField(label='Name')
|
||||
|
||||
|
||||
class MetaBSerializer(serializers.Serializer):
|
||||
age = serializers.IntegerField(label='Age')
|
||||
|
||||
|
||||
class Serializer(serializers.Serializer):
|
||||
meta = serializers.JSONField()
|
||||
|
||||
当 view 获取 serializer 时,无论 action 是什么, 获取到的 Serializer.meta 字段始终是
|
||||
serializers.JSONField() 类型
|
||||
|
||||
但我们希望:
|
||||
当 view.action = A 时,获取到的 Serializer.meta 是 MetaASerializerMetaASerializer()
|
||||
当 view.action = B 时,获取到的 Serializer.meta 是 MetaBSerializerMetaASerializer()
|
||||
|
||||
分析:
|
||||
问题关键在于数据映射规则的定义和匹配,
|
||||
即, 用 View 给定的规则动态去匹配 Serializer Class 中定义的规则, 从而创建出想要的序列类
|
||||
|
||||
当然, 使用 dict 可以很好的解决规则定义问题,但要直接进行匹配, 操作起来比较复杂
|
||||
所以, 决定使用以下方案实现, 即, dict, dict-> tree 数据类型转化, tree 搜索:
|
||||
Serializer Class 中规则的定义使用 dict,
|
||||
View 中指定的规则也使用 dict,
|
||||
MetaClass 中进行规则匹配的过程使用 dict tree, 即将给定的 dic 转换为 tree 的数据结构,再进行匹配,
|
||||
* dict -> tree 的转化使用 `data-tree` 库来实现
|
||||
|
||||
|
||||
方案:
|
||||
view:
|
||||
使用元类 MetaClass: 在 View 中动态创建所需要的 Serializer Class
|
||||
|
||||
serializer:
|
||||
实现 DynamicMappingField: 序列类的动态映射字段, 用来定义字段的映射规则
|
||||
|
||||
实现 IncludeDynamicMappingFieldSerializerMetaClass:
|
||||
在 View 中用来动态创建 Serializer Class.
|
||||
即, 用 View 中给定的规则,去匹配 Serializer Class 里每一个 DynamicMappingField 字段定义的规则,
|
||||
基于 bases, 创建并返回新的 Serializer Class
|
||||
|
||||
实现 IncludeDynamicMappingFieldSerializerViewMixin:
|
||||
实现动态创建 Serializer Class 的逻辑,
|
||||
同时定义获取 Serializer Class 中所有 DynamicMappingField 字段匹配规则的方法
|
||||
=>
|
||||
def get_dynamic_mapping_fields_mapping_rule(self):
|
||||
return {
|
||||
'dynamic_mapping_field_name1': `mapping_path`,
|
||||
'dynamic_mapping_field_name2': `mapping_path',
|
||||
}
|
||||
供 View 子类重写
|
||||
|
||||
实现:
|
||||
请看 ./serializer.py 和 ./serializer.py
|
||||
|
||||
"""
|
|
@ -1,226 +0,0 @@
|
|||
import copy
|
||||
import data_tree
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
#
|
||||
# IncludeDynamicMappingFieldSerializerMetaClass
|
||||
# ---------------------------------------------
|
||||
|
||||
class IncludeDynamicMappingFieldSerializerMetaClass(serializers.SerializerMetaclass, type):
|
||||
"""
|
||||
SerializerMetaClass: 动态创建包含 `common.drf.fields.DynamicMappingField` 字段的 `SerializerClass`
|
||||
|
||||
* Process only fields of type `DynamicMappingField` in `_declared_fields`
|
||||
* 只处理 `_declared_fields` 中类型为 `DynamicMappingField` 的字段
|
||||
|
||||
根据 `attrs['dynamic_mapping_fields_mapping_rule']` 中指定的 `fields_mapping_rule`,
|
||||
从 `DynamicMappingField` 中匹配出满足给定规则的字段, 并使用匹配到的字段替换自身的 `DynamicMappingField`
|
||||
|
||||
* 注意: 如果未能根据给定的匹配规则获取到对应的字段,先获取与给定规则同级的 `default` 字段,
|
||||
如果仍未获取到,则再获取 `DynamicMappingField`中定义的最外层的 `default` 字段
|
||||
|
||||
* 说明: 如果获取到的不是 `serializers.Field` 类型, 则返回 `DynamicMappingField()`
|
||||
|
||||
For example, define attrs['dynamic_mapping_fields_mapping_rule']:
|
||||
|
||||
mapping_rules = {
|
||||
'default': serializer.JSONField,
|
||||
'type': {
|
||||
'apply_asset': {
|
||||
'default': serializer.ChoiceField(),
|
||||
'get': serializer.CharField()
|
||||
}
|
||||
}
|
||||
}
|
||||
meta = DynamicMappingField(mapping_rules=mapping_rules)
|
||||
|
||||
dynamic_mapping_fields_mapping_rule = {'meta': ['type', 'apply_asset', 'get'],}
|
||||
=> Got `serializer.CharField()`
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset.get',}}
|
||||
=> Got `serializer.CharField()`
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset.',}}
|
||||
=> Got serializer.ChoiceField(),
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset.xxx',}}
|
||||
=> Got `serializer.ChoiceField()`
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset.get.xxx',}}
|
||||
=> Got `serializer.JSONField()`
|
||||
* or *
|
||||
dynamic_mapping_fields_mapping_rule = {{'meta': 'type.apply_asset',}}
|
||||
=> Got `{'get': {}}`, type is not `serializers.Field`, So `meta` is `DynamicMappingField()`
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_dynamic_mapping_fields(mcs, bases, attrs):
|
||||
fields = {}
|
||||
|
||||
fields_mapping_rules = attrs.get('dynamic_mapping_fields_mapping_rule')
|
||||
|
||||
assert isinstance(fields_mapping_rules, dict), (
|
||||
'`dynamic_mapping_fields_mapping_rule` must be `dict` type , but get `{}`'
|
||||
''.format(type(fields_mapping_rules))
|
||||
)
|
||||
|
||||
fields_mapping_rules = copy.deepcopy(fields_mapping_rules)
|
||||
|
||||
declared_fields = mcs._get_declared_fields(bases, attrs)
|
||||
|
||||
for field_name, field_mapping_rule in fields_mapping_rules.items():
|
||||
|
||||
assert isinstance(field_mapping_rule, (list, str)), (
|
||||
'`dynamic_mapping_fields_mapping_rule.field_mapping_rule` '
|
||||
'- can be either a list of keys, or a delimited string. '
|
||||
'Such as: `["type", "apply_asset", "get"]` or `type.apply_asset.get` '
|
||||
'but, get type is `{}`, `{}`'
|
||||
''.format(type(field_mapping_rule), field_mapping_rule)
|
||||
)
|
||||
|
||||
if field_name not in declared_fields.keys():
|
||||
continue
|
||||
|
||||
declared_field = declared_fields[field_name]
|
||||
if not isinstance(declared_field, DynamicMappingField):
|
||||
continue
|
||||
|
||||
dynamic_field = declared_field
|
||||
|
||||
mapping_tree = dynamic_field.mapping_tree.copy()
|
||||
|
||||
def get_field(rule):
|
||||
return mapping_tree.get(arg_path=rule)
|
||||
|
||||
if isinstance(field_mapping_rule, str):
|
||||
field_mapping_rule = field_mapping_rule.split('.')
|
||||
|
||||
field_mapping_rule[-1] = field_mapping_rule[-1] or 'default'
|
||||
|
||||
field = get_field(rule=field_mapping_rule)
|
||||
|
||||
if not field:
|
||||
field_mapping_rule[-1] = 'default'
|
||||
field = get_field(rule=field_mapping_rule)
|
||||
|
||||
if field is None:
|
||||
field_mapping_rule = ['default']
|
||||
field = get_field(rule=field_mapping_rule)
|
||||
|
||||
if isinstance(field, type):
|
||||
field = field()
|
||||
|
||||
if not isinstance(field, serializers.Field):
|
||||
continue
|
||||
|
||||
fields[field_name] = field
|
||||
|
||||
return fields
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
dynamic_mapping_fields = mcs.get_dynamic_mapping_fields(bases, attrs)
|
||||
attrs.update(dynamic_mapping_fields)
|
||||
return super().__new__(mcs, name, bases, attrs)
|
||||
|
||||
#
|
||||
# DynamicMappingField
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
class DynamicMappingField(serializers.Field):
|
||||
""" 一个根据用户行为而动态匹配的字段 """
|
||||
|
||||
def __init__(self, mapping_rules, *args, **kwargs):
|
||||
|
||||
assert isinstance(mapping_rules, dict), (
|
||||
'`mapping_rule` argument expect type `dict`, gut get `{}`'
|
||||
''.format(type(mapping_rules))
|
||||
)
|
||||
|
||||
assert 'default' in mapping_rules, (
|
||||
"mapping_rules['default'] is a required, but only get `{}`"
|
||||
"".format(list(mapping_rules.keys()))
|
||||
)
|
||||
|
||||
self.mapping_rules = mapping_rules
|
||||
|
||||
self.mapping_tree = self._build_mapping_tree()
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _build_mapping_tree(self):
|
||||
tree = data_tree.Data_tree_node(arg_data=self.mapping_rules)
|
||||
return tree
|
||||
|
||||
def to_internal_value(self, data):
|
||||
""" 实际是一个虚拟字段所以不返回任何值 """
|
||||
pass
|
||||
|
||||
def to_representation(self, value):
|
||||
""" 实际是一个虚拟字段所以不返回任何值 """
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# Test data
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
# ticket type
|
||||
class ApplyAssetSerializer(serializers.Serializer):
|
||||
apply_asset = serializers.CharField(label='Apply Asset')
|
||||
|
||||
|
||||
class ApproveAssetSerializer(serializers.Serializer):
|
||||
approve_asset = serializers.CharField(label='Approve Asset')
|
||||
|
||||
|
||||
class ApplyApplicationSerializer(serializers.Serializer):
|
||||
apply_application = serializers.CharField(label='Application')
|
||||
|
||||
|
||||
class LoginConfirmSerializer(serializers.Serializer):
|
||||
login_ip = serializers.IPAddressField()
|
||||
|
||||
|
||||
class LoginTimesSerializer(serializers.Serializer):
|
||||
login_times = serializers.IntegerField()
|
||||
|
||||
|
||||
# ticket category
|
||||
class ApplySerializer(serializers.Serializer):
|
||||
apply_datetime = serializers.DateTimeField()
|
||||
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
login_datetime = serializers.DateTimeField()
|
||||
|
||||
|
||||
meta_mapping_rules = {
|
||||
'default': serializers.JSONField(),
|
||||
'type': {
|
||||
'apply_asset': {
|
||||
'default': serializers.CharField(label='default'),
|
||||
'get': ApplyAssetSerializer,
|
||||
'post': ApproveAssetSerializer,
|
||||
},
|
||||
'apply_application': ApplyApplicationSerializer,
|
||||
'login_confirm': LoginConfirmSerializer,
|
||||
'login_times': LoginTimesSerializer
|
||||
},
|
||||
'category': {
|
||||
'apply': ApplySerializer,
|
||||
'login': LoginSerializer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TicketSerializer(serializers.Serializer):
|
||||
title = serializers.CharField(label='Title')
|
||||
type = serializers.ChoiceField(choices=('apply_asset', 'apply_application'), label='Type')
|
||||
meta1 = DynamicMappingField(mapping_rules=meta_mapping_rules)
|
||||
meta2 = DynamicMappingField(mapping_rules=meta_mapping_rules)
|
||||
meta3 = DynamicMappingField(mapping_rules=meta_mapping_rules)
|
||||
meta4 = DynamicMappingField(mapping_rules=meta_mapping_rules)
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
from tickets.tests.design.architecture_for_view_to_serializer_mapping.serializer import (
|
||||
IncludeDynamicMappingFieldSerializerMetaClass, TicketSerializer
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# IncludeDynamicMappingFieldSerializerViewMixin
|
||||
# ---------------------------------------------
|
||||
|
||||
|
||||
class IncludeDynamicMappingFieldSerializerViewMixin:
|
||||
"""
|
||||
动态创建 `view` 使用的 `serializer_class`,
|
||||
|
||||
根据用户请求行为的不同, 构造出获取 `serializer_class` 中 `common.drf.fields.DynamicMappingField` 字段
|
||||
的映射规则, 并通过 `IncludeDynamicMappingFieldSerializerMetaClass` 元类,
|
||||
基于父类的 `serializer_class` 和 构造出的映射规则 `dynamic_mapping_fields_mapping_rule`
|
||||
创建出满足要求的新的 `serializer_class`
|
||||
|
||||
* 重写 get_dynamic_mapping_fields_mapping_rule 方法:
|
||||
|
||||
For example,
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule(self):
|
||||
return {'meta': ['type', 'apply_asset', 'get']
|
||||
|
||||
"""
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule(self):
|
||||
"""
|
||||
return:
|
||||
{
|
||||
'meta': ['type', 'apply_asset', 'get'],
|
||||
'meta2': 'category.login'
|
||||
}
|
||||
"""
|
||||
print(self)
|
||||
return {
|
||||
'meta1': ['type', 'apply_asset', 'getX', 'asdf'],
|
||||
'meta2': 'category.login',
|
||||
'meta3': 'type.apply_asset.',
|
||||
'meta4': 'category.apply'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _create_serializer_class(base, attrs):
|
||||
serializer_class = IncludeDynamicMappingFieldSerializerMetaClass(
|
||||
base.__name__, (base, ), attrs
|
||||
)
|
||||
return serializer_class
|
||||
|
||||
def get_serializer_class(self):
|
||||
serializer_class = super().get_serializer_class()
|
||||
|
||||
fields_mapping_rule = self.get_dynamic_mapping_fields_mapping_rule()
|
||||
if not fields_mapping_rule:
|
||||
return serializer_class
|
||||
|
||||
attrs = {'dynamic_mapping_fields_mapping_rule': fields_mapping_rule}
|
||||
serializer_class = self._create_serializer_class(base=serializer_class, attrs=attrs)
|
||||
return serializer_class
|
||||
|
||||
|
||||
#
|
||||
# Test data
|
||||
# ---------
|
||||
|
||||
|
||||
class GenericViewSet(object):
|
||||
|
||||
def get_serializer_class(self):
|
||||
return TicketSerializer
|
||||
|
||||
|
||||
class TicketViewSet(IncludeDynamicMappingFieldSerializerViewMixin, GenericViewSet):
|
||||
pass
|
||||
|
||||
|
||||
view = TicketViewSet()
|
||||
|
||||
_serializer_class = view.get_serializer_class()
|
||||
|
||||
_serializer = _serializer_class()
|
||||
|
||||
print(_serializer_class)
|
||||
print(_serializer)
|
|
@ -1,196 +0,0 @@
|
|||
# 测试通过 view 动态创建 serializer
|
||||
|
||||
|
||||
class BaseSerializerMetaClass(type):
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
attrs.update({'color': 'blank'})
|
||||
return super().__new__(mcs, name, bases, attrs)
|
||||
|
||||
|
||||
class BaseSerializer(metaclass=BaseSerializerMetaClass):
|
||||
x_id = 'id_value'
|
||||
|
||||
|
||||
class Serializer(BaseSerializer):
|
||||
|
||||
x_name = 'name_value'
|
||||
x_hobby = {
|
||||
'music': 'chinese',
|
||||
'ball': 'basketball'
|
||||
}
|
||||
x_age = {
|
||||
'real': 19,
|
||||
'fake': 27
|
||||
}
|
||||
|
||||
|
||||
# custom metaclass
|
||||
class SerializerMetaClass(BaseSerializerMetaClass, type):
|
||||
|
||||
@classmethod
|
||||
def _get_declared_x_attr_value(mcs, x_types, attr_name, attr_value):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _get_declared_x_attrs(mcs, bases, attrs):
|
||||
x_types = attrs['view'].x_types
|
||||
|
||||
bases_attrs = {}
|
||||
for base in bases:
|
||||
for k in dir(base):
|
||||
if not k.startswith('x_'):
|
||||
continue
|
||||
v = getattr(base, k)
|
||||
if isinstance(v, str):
|
||||
bases_attrs[k] = v
|
||||
continue
|
||||
if isinstance(v, dict):
|
||||
v = mcs._get_declared_x_attr_value( x_types, k, v)
|
||||
bases_attrs[k] = v
|
||||
attrs.update(bases_attrs)
|
||||
return attrs
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
attrs = mcs._get_declared_x_attrs(bases, attrs)
|
||||
return super().__new__(mcs, name, bases, attrs)
|
||||
|
||||
|
||||
class View(object):
|
||||
x_types = ['x_age', 'fake']
|
||||
serializer_class = Serializer
|
||||
|
||||
def get_serializer_class(self):
|
||||
return self.serializer_class
|
||||
|
||||
def build_serializer_class(self):
|
||||
serializer_class = self.get_serializer_class()
|
||||
serializer_class = SerializerMetaClass(
|
||||
serializer_class.__name__, (serializer_class,), {'view': self}
|
||||
)
|
||||
return serializer_class
|
||||
|
||||
|
||||
view = View()
|
||||
|
||||
|
||||
serializer = view.build_serializer_class()
|
||||
print('End!')
|
||||
|
||||
#
|
||||
from rest_framework.serializers import SerializerMetaclass
|
||||
|
||||
|
||||
data = {
|
||||
'meta': {
|
||||
'type': {
|
||||
'apply_asset': {
|
||||
'get': 'get',
|
||||
'post': 'post'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_value(keys_dict, data_dict):
|
||||
|
||||
def _get_value(_key_list, _data_dict):
|
||||
if len(_key_list) == 0:
|
||||
return _data_dict
|
||||
for i, key in enumerate(_key_list):
|
||||
_keys = _key_list[i+1:]
|
||||
__data_dict = _data_dict.get(key)
|
||||
if __data_dict is None:
|
||||
return _data_dict
|
||||
if not isinstance(__data_dict, dict):
|
||||
return __data_dict
|
||||
return _get_value(_keys, __data_dict)
|
||||
|
||||
_values_dict = {}
|
||||
for field, keys in keys_dict.items():
|
||||
keys.insert(0, field)
|
||||
_values_dict[field] = _get_value(keys, data_dict)
|
||||
return _values_dict
|
||||
|
||||
|
||||
keys_dict_list = {
|
||||
'meta': ['type', 'apply_asset', 'get']
|
||||
}
|
||||
values_dict = get_value(keys_dict_list, data)
|
||||
print(values_dict)
|
||||
|
||||
keys_dict_list = {
|
||||
'meta': ['type', 'apply_asset', 'post']
|
||||
}
|
||||
values_dict = get_value(keys_dict_list, data)
|
||||
print(values_dict)
|
||||
|
||||
keys_dict_list = {
|
||||
'meta': ['type', 'apply_asset', 'post', 'dog']
|
||||
}
|
||||
values_dict = get_value(keys_dict_list, data)
|
||||
print(values_dict)
|
||||
|
||||
keys_dict_list = {
|
||||
'meta': ['type', 'apply_asset', 'dog']
|
||||
}
|
||||
values_dict = get_value(keys_dict_list, data)
|
||||
print(values_dict)
|
||||
|
||||
|
||||
#
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.a = 'A'
|
||||
|
||||
|
||||
get_action_serializer = 'GETSerializer'
|
||||
post_action_serializer = 'POSTSerializer'
|
||||
apply_action_serializer = A()
|
||||
|
||||
apply_asset_tree_serializer = {
|
||||
'get': get_action_serializer,
|
||||
'post': post_action_serializer,
|
||||
'apply': apply_action_serializer
|
||||
}
|
||||
|
||||
type_tree_serializer = {
|
||||
'apply_asset': apply_asset_tree_serializer,
|
||||
}
|
||||
|
||||
meta_tree_serializer = {
|
||||
'type': type_tree_serializer,
|
||||
}
|
||||
|
||||
json_fields_serializer_mapping = {
|
||||
'meta': meta_tree_serializer
|
||||
}
|
||||
|
||||
|
||||
def data_dict_to_tree(data_dict):
|
||||
import data_tree
|
||||
t = data_tree.Data_tree_node(arg_data=data_dict)
|
||||
return t
|
||||
|
||||
|
||||
tree = data_dict_to_tree(json_fields_serializer_mapping)
|
||||
|
||||
|
||||
def get_tree_node(t, path):
|
||||
return t.get(path, arg_default_value_to_return='Not Found')
|
||||
|
||||
|
||||
node = get_tree_node(tree, 'meta.type.apply_asset.get')
|
||||
print(node)
|
||||
|
||||
node = get_tree_node(tree, 'meta.type.apply_asset.post')
|
||||
print(node)
|
||||
|
||||
node = get_tree_node(tree, 'meta.type.apply_asset.apply')
|
||||
print(node)
|
||||
|
||||
|
||||
node = get_tree_node(tree, 'meta.type.apply_asset.xxxx')
|
||||
print(node)
|
|
@ -11,30 +11,23 @@ from . import const
|
|||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def convert_model_data_field_name_to_verbose_name(model, name_data):
|
||||
"""将Model以field_name为key的数据转换为以field_verbose_name为key的数据"""
|
||||
if isinstance(name_data, dict):
|
||||
name_data = [name_data]
|
||||
|
||||
model_fields_name_verbose_name_mapping = {
|
||||
def get_model_field_verbose_name(model, field_name):
|
||||
field_name_field_verbose_name_mapping = {
|
||||
field.name: field.verbose_name for field in model._meta.fields
|
||||
}
|
||||
field_name = field_name.split('__', 1)[0]
|
||||
field_verbose_name = field_name_field_verbose_name_mapping.get(field_name, field_name)
|
||||
return field_verbose_name
|
||||
|
||||
def get_verbose_name(field_name):
|
||||
verbose_name = model_fields_name_verbose_name_mapping.get(field_name)
|
||||
if not verbose_name:
|
||||
other_name = field_name.split('__', 1)[0]
|
||||
verbose_name = model_fields_name_verbose_name_mapping.get(other_name)
|
||||
if not verbose_name:
|
||||
verbose_name = field_name
|
||||
return verbose_name
|
||||
|
||||
verbose_name_data = [
|
||||
{get_verbose_name(name): value for name, value in d.items()}
|
||||
for d in name_data
|
||||
def convert_model_instance_data_field_name_to_verbose_name(model, data):
|
||||
if isinstance(data, dict):
|
||||
data = [data]
|
||||
converted_data = [
|
||||
{get_model_field_verbose_name(model, name): value for name, value in d.items()}
|
||||
for d in data
|
||||
]
|
||||
|
||||
return verbose_name_data
|
||||
return converted_data
|
||||
|
||||
|
||||
def send_ticket_applied_mail_to_assignees(ticket, assignees):
|
||||
|
|
Loading…
Reference in New Issue