feat: 实现MethodSerializer, 满足serializer中SerializerField动态更改的需求 (#5382)

* feat: 实现MethodSerializer, 满足serializer中SerializerField动态更改的需求

* feat: 实现MethodSerializer, 满足serializer中SerializerField动态更改的需求 (2)

Co-authored-by: Bai <bugatti_it@163.com>
pull/5383/head
fit2bot 2021-01-06 12:44:12 +08:00 committed by GitHub
parent 17a01a12db
commit 7167515a53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 163 deletions

View File

@ -4,35 +4,36 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import DynamicMappingSerializer
from .attrs import attrs_field_dynamic_mapping_serializers
from common.drf.serializers import MethodSerializer
from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping
from .. import models
__all__ = [
'ApplicationSerializer',
'IncludeDynamicMappingSerializerFieldApplicationSerializerMixin',
'ApplicationSerializer', 'ApplicationSerializerMixin',
]
class IncludeDynamicMappingSerializerFieldApplicationSerializerMixin(serializers.Serializer):
attrs = DynamicMappingSerializer(mapping_serializers=attrs_field_dynamic_mapping_serializers)
class ApplicationSerializerMixin(serializers.Serializer):
attrs = MethodSerializer()
def get_attrs_mapping_path(self, mapping_serializers):
def get_attrs_serializer(self):
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]
serializer_class = type_serializer_classes_mapping.get(query_type)
elif query_category:
mapping_path = ['category', query_category]
serializer_class = category_serializer_classes_mapping.get(query_category)
else:
mapping_path = ['default']
return mapping_path
serializer_class = None
if serializer_class is None:
serializer_class = serializers.Serializer
serializer = serializer_class()
return serializer
class ApplicationSerializer(IncludeDynamicMappingSerializerFieldApplicationSerializerMixin,
BulkOrgResourceModelSerializer):
class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSerializer):
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category'))
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))

View File

@ -4,62 +4,39 @@ from . import application_category, application_type
__all__ = [
'attrs_field_dynamic_mapping_serializers',
'category_serializer_classes_mapping',
'type_serializer_classes_mapping',
'get_serializer_class_by_application_type',
]
# application category
# --------------------
# define `attrs` field `category serializers mapping`
# ---------------------------------------------------
category_db = const.ApplicationCategoryChoices.db.value
category_remote_app = const.ApplicationCategoryChoices.remote_app.value
category_cloud = const.ApplicationCategoryChoices.cloud.value
category_serializer_classes_mapping = {
const.ApplicationCategoryChoices.db.value: application_category.DBSerializer,
const.ApplicationCategoryChoices.remote_app.value: application_category.RemoteAppSerializer,
const.ApplicationCategoryChoices.cloud.value: application_category.CloudSerializer,
}
# define `attrs` field `type serializers mapping`
# -----------------------------------------------
# application type
# ----------------
# db
type_mysql = const.ApplicationTypeChoices.mysql.value
type_mariadb = const.ApplicationTypeChoices.mariadb.value
type_oracle = const.ApplicationTypeChoices.oracle.value
type_pgsql = const.ApplicationTypeChoices.pgsql.value
# remote-app
type_chrome = const.ApplicationTypeChoices.chrome.value
type_mysql_workbench = const.ApplicationTypeChoices.mysql_workbench.value
type_vmware_client = const.ApplicationTypeChoices.vmware_client.value
type_custom = const.ApplicationTypeChoices.custom.value
# cloud
type_k8s = const.ApplicationTypeChoices.k8s.value
# define `attrs` field `dynamic mapping serializers`
# --------------------------------------------------
attrs_field_dynamic_mapping_serializers = {
'category': {
category_db: application_category.DBSerializer,
category_remote_app: application_category.RemoteAppSerializer,
category_cloud: application_category.CloudSerializer,
},
'type': {
# db
type_mysql: application_type.MySQLSerializer,
type_mariadb: application_type.MariaDBSerializer,
type_oracle: application_type.OracleSerializer,
type_pgsql: application_type.PostgreSerializer,
# remote-app
type_chrome: application_type.ChromeSerializer,
type_mysql_workbench: application_type.MySQLWorkbenchSerializer,
type_vmware_client: application_type.VMwareClientSerializer,
type_custom: application_type.CustomSerializer,
# cloud
type_k8s: application_type.K8SSerializer
}
type_serializer_classes_mapping = {
# db
const.ApplicationTypeChoices.mysql.value: application_type.MySQLSerializer,
const.ApplicationTypeChoices.mariadb.value: application_type.MariaDBSerializer,
const.ApplicationTypeChoices.oracle.value: application_type.OracleSerializer,
const.ApplicationTypeChoices.pgsql.value: application_type.PostgreSerializer,
# remote-app
const.ApplicationTypeChoices.chrome.value: application_type.ChromeSerializer,
const.ApplicationTypeChoices.mysql_workbench.value: application_type.MySQLWorkbenchSerializer,
const.ApplicationTypeChoices.vmware_client.value: application_type.VMwareClientSerializer,
const.ApplicationTypeChoices.custom.value: application_type.CustomSerializer,
# cloud
const.ApplicationTypeChoices.k8s.value: application_type.K8SSerializer
}
def get_serializer_class_by_application_type(_application_type):
return attrs_field_dynamic_mapping_serializers['type'].get(_application_type)
return type_serializer_classes_mapping.get(_application_type)

View File

@ -1,4 +1,3 @@
import copy
from rest_framework import serializers
from rest_framework.serializers import Serializer
from rest_framework.serializers import ModelSerializer
@ -8,97 +7,51 @@ from common.mixins import BulkListSerializerMixin
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__ = [
'DynamicMappingSerializer',
'MethodSerializer',
'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer'
]
# DynamicMappingSerializer
# ------------------------
# MethodSerializer
# ----------------
class DynamicMappingSerializer(serializers.Serializer):
data_type_error_messages = 'Expect get instance of type `{}`, but got instance type of `{}`'
class MethodSerializer(serializers.Serializer):
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
def __init__(self, method_name=None, **kwargs):
self.method_name = method_name
super().__init__(**kwargs)
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
# 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
if self.method_name is None:
method_name = 'get_{field_name}_serializer'.format(field_name=field_name)
self.method_name = method_name
super().bind(field_name, parent)
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)
@cached_property
def serializer(self) -> serializers.Serializer:
method = getattr(self.parent, self.method_name)
return method()
def get_mapping_path(self, mapping_serializers):
method = getattr(self.parent, self.get_mapping_path_method_name)
return method(mapping_serializers)
@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
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
@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
return self.serializer.get_fields()
@cached_property
def fields(self):
"""
重写此方法因为在 BindingDict 中要设置每一个 field parent `mapped_serializer`,
重写此方法因为在 BindingDict 中要设置每一个 field parent `serializer`,
这样在调用 field.parent , 才会达到预期的结果
比如: serializers.SerializerMethodField
"""
fields = BindingDict(self.mapped_serializer)
fields = BindingDict(self.serializer)
for key, value in self.get_fields().items():
fields[key] = value
return fields
#
# Other Serializer
# ----------------

