From 7167515a5312dcd6f6aaff5141c8bd9479c07e9d Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 6 Jan 2021 12:44:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0MethodSerializer,=20?= =?UTF-8?q?=E6=BB=A1=E8=B6=B3serializer=E4=B8=ADSerializerField=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E6=9B=B4=E6=94=B9=E7=9A=84=E9=9C=80=E6=B1=82=20(#5382?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 实现MethodSerializer, 满足serializer中SerializerField动态更改的需求 * feat: 实现MethodSerializer, 满足serializer中SerializerField动态更改的需求 (2) Co-authored-by: Bai --- apps/applications/serializers/application.py | 27 +++---- apps/applications/serializers/attrs/attrs.py | 73 ++++++------------ apps/common/drf/serializers.py | 77 ++++--------------- .../application/user_permission.py | 5 +- apps/tickets/serializers/ticket/meta/meta.py | 41 +++------- apps/tickets/serializers/ticket/ticket.py | 23 ++++-- 6 files changed, 83 insertions(+), 163 deletions(-) diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index fd45a6eea..10c95da24 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -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')) diff --git a/apps/applications/serializers/attrs/attrs.py b/apps/applications/serializers/attrs/attrs.py index a1d6d684b..18d420fd1 100644 --- a/apps/applications/serializers/attrs/attrs.py +++ b/apps/applications/serializers/attrs/attrs.py @@ -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) diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers.py index 817c6cd6e..274bceaf6 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers.py @@ -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 # ---------------- diff --git a/apps/perms/serializers/application/user_permission.py b/apps/perms/serializers/application/user_permission.py index c96316d71..5c722852b 100644 --- a/apps/perms/serializers/application/user_permission.py +++ b/apps/perms/serializers/application/user_permission.py @@ -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): """ 被授权应用的数据结构 """ diff --git a/apps/tickets/serializers/ticket/meta/meta.py b/apps/tickets/serializers/ticket/meta/meta.py index 6097cec4a..63a25207d 100644 --- a/apps/tickets/serializers/ticket/meta/meta.py +++ b/apps/tickets/serializers/ticket/meta/meta.py @@ -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, } } - - diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index 0cf940e0d..0578bcce0 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -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):