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 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.serializers import DynamicMappingSerializer from common.drf.serializers import MethodSerializer
from .attrs import attrs_field_dynamic_mapping_serializers from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping
from .. import models from .. import models
__all__ = [ __all__ = [
'ApplicationSerializer', 'ApplicationSerializer', 'ApplicationSerializerMixin',
'IncludeDynamicMappingSerializerFieldApplicationSerializerMixin',
] ]
class IncludeDynamicMappingSerializerFieldApplicationSerializerMixin(serializers.Serializer): class ApplicationSerializerMixin(serializers.Serializer):
attrs = DynamicMappingSerializer(mapping_serializers=attrs_field_dynamic_mapping_serializers) attrs = MethodSerializer()
def get_attrs_mapping_path(self, mapping_serializers): def get_attrs_serializer(self):
request = self.context['request'] request = self.context['request']
query_type = request.query_params.get('type') query_type = request.query_params.get('type')
query_category = request.query_params.get('category') query_category = request.query_params.get('category')
if query_type: if query_type:
mapping_path = ['type', query_type] serializer_class = type_serializer_classes_mapping.get(query_type)
elif query_category: elif query_category:
mapping_path = ['category', query_category] serializer_class = category_serializer_classes_mapping.get(query_category)
else: else:
mapping_path = ['default'] serializer_class = None
return mapping_path if serializer_class is None:
serializer_class = serializers.Serializer
serializer = serializer_class()
return serializer
class ApplicationSerializer(IncludeDynamicMappingSerializerFieldApplicationSerializerMixin, class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSerializer):
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'))

View File

@ -4,62 +4,39 @@ from . import application_category, application_type
__all__ = [ __all__ = [
'attrs_field_dynamic_mapping_serializers', 'category_serializer_classes_mapping',
'type_serializer_classes_mapping',
'get_serializer_class_by_application_type', 'get_serializer_class_by_application_type',
] ]
# application category # define `attrs` field `category serializers mapping`
# -------------------- # ---------------------------------------------------
category_db = const.ApplicationCategoryChoices.db.value category_serializer_classes_mapping = {
category_remote_app = const.ApplicationCategoryChoices.remote_app.value const.ApplicationCategoryChoices.db.value: application_category.DBSerializer,
category_cloud = const.ApplicationCategoryChoices.cloud.value const.ApplicationCategoryChoices.remote_app.value: application_category.RemoteAppSerializer,
const.ApplicationCategoryChoices.cloud.value: application_category.CloudSerializer,
# 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
} }
# define `attrs` field `type serializers mapping`
# -----------------------------------------------
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): 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 import serializers
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
@ -8,97 +7,51 @@ from common.mixins import BulkListSerializerMixin
from django.utils.functional import cached_property from django.utils.functional import cached_property
from rest_framework.utils.serializer_helpers import BindingDict 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
__all__ = [ __all__ = [
'DynamicMappingSerializer', 'MethodSerializer',
'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer' 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer'
] ]
# DynamicMappingSerializer # MethodSerializer
# ------------------------ # ----------------
class DynamicMappingSerializer(serializers.Serializer): class MethodSerializer(serializers.Serializer):
data_type_error_messages = 'Expect get instance of type `{}`, but got instance type of `{}`'
def __init__(self, mapping_serializers=None, get_mapping_serializers_method_name=None, def __init__(self, method_name=None, **kwargs):
get_mapping_path_method_name=None, default_serializer=None, **kwargs): self.method_name = method_name
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) super().__init__(**kwargs)
def bind(self, field_name, parent): def bind(self, field_name, parent):
# The get mapping serializers method name defaults to `get_{field_name}_mapping_serializers` if self.method_name is None:
if self.get_mapping_serializers_method_name is None: method_name = 'get_{field_name}_serializer'.format(field_name=field_name)
method_name = 'get_{field_name}_mapping_serializers'.format(field_name=field_name) self.method_name = method_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
super().bind(field_name, parent) super().bind(field_name, parent)
def get_mapping_serializers(self): @cached_property
if self.mapping_serializers is not None: def serializer(self) -> serializers.Serializer:
return self.mapping_serializers method = getattr(self.parent, self.method_name)
method = getattr(self.parent, self.get_mapping_serializers_method_name)
return method() 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): def get_fields(self):
fields = self.mapped_serializer.get_fields() return self.serializer.get_fields()
return fields
@cached_property @cached_property
def fields(self): def fields(self):
""" """
重写此方法因为在 BindingDict 中要设置每一个 field parent `mapped_serializer`, 重写此方法因为在 BindingDict 中要设置每一个 field parent `serializer`,
这样在调用 field.parent , 才会达到预期的结果 这样在调用 field.parent , 才会达到预期的结果
比如: serializers.SerializerMethodField 比如: serializers.SerializerMethodField
""" """
fields = BindingDict(self.mapped_serializer) fields = BindingDict(self.serializer)
for key, value in self.get_fields().items(): for key, value in self.get_fields().items():
fields[key] = value fields[key] = value
return fields return fields
#
# Other Serializer # Other Serializer
# ---------------- # ----------------

View File

@ -6,7 +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 import IncludeDynamicMappingSerializerFieldApplicationSerializerMixin from applications.serializers import ApplicationSerializerMixin
__all__ = [ __all__ = [
'ApplicationGrantedSerializer', 'ApplicationSystemUserSerializer' 'ApplicationGrantedSerializer', 'ApplicationSystemUserSerializer'
@ -26,8 +26,7 @@ class ApplicationSystemUserSerializer(serializers.ModelSerializer):
read_only_fields = fields read_only_fields = fields
class ApplicationGrantedSerializer(IncludeDynamicMappingSerializerFieldApplicationSerializerMixin, class ApplicationGrantedSerializer(ApplicationSerializerMixin, serializers.ModelSerializer):
serializers.ModelSerializer):
""" """
被授权应用的数据结构 被授权应用的数据结构
""" """

View File

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

View File

@ -3,13 +3,13 @@
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 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.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 meta_field_dynamic_mapping_serializers from .meta import type_serializer_classes_mapping
__all__ = [ __all__ = [
'TicketSerializer', 'TicketDisplaySerializer', 'TicketSerializer', 'TicketDisplaySerializer',
@ -22,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 = DynamicMappingSerializer(mapping_serializers=meta_field_dynamic_mapping_serializers) meta = MethodSerializer()
class Meta: class Meta:
model = Ticket model = Ticket
@ -36,14 +36,21 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
'body' 'body'
] ]
def get_meta_mapping_path(self, mapping_serializers): def get_meta_serializer(self):
view = self.context['view']
request = self.context['request'] request = self.context['request']
view = self.context['view']
query_type = request.query_params.get('type') query_type = request.query_params.get('type')
query_action = request.query_params.get('action') query_action = request.query_params.get('action')
action = query_action if query_action else view.action view_action = view.action
mapping_path = ['type', query_type, action] action = query_action if query_action else view_action
return mapping_path 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): class TicketDisplaySerializer(TicketSerializer):