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 ..hands import IsOrgAdminOrAppUser
|
||||||
from .. import models, serializers
|
from .. import models, serializers
|
||||||
from .mixin import ApplicationViewMixin
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ApplicationViewSet']
|
__all__ = ['ApplicationViewSet']
|
||||||
|
|
||||||
|
|
||||||
class ApplicationViewSet(ApplicationViewMixin, OrgBulkModelViewSet):
|
class ApplicationViewSet(OrgBulkModelViewSet):
|
||||||
model = models.Application
|
model = models.Application
|
||||||
filter_fields = ('name', 'type', 'category')
|
filter_fields = ('name', 'type', 'category')
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
|
|
|
@ -1,16 +1,7 @@
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
from ..serializers.utils import get_dynamic_mapping_fields_mapping_rule_by_view
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ApplicationViewMixin', 'SerializeApplicationToTreeNodeMixin']
|
__all__ = ['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
|
|
||||||
|
|
||||||
|
|
||||||
class SerializeApplicationToTreeNodeMixin:
|
class SerializeApplicationToTreeNodeMixin:
|
||||||
|
|
|
@ -4,20 +4,37 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from common.drf.fields import DynamicMappingField
|
from common.drf.serializers import DynamicMappingSerializer
|
||||||
from .attrs import get_attrs_field_dynamic_mapping_rules
|
from .attrs import attrs_field_dynamic_mapping_serializers
|
||||||
|
|
||||||
from .. import models
|
from .. import models
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ApplicationSerializer',
|
'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'))
|
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
|
||||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||||
attrs = DynamicMappingField(mapping_rules=get_attrs_field_dynamic_mapping_rules())
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Application
|
model = models.Application
|
||||||
|
@ -33,3 +50,4 @@ class ApplicationSerializer(BulkOrgResourceModelSerializer):
|
||||||
_attrs = self.instance.attrs if self.instance else {}
|
_attrs = self.instance.attrs if self.instance else {}
|
||||||
_attrs.update(attrs)
|
_attrs.update(attrs)
|
||||||
return _attrs
|
return _attrs
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from ..category import RemoteAppSerializer
|
from ..application_category import RemoteAppSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ChromeSerializer']
|
__all__ = ['ChromeSerializer']
|
|
@ -2,7 +2,7 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from ..category import RemoteAppSerializer
|
from ..application_category import RemoteAppSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['CustomSerializer']
|
__all__ = ['CustomSerializer']
|
|
@ -1,4 +1,4 @@
|
||||||
from ..category import CloudSerializer
|
from ..application_category import CloudSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['K8SSerializer']
|
__all__ = ['K8SSerializer']
|
|
@ -1,7 +1,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from ..category import DBSerializer
|
from ..application_category import DBSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['MySQLSerializer']
|
__all__ = ['MySQLSerializer']
|
|
@ -1,7 +1,7 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from ..category import RemoteAppSerializer
|
from ..application_category import RemoteAppSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['MySQLWorkbenchSerializer']
|
__all__ = ['MySQLWorkbenchSerializer']
|
|
@ -1,7 +1,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from ..category import DBSerializer
|
from ..application_category import DBSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['OracleSerializer']
|
__all__ = ['OracleSerializer']
|
|
@ -1,7 +1,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from ..category import DBSerializer
|
from ..application_category import DBSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['PostgreSerializer']
|
__all__ = ['PostgreSerializer']
|
|
@ -1,7 +1,7 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from ..category import RemoteAppSerializer
|
from ..application_category import RemoteAppSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['VMwareClientSerializer']
|
__all__ = ['VMwareClientSerializer']
|
|
@ -1,12 +1,11 @@
|
||||||
import copy
|
from rest_framework import serializers
|
||||||
from applications import const
|
from applications import const
|
||||||
from common.drf.fields import IgnoreSensitiveInfoReadOnlyJSONField
|
from . import application_category, application_type
|
||||||
from . import category, type as application_type
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'get_attrs_field_dynamic_mapping_rules', 'get_attrs_field_mapping_rule_by_view',
|
'attrs_field_dynamic_mapping_serializers',
|
||||||
'get_serializer_by_application_type',
|
'get_serializer_class_by_application_type',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,16 +34,15 @@ type_custom = const.ApplicationTypeChoices.custom.value
|
||||||
type_k8s = const.ApplicationTypeChoices.k8s.value
|
type_k8s = const.ApplicationTypeChoices.k8s.value
|
||||||
|
|
||||||
|
|
||||||
# define `attrs` field `DynamicMappingField` mapping_rules
|
# define `attrs` field `dynamic mapping serializers`
|
||||||
# -----------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
__ATTRS_FIELD_DYNAMIC_MAPPING_RULES = {
|
attrs_field_dynamic_mapping_serializers = {
|
||||||
'default': IgnoreSensitiveInfoReadOnlyJSONField,
|
|
||||||
'category': {
|
'category': {
|
||||||
category_db: category.DBSerializer,
|
category_db: application_category.DBSerializer,
|
||||||
category_remote_app: category.RemoteAppSerializer,
|
category_remote_app: application_category.RemoteAppSerializer,
|
||||||
category_cloud: category.CloudSerializer,
|
category_cloud: application_category.CloudSerializer,
|
||||||
},
|
},
|
||||||
'type': {
|
'type': {
|
||||||
# db
|
# db
|
||||||
|
@ -63,32 +61,5 @@ __ATTRS_FIELD_DYNAMIC_MAPPING_RULES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Note:
|
def get_serializer_class_by_application_type(_application_type):
|
||||||
# The dynamic mapping rules of `attrs` field is obtained
|
return attrs_field_dynamic_mapping_serializers['type'].get(_application_type)
|
||||||
# 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)
|
|
||||||
|
|
|
@ -31,8 +31,8 @@ class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
|
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
|
||||||
"""
|
"""
|
||||||
from .attrs import get_serializer_by_application_type
|
from .attrs import get_serializer_class_by_application_type
|
||||||
serializer_class = get_serializer_by_application_type(obj.type)
|
serializer_class = get_serializer_class_by_application_type(obj.type)
|
||||||
fields = serializer_class().get_fields()
|
fields = serializer_class().get_fields()
|
||||||
|
|
||||||
parameters = [obj.type]
|
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__ = [
|
__all__ = [
|
||||||
'DynamicMappingField', 'ReadableHiddenField',
|
'ReadableHiddenField', 'CustomMetaDictField',
|
||||||
'CustomMetaDictField', 'IgnoreSensitiveInfoReadOnlyJSONField',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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
|
# ReadableHiddenField
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
|
|
|
@ -5,133 +5,98 @@ from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework_bulk.serializers import BulkListSerializer
|
from rest_framework_bulk.serializers import BulkListSerializer
|
||||||
|
|
||||||
from common.mixins import BulkListSerializerMixin
|
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.mixins.serializers import BulkSerializerMixin
|
||||||
from common.utils import QuickLookupDict
|
from common.utils import QuickLookupDict
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'IncludeDynamicMappingFieldSerializerMetaClass',
|
'DynamicMappingSerializer',
|
||||||
'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer'
|
'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
# DynamicMappingSerializer
|
||||||
# IncludeDynamicMappingFieldSerializerMetaClass
|
# ------------------------
|
||||||
# ---------------------------------------------
|
|
||||||
|
|
||||||
class IncludeDynamicMappingFieldSerializerMetaClass(serializers.SerializerMetaclass, type):
|
|
||||||
"""
|
|
||||||
SerializerMetaClass: 动态创建包含 `common.drf.fields.DynamicMappingField` 字段的 `SerializerClass`
|
|
||||||
|
|
||||||
* Process only fields of type `DynamicMappingField` in `_declared_fields`
|
class DynamicMappingSerializer(serializers.Serializer):
|
||||||
* 只处理 `_declared_fields` 中类型为 `DynamicMappingField` 的字段
|
data_type_error_messages = 'Expect get instance of type `{}`, but got instance type of `{}`'
|
||||||
|
|
||||||
根据 `attrs['dynamic_mapping_fields_mapping_rule']` 中指定的 `fields_mapping_rule`,
|
def __init__(self, mapping_serializers=None, get_mapping_serializers_method_name=None,
|
||||||
从 `DynamicMappingField` 中匹配出满足给定规则的字段, 并使用匹配到的字段替换自身的 `DynamicMappingField`
|
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` 字段,
|
def bind(self, field_name, parent):
|
||||||
如果仍未获取到,则再获取 `DynamicMappingField`中定义的最外层的 `default` 字段
|
# 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 = {
|
def get_mapping_serializers(self):
|
||||||
'default': serializer.JSONField,
|
if self.mapping_serializers is not None:
|
||||||
'type': {
|
return self.mapping_serializers
|
||||||
'apply_asset': {
|
method = getattr(self.parent, self.get_mapping_serializers_method_name)
|
||||||
'default': serializer.ChoiceField(),
|
return method()
|
||||||
'get': serializer.CharField()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
meta = DynamicMappingField(mapping_rules=mapping_rules)
|
|
||||||
|
|
||||||
dynamic_mapping_fields_mapping_rule = {'meta': ['type', 'apply_asset', 'get'],}
|
def get_mapping_path(self, mapping_serializers):
|
||||||
=> Got `serializer.CharField()`
|
method = getattr(self.parent, self.get_mapping_path_method_name)
|
||||||
* or *
|
return method(mapping_serializers)
|
||||||
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
|
@staticmethod
|
||||||
def get_dynamic_mapping_fields(mcs, bases, attrs):
|
def mapping(mapping_serializers, mapping_path):
|
||||||
fields = {}
|
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`
|
def get_mapped_serializer(self):
|
||||||
fields_mapping_rule = attrs.get('dynamic_mapping_fields_mapping_rule')
|
mapping_serializers = self.get_mapping_serializers()
|
||||||
|
assert isinstance(mapping_serializers, dict), (
|
||||||
# check `fields_mapping_rule` type
|
self.data_type_error_messages.format('dict', type(mapping_serializers))
|
||||||
assert isinstance(fields_mapping_rule, dict), (
|
|
||||||
'`dynamic_mapping_fields_mapping_rule` must be `dict` type , but get `{}`'
|
|
||||||
''.format(type(fields_mapping_rule))
|
|
||||||
)
|
)
|
||||||
|
mapping_path = self.get_mapping_path(mapping_serializers)
|
||||||
# get `serializer class` declared fields
|
assert isinstance(mapping_path, list), (
|
||||||
declared_fields = mcs._get_declared_fields(bases, attrs)
|
self.data_type_error_messages.format('list', type(mapping_path))
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
|
serializer = self.mapping(mapping_serializers, mapping_path)
|
||||||
|
return serializer
|
||||||
|
|
||||||
if isinstance(field_mapping_rule, str):
|
@cached_property
|
||||||
field_mapping_rule = field_mapping_rule.split('.')
|
def mapped_serializer(self):
|
||||||
|
serializer = self.get_mapped_serializer()
|
||||||
# construct `field mapping rules` sequence list
|
if serializer is None:
|
||||||
field_mapping_rules = [
|
serializer = self.default_serializer
|
||||||
field_mapping_rule,
|
if isinstance(serializer, type):
|
||||||
copy.deepcopy(field_mapping_rule)[:-1] + ['default'],
|
serializer = serializer()
|
||||||
['default']
|
return serializer
|
||||||
]
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
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
|
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
|
# Other Serializer
|
||||||
|
|
|
@ -13,7 +13,6 @@ from rest_framework.response import Response
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
|
from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
|
||||||
from common.drf.serializers import IncludeDynamicMappingFieldSerializerMetaClass
|
|
||||||
from ..utils import lazyproperty
|
from ..utils import lazyproperty
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -29,12 +28,11 @@ class JSONResponseMixin(object):
|
||||||
return JsonResponse(context)
|
return JsonResponse(context)
|
||||||
|
|
||||||
|
|
||||||
#
|
# SerializerMixin
|
||||||
# GenericSerializerMixin
|
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
|
|
||||||
class GenericSerializerMixin:
|
class SerializerMixin:
|
||||||
""" 根据用户请求动作的不同,获取不同的 `serializer_class `"""
|
""" 根据用户请求动作的不同,获取不同的 `serializer_class `"""
|
||||||
|
|
||||||
serializer_classes = None
|
serializer_classes = None
|
||||||
|
@ -65,65 +63,6 @@ class GenericSerializerMixin:
|
||||||
return serializer_class
|
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:
|
class ExtraFilterFieldsMixin:
|
||||||
"""
|
"""
|
||||||
额外的 api filter
|
额外的 api filter
|
||||||
|
|
|
@ -7,7 +7,6 @@ from rest_framework.generics import ListAPIView
|
||||||
from common.permissions import IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdminOrAppUser
|
||||||
from common.mixins.api import CommonApiMixin
|
from common.mixins.api import CommonApiMixin
|
||||||
from applications.models import Application
|
from applications.models import Application
|
||||||
from applications.api.mixin import ApplicationViewMixin
|
|
||||||
from perms import serializers
|
from perms import serializers
|
||||||
|
|
||||||
__all__ = [
|
__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 common.mixins.api import CommonApiMixin
|
||||||
from applications.api.mixin import (
|
from applications.api.mixin import (
|
||||||
SerializeApplicationToTreeNodeMixin, ApplicationViewMixin
|
SerializeApplicationToTreeNodeMixin
|
||||||
)
|
)
|
||||||
from perms import serializers
|
from perms import serializers
|
||||||
from perms.api.asset.user_permission.mixin import ForAdminMixin, ForUserMixin
|
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
|
only_fields = serializers.ApplicationGrantedSerializer.Meta.only_fields
|
||||||
serializer_class = serializers.ApplicationGrantedSerializer
|
serializer_class = serializers.ApplicationGrantedSerializer
|
||||||
filter_fields = ['id', 'name', 'category', 'type', 'comment']
|
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 assets.models import SystemUser
|
||||||
from applications.models import Application
|
from applications.models import Application
|
||||||
from applications.serializers.attrs import get_attrs_field_dynamic_mapping_rules
|
from applications.serializers import IncludeDynamicMappingSerializerFieldApplicationSerializerMixin
|
||||||
from common.drf.fields import DynamicMappingField
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ApplicationGrantedSerializer', 'ApplicationSystemUserSerializer'
|
'ApplicationGrantedSerializer', 'ApplicationSystemUserSerializer'
|
||||||
|
@ -27,13 +26,13 @@ class ApplicationSystemUserSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
class ApplicationGrantedSerializer(serializers.ModelSerializer):
|
class ApplicationGrantedSerializer(IncludeDynamicMappingSerializerFieldApplicationSerializerMixin,
|
||||||
|
serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
被授权应用的数据结构
|
被授权应用的数据结构
|
||||||
"""
|
"""
|
||||||
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
|
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
|
||||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||||
attrs = DynamicMappingField(mapping_rules=get_attrs_field_dynamic_mapping_rules())
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Application
|
model = Application
|
||||||
|
|
|
@ -12,7 +12,6 @@ from common.permissions import IsValidUser, IsOrgAdmin
|
||||||
from tickets import serializers
|
from tickets import serializers
|
||||||
from tickets.models import Ticket
|
from tickets.models import Ticket
|
||||||
from tickets.permissions.ticket import IsAssignee, NotClosed
|
from tickets.permissions.ticket import IsAssignee, NotClosed
|
||||||
from tickets.serializers.ticket.utils import get_dynamic_mapping_fields_mapping_rule_by_view
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['TicketViewSet']
|
__all__ = ['TicketViewSet']
|
||||||
|
@ -66,6 +65,3 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||||
def close(self, request, *args, **kwargs):
|
def close(self, request, *args, **kwargs):
|
||||||
return super().update(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 applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
|
||||||
from assets.models import SystemUser
|
from assets.models import SystemUser
|
||||||
from perms.models import ApplicationPermission
|
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:
|
class ConstructDisplayFieldMixin:
|
||||||
|
@ -67,11 +67,11 @@ class ConstructBodyMixin:
|
||||||
def construct_apply_application_approved_body(self):
|
def construct_apply_application_approved_body(self):
|
||||||
# 审批信息
|
# 审批信息
|
||||||
approve_applications_snapshot = self.meta['approve_applications_snapshot']
|
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
|
Application, approve_applications_snapshot
|
||||||
)
|
)
|
||||||
approve_system_users_snapshot = self.meta['approve_system_users_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
|
SystemUser, approve_system_users_snapshot
|
||||||
)
|
)
|
||||||
approve_date_start = self.meta['approve_date_start']
|
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 perms.models import AssetPermission, Action
|
||||||
from assets.models import Asset, SystemUser
|
from assets.models import Asset, SystemUser
|
||||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
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:
|
class ConstructDisplayFieldMixin:
|
||||||
|
@ -69,11 +69,11 @@ class ConstructBodyMixin:
|
||||||
|
|
||||||
def construct_apply_asset_approved_body(self):
|
def construct_apply_asset_approved_body(self):
|
||||||
approve_assets_snapshot = self.meta['approve_assets_snapshot']
|
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
|
Asset, approve_assets_snapshot
|
||||||
)
|
)
|
||||||
approve_system_users_snapshot = self.meta['approve_system_users_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
|
SystemUser, approve_system_users_snapshot
|
||||||
)
|
)
|
||||||
approve_actions_display = self.meta['approve_actions_display']
|
approve_actions_display = self.meta['approve_actions_display']
|
||||||
|
|
|
@ -19,7 +19,7 @@ class TicketCreatePermissionMixin(meta.CreatePermissionMixin, base.CreatePermiss
|
||||||
|
|
||||||
|
|
||||||
class TicketCreateCommentMixin(base.CreateCommentMixin):
|
class TicketCreateCommentMixin(base.CreateCommentMixin):
|
||||||
""" 创建 ticket 评论"""
|
""" 创建 ticket 备注"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
from .ticket import *
|
from .ticket import *
|
||||||
from .meta import *
|
from .meta import *
|
||||||
from .utils import *
|
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import copy
|
|
||||||
from common.drf.fields import IgnoreSensitiveInfoReadOnlyJSONField
|
|
||||||
from tickets import const
|
from tickets import const
|
||||||
from . import apply_asset, apply_application, login_confirm
|
from .ticket_type import apply_asset, apply_application, login_confirm
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'get_meta_field_dynamic_mapping_rules',
|
'meta_field_dynamic_mapping_serializers',
|
||||||
'get_meta_field_mapping_rule_by_view',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
#
|
|
||||||
# ticket type
|
# ticket type
|
||||||
# -----------
|
# -----------
|
||||||
|
|
||||||
|
@ -17,7 +13,6 @@ type_apply_asset = const.TicketTypeChoices.apply_asset.value
|
||||||
type_apply_application = const.TicketTypeChoices.apply_application.value
|
type_apply_application = const.TicketTypeChoices.apply_application.value
|
||||||
type_login_confirm = const.TicketTypeChoices.login_confirm.value
|
type_login_confirm = const.TicketTypeChoices.login_confirm.value
|
||||||
|
|
||||||
#
|
|
||||||
# ticket action
|
# ticket action
|
||||||
# -------------
|
# -------------
|
||||||
|
|
||||||
|
@ -29,13 +24,11 @@ action_reject = const.TicketActionChoices.reject.value
|
||||||
action_close = const.TicketActionChoices.close.value
|
action_close = const.TicketActionChoices.close.value
|
||||||
|
|
||||||
|
|
||||||
#
|
# defines `meta` field dynamic mapping serializers
|
||||||
# defines the dynamic mapping rules for the DynamicMappingField `meta`
|
# ------------------------------------------------
|
||||||
# --------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
__META_FIELD_DYNAMIC_MAPPING_RULES = {
|
meta_field_dynamic_mapping_serializers = {
|
||||||
'default': IgnoreSensitiveInfoReadOnlyJSONField,
|
|
||||||
'type': {
|
'type': {
|
||||||
type_apply_asset: {
|
type_apply_asset: {
|
||||||
action_open: apply_asset.ApplySerializer,
|
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 rest_framework import serializers
|
||||||
from perms.serializers import ActionsField
|
from perms.serializers import ActionsField
|
||||||
from assets.models import Asset, SystemUser
|
from assets.models import Asset, SystemUser
|
||||||
from tickets.models import Ticket
|
|
||||||
from .mixin import BaseApproveSerializerMixin
|
from .mixin import BaseApproveSerializerMixin
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ApplyAssetTypeSerializer', 'ApplySerializer', 'ApproveSerializer',
|
'ApplyAssetSerializer', 'ApplySerializer', 'ApproveSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,5 +83,5 @@ class ApproveSerializer(BaseApproveSerializerMixin, serializers.Serializer):
|
||||||
return system_users_id
|
return system_users_id
|
||||||
|
|
||||||
|
|
||||||
class ApplyAssetTypeSerializer(ApplySerializer, ApproveSerializer):
|
class ApplyAssetSerializer(ApplySerializer, ApproveSerializer):
|
||||||
pass
|
pass
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from tickets.models import Ticket
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ApplySerializer',
|
'ApplySerializer',
|
|
@ -2,13 +2,14 @@
|
||||||
#
|
#
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
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.utils import get_org_by_id
|
||||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from tickets import const
|
from tickets import const
|
||||||
from tickets.models import Ticket
|
from tickets.models import Ticket
|
||||||
from .meta import get_meta_field_dynamic_mapping_rules
|
from .meta import meta_field_dynamic_mapping_serializers
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'TicketSerializer', 'TicketDisplaySerializer',
|
'TicketSerializer', 'TicketDisplaySerializer',
|
||||||
|
@ -21,7 +22,7 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
|
||||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||||
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
||||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status'))
|
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:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
|
@ -35,6 +36,15 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
|
||||||
'body'
|
'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):
|
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__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
def convert_model_data_field_name_to_verbose_name(model, name_data):
|
def get_model_field_verbose_name(model, field_name):
|
||||||
"""将Model以field_name为key的数据转换为以field_verbose_name为key的数据"""
|
field_name_field_verbose_name_mapping = {
|
||||||
if isinstance(name_data, dict):
|
|
||||||
name_data = [name_data]
|
|
||||||
|
|
||||||
model_fields_name_verbose_name_mapping = {
|
|
||||||
field.name: field.verbose_name for field in model._meta.fields
|
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 = [
|
def convert_model_instance_data_field_name_to_verbose_name(model, data):
|
||||||
{get_verbose_name(name): value for name, value in d.items()}
|
if isinstance(data, dict):
|
||||||
for d in name_data
|
data = [data]
|
||||||
|
converted_data = [
|
||||||
|
{get_model_field_verbose_name(model, name): value for name, value in d.items()}
|
||||||
|
for d in data
|
||||||
]
|
]
|
||||||
|
return converted_data
|
||||||
return verbose_name_data
|
|
||||||
|
|
||||||
|
|
||||||
def send_ticket_applied_mail_to_assignees(ticket, assignees):
|
def send_ticket_applied_mail_to_assignees(ticket, assignees):
|
||||||
|
|
Loading…
Reference in New Issue