View File

@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from assets.models import SystemUser
from applications.models import Application
from applications.serializers import IncludeDynamicMappingSerializerFieldApplicationSerializerMixin
from applications.serializers import ApplicationSerializerMixin
__all__ = [
'ApplicationGrantedSerializer', 'ApplicationSystemUserSerializer'
@ -26,8 +26,7 @@ class ApplicationSystemUserSerializer(serializers.ModelSerializer):
read_only_fields = fields
class ApplicationGrantedSerializer(IncludeDynamicMappingSerializerFieldApplicationSerializerMixin,
serializers.ModelSerializer):
class ApplicationGrantedSerializer(ApplicationSerializerMixin, serializers.ModelSerializer):
"""
被授权应用的数据结构
"""

View File

@ -2,46 +2,29 @@ from tickets import const
from .ticket_type import apply_asset, apply_application, login_confirm
__all__ = [
'meta_field_dynamic_mapping_serializers',
'type_serializer_classes_mapping',
]
# ticket type
# -----------
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
# -------------
actions = const.TicketActionChoices.values
action_open = const.TicketActionChoices.open.value
action_approve = const.TicketActionChoices.approve.value
action_reject = const.TicketActionChoices.reject.value
action_close = const.TicketActionChoices.close.value
# defines `meta` field dynamic mapping serializers
# ------------------------------------------------
meta_field_dynamic_mapping_serializers = {
'type': {
type_apply_asset: {
action_open: apply_asset.ApplySerializer,
action_approve: apply_asset.ApproveSerializer,
},
type_apply_application: {
action_open: apply_application.ApplySerializer,
action_approve: apply_application.ApproveSerializer,
},
type_login_confirm: {
action_open: login_confirm.ApplySerializer,
}
type_serializer_classes_mapping = {
const.TicketTypeChoices.apply_asset.value: {
action_open: apply_asset.ApplySerializer,
action_approve: apply_asset.ApproveSerializer,
},
const.TicketTypeChoices.apply_application.value: {
action_open: apply_application.ApplySerializer,
action_approve: apply_application.ApproveSerializer,
},
const.TicketTypeChoices.login_confirm.value: {
action_open: login_confirm.ApplySerializer,
}
}

View File

@ -3,13 +3,13 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.drf.fields import ReadableHiddenField
from common.drf.serializers import DynamicMappingSerializer
from common.drf.serializers import MethodSerializer
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 meta_field_dynamic_mapping_serializers
from .meta import type_serializer_classes_mapping
__all__ = [
'TicketSerializer', 'TicketDisplaySerializer',
@ -22,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 = DynamicMappingSerializer(mapping_serializers=meta_field_dynamic_mapping_serializers)
meta = MethodSerializer()
class Meta:
model = Ticket
@ -36,14 +36,21 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
'body'
]
def get_meta_mapping_path(self, mapping_serializers):
view = self.context['view']
def get_meta_serializer(self):
request = self.context['request']
view = self.context['view']
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
view_action = view.action
action = query_action if query_action else view_action
if query_type:
serializer_class = type_serializer_classes_mapping.get(query_type, {}).get(action)
else:
serializer_class = None
if serializer_class is None:
serializer_class = serializers.Serializer
serializer = serializer_class()
return serializer
class TicketDisplaySerializer(TicketSerializer):