feat: 抽象View Mapping Serializer架构设计; 重构工单View、Serializer模块 (#5371)

* perf: 优化工单模块(修改迁移文件->Model assignees_display 字段类型为list)

* ignore: try `view` `serializer jsonfields` Map design (1)

* ignore: try `view` `serializer jsonfields` Map design (2)

* ignore: try `view` `serializer jsonfields` Map design (3)

* ignore: try `view` `serializer jsonfields` Map design (4)

* ignore: try `view` `serializer jsonfields` Map design (5)

* ignore: try `view` `serializer.DynamicMappingField` Mapping design (6)

* feat: 抽象view_mapping_serializer逻辑架构; 重构工单View、Serializer模块

* feat: 抽象view_mapping_serializer逻辑架构; 重构工单View、Serializer模块(2)

* feat: 抽象view_mapping_serializer逻辑架构; 重构工单View、Serializer模块(3)

* feat: 抽象view_mapping_serializer逻辑架构; 重构工单View、Serializer模块(4)

Co-authored-by: Bai <bugatti_it@163.com>
This commit is contained in:
fit2bot
2021-01-02 07:25:23 +08:00
committed by GitHub
parent 5c483084b7
commit cef93abb2f
48 changed files with 1137 additions and 795 deletions

View File

@@ -1,43 +1,171 @@
from uuid import UUID
# -*- coding: utf-8 -*-
#
from rest_framework.fields import get_attribute
from rest_framework.relations import ManyRelatedField, PrimaryKeyRelatedField, MANY_RELATION_KWARGS
import data_tree
from rest_framework import serializers
class GroupConcatedManyRelatedField(ManyRelatedField):
def get_attribute(self, instance):
if hasattr(instance, 'pk') and instance.pk is None:
return []
attr = self.source_attrs[-1]
# `gc` 是 `GroupConcat` 的缩写
gc_attr = f'gc_{attr}'
if hasattr(instance, gc_attr):
gc_value = getattr(instance, gc_attr)
if isinstance(gc_value, str):
return [UUID(pk) for pk in set(gc_value.split(','))]
else:
return ''
relationship = get_attribute(instance, self.source_attrs)
return relationship.all() if hasattr(relationship, 'all') else relationship
__all__ = [
'DynamicMappingField', 'ReadableHiddenField',
'CustomMetaDictField',
]
class GroupConcatedPrimaryKeyRelatedField(PrimaryKeyRelatedField):
@classmethod
def many_init(cls, *args, **kwargs):
list_kwargs = {'child_relation': cls(*args, **kwargs)}
for key in kwargs:
if key in MANY_RELATION_KWARGS:
list_kwargs[key] = kwargs[key]
return GroupConcatedManyRelatedField(**list_kwargs)
#
# 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))
)
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):
if self.pk_field is not None:
return self.pk_field.to_representation(value.pk)
""" 实际是一个虚拟字段所以不返回任何值 """
pass
if hasattr(value, 'pk'):
return value.pk
else:
#
# ReadableHiddenField
# -------------------
class ReadableHiddenField(serializers.HiddenField):
""" 可读的 HiddenField """
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.write_only = False
def to_representation(self, value):
if hasattr(value, 'id'):
return getattr(value, 'id')
return value
#
# OtherField
# ----------
# TODO: DELETE 替换完成后删除
class CustomMetaDictField(serializers.DictField):
"""
In use:
RemoteApp params field
CommandStorage meta field
ReplayStorage meta field
"""
type_fields_map = {}
default_type = None
convert_key_remove_type_prefix = False
convert_key_to_upper = False
def filter_attribute(self, attribute, instance):
fields = self.type_fields_map.get(instance.type, [])
for field in fields:
if field.get('write_only', False):
attribute.pop(field['name'], None)
return attribute
def get_attribute(self, instance):
"""
序列化时调用
"""
attribute = super().get_attribute(instance)
attribute = self.filter_attribute(attribute, instance)
return attribute
def convert_value_key_remove_type_prefix(self, dictionary, value):
if not self.convert_key_remove_type_prefix:
return value
tp = dictionary.get('type')
prefix = '{}_'.format(tp)
convert_value = {}
for k, v in value.items():
if k.lower().startswith(prefix):
k = k.lower().split(prefix, 1)[1]
convert_value[k] = v
return convert_value
def convert_value_key_to_upper(self, value):
if not self.convert_key_to_upper:
return value
convert_value = {k.upper(): v for k, v in value.items()}
return convert_value
def convert_value_key(self, dictionary, value):
value = self.convert_value_key_remove_type_prefix(dictionary, value)
value = self.convert_value_key_to_upper(value)
return value
def filter_value_key(self, dictionary, value):
tp = dictionary.get('type')
fields = self.type_fields_map.get(tp, [])
fields_names = [field['name'] for field in fields]
filter_value = {k: v for k, v in value.items() if k in fields_names}
return filter_value
@staticmethod
def strip_value(value):
new_value = {}
for k, v in value.items():
if isinstance(v, str):
v = v.strip()
new_value[k] = v
return new_value
def get_value(self, dictionary):
"""
反序列化时调用
"""
value = super().get_value(dictionary)
value = self.convert_value_key(dictionary, value)
value = self.filter_value_key(dictionary, value)
value = self.strip_value(value)
return value

View File

@@ -1,12 +1,142 @@
import copy
from rest_framework import serializers
from rest_framework.serializers import Serializer
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins.serializers import BulkSerializerMixin
from common.mixins import BulkListSerializerMixin
from common.drf.fields import DynamicMappingField
from common.mixins.serializers import BulkSerializerMixin
__all__ = ['EmptySerializer', 'BulkModelSerializer']
__all__ = [
'IncludeDynamicMappingFieldSerializerMetaClass',
'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer'
]
#
# 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)
#
# Other Serializer
# ----------------
class EmptySerializer(Serializer):
@@ -23,3 +153,5 @@ class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer):
class CeleryTaskSerializer(serializers.Serializer):
task = serializers.CharField(read_only=True)

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
#
from .form import *
from .model import *
from .serializer import *

View File

@@ -1,62 +0,0 @@
# -*- coding: utf-8 -*-
#
import json
from django import forms
import six
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from ..utils import signer
__all__ = [
'FormDictField', 'FormEncryptCharField', 'FormEncryptDictField',
'FormEncryptMixin',
]
class FormDictField(forms.Field):
widget = forms.Textarea
def to_python(self, value):
"""Returns a Python boolean object."""
# Explicitly check for the string 'False', which is what a hidden field
# will submit for False. Also check for '0', since this is what
# RadioSelect will provide. Because bool("True") == bool('1') == True,
# we don't need to handle that explicitly.
if isinstance(value, six.string_types):
value = value.replace("'", '"')
try:
value = json.loads(value)
return value
except json.JSONDecodeError:
return ValidationError(_("Not a valid json"))
else:
return ValidationError(_("Not a string type"))
def validate(self, value):
if isinstance(value, ValidationError):
raise value
if not value and self.required:
raise ValidationError(self.error_messages['required'], code='required')
def has_changed(self, initial, data):
# Sometimes data or initial may be a string equivalent of a boolean
# so we should run it through to_python first to get a boolean value
return self.to_python(initial) != self.to_python(data)
class FormEncryptMixin:
pass
class FormEncryptCharField(FormEncryptMixin, forms.CharField):
pass
class FormEncryptDictField(FormEncryptMixin, FormDictField):
pass

View File

@@ -1,316 +0,0 @@
# -*- coding: utf-8 -*-
#
import copy
from collections import OrderedDict
from rest_framework.serializers import ALL_FIELDS
from rest_framework import serializers
import six
__all__ = [
'StringIDField', 'StringManyToManyField', 'ChoiceDisplayField',
'CustomMetaDictField', 'ReadableHiddenField', 'JSONFieldModelSerializer'
]
class StringIDField(serializers.Field):
def to_representation(self, value):
return {"pk": value.pk, "name": value.__str__()}
class StringManyToManyField(serializers.RelatedField):
def to_representation(self, value):
return value.__str__()
class ChoiceDisplayField(serializers.ChoiceField):
def __init__(self, *args, **kwargs):
super(ChoiceDisplayField, self).__init__(*args, **kwargs)
self.choice_strings_to_display = {
six.text_type(key): value for key, value in self.choices.items()
}
def to_representation(self, value):
if value is None:
return value
return {
'value': self.choice_strings_to_values.get(six.text_type(value), value),
'display': self.choice_strings_to_display.get(six.text_type(value), value),
}
class DictField(serializers.DictField):
def to_representation(self, value):
if not value or not isinstance(value, dict):
value = {}
return super().to_representation(value)
class ReadableHiddenField(serializers.HiddenField):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.write_only = False
def to_representation(self, value):
if hasattr(value, 'id'):
return getattr(value, 'id')
return value
class CustomMetaDictField(serializers.DictField):
"""
In use:
RemoteApp params field
CommandStorage meta field
ReplayStorage meta field
"""
type_fields_map = {}
default_type = None
convert_key_remove_type_prefix = False
convert_key_to_upper = False
def filter_attribute(self, attribute, instance):
fields = self.type_fields_map.get(instance.type, [])
for field in fields:
if field.get('write_only', False):
attribute.pop(field['name'], None)
return attribute
def get_attribute(self, instance):
"""
序列化时调用
"""
attribute = super().get_attribute(instance)
attribute = self.filter_attribute(attribute, instance)
return attribute
def convert_value_key_remove_type_prefix(self, dictionary, value):
if not self.convert_key_remove_type_prefix:
return value
tp = dictionary.get('type')
prefix = '{}_'.format(tp)
convert_value = {}
for k, v in value.items():
if k.lower().startswith(prefix):
k = k.lower().split(prefix, 1)[1]
convert_value[k] = v
return convert_value
def convert_value_key_to_upper(self, value):
if not self.convert_key_to_upper:
return value
convert_value = {k.upper(): v for k, v in value.items()}
return convert_value
def convert_value_key(self, dictionary, value):
value = self.convert_value_key_remove_type_prefix(dictionary, value)
value = self.convert_value_key_to_upper(value)
return value
def filter_value_key(self, dictionary, value):
tp = dictionary.get('type')
fields = self.type_fields_map.get(tp, [])
fields_names = [field['name'] for field in fields]
filter_value = {k: v for k, v in value.items() if k in fields_names}
return filter_value
@staticmethod
def strip_value(value):
new_value = {}
for k, v in value.items():
if isinstance(v, str):
v = v.strip()
new_value[k] = v
return new_value
def get_value(self, dictionary):
"""
反序列化时调用
"""
value = super().get_value(dictionary)
value = self.convert_value_key(dictionary, value)
value = self.filter_value_key(dictionary, value)
value = self.strip_value(value)
return value
class JSONFieldModelSerializer(serializers.Serializer):
""" Model JSONField Serializer"""
def __init__(self, *args, **kwargs):
mode_field = getattr(self.Meta, 'model_field')
if mode_field:
kwargs['label'] = mode_field.field.verbose_name
super().__init__(*args, **kwargs)
class Meta:
model = None
model_field = None
fields = None
exclude = None
def get_fields(self):
assert hasattr(self, 'Meta'), (
'Class {serializer_class} missing "Meta" attribute'.format(
serializer_class=self.__class__.__name__
)
)
assert hasattr(self.Meta, 'model'), (
'Class {serializer_class} missing "Meta.model" attribute'.format(
serializer_class=self.__class__.__name__
)
)
model_fields_mapping = {field.name: field for field in self.Meta.model._meta.fields}
assert hasattr(self.Meta, 'model_field'), (
'Class {serializer_class} missing "Meta.model_field" attribute'.format(
serializer_class=self.__class__.__name__
)
)
assert self.Meta.model_field.field.name in model_fields_mapping.keys(), (
'Class {serializer_class} "Meta.model_field" attribute not in '
'"Meta.model._meta.fields"'.format(
serializer_class=self.__class__.__name__,
)
)
declared_fields = copy.deepcopy(self._declared_fields)
read_only_field_names = self.get_read_only_field_names()
field_names = self.get_field_names(declared_fields)
fields = OrderedDict()
for field_name in field_names:
if field_name not in declared_fields:
continue
field = declared_fields[field_name]
if field_name in read_only_field_names:
setattr(field, 'read_only', True)
fields[field_name] = field
return fields
def get_field_names(self, declared_fields):
"""
Returns the list of all field names that should be created when
instantiating this serializer class. This is based on the default
set of fields, but also takes into account the `Meta.fields` or
`Meta.exclude` options if they have been specified.
"""
fields = getattr(self.Meta, 'fields', None)
exclude = getattr(self.Meta, 'exclude', None)
if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)):
raise TypeError(
'The `fields` option must be a list or tuple or "__all__". '
'Got %s.' % type(fields).__name__
)
if exclude and not isinstance(exclude, (list, tuple)):
raise TypeError(
'The `exclude` option must be a list or tuple. Got %s.' %
type(exclude).__name__
)
assert not (fields and exclude), (
"Cannot set both 'fields' and 'exclude' options on "
"serializer {serializer_class}.".format(
serializer_class=self.__class__.__name__
)
)
assert not (fields is None and exclude is None), (
"Creating a ModelSerializer without either the 'fields' attribute "
"or the 'exclude' attribute has been deprecated since 3.3.0, "
"and is now disallowed. Add an explicit fields = '__all__' to the "
"{serializer_class} serializer.".format(
serializer_class=self.__class__.__name__
),
)
if fields == ALL_FIELDS:
fields = None
if fields is not None:
# Ensure that all declared fields have also been included in the
# `Meta.fields` option.
# Do not require any fields that are declared in a parent class,
# in order to allow serializer subclasses to only include
# a subset of fields.
required_field_names = set(declared_fields)
for cls in self.__class__.__bases__:
required_field_names -= set(getattr(cls, '_declared_fields', []))
for field_name in required_field_names:
assert field_name in fields, (
"The field '{field_name}' was declared on serializer "
"{serializer_class}, but has not been included in the "
"'fields' option.".format(
field_name=field_name,
serializer_class=self.__class__.__name__
)
)
return fields
# Use the default set of field names if `Meta.fields` is not specified.
fields = self.get_default_field_names(declared_fields)
if exclude is not None:
# If `Meta.exclude` is included, then remove those fields.
for field_name in exclude:
assert field_name not in self._declared_fields, (
"Cannot both declare the field '{field_name}' and include "
"it in the {serializer_class} 'exclude' option. Remove the "
"field or, if inherited from a parent serializer, disable "
"with `{field_name} = None`."
.format(
field_name=field_name,
serializer_class=self.__class__.__name__
)
)
assert field_name in fields, (
"The field '{field_name}' was included on serializer "
"{serializer_class} in the 'exclude' option, but does "
"not match any model field.".format(
field_name=field_name,
serializer_class=self.__class__.__name__
)
)
fields.remove(field_name)
return fields
@staticmethod
def get_default_field_names(declared_fields):
return declared_fields
def get_read_only_field_names(self):
read_only_fields = getattr(self.Meta, 'read_only_fields', None)
if read_only_fields is not None:
if not isinstance(read_only_fields, (list, tuple)):
raise TypeError(
'The `read_only_fields` option must be a list or tuple. '
'Got %s.' % type(read_only_fields).__name__
)
return read_only_fields
def to_internal_value(self, data):
return super().to_internal_value(data)
def to_representation(self, instance):
if not isinstance(instance, dict):
return super().to_representation(instance)
for field_name, field in self.fields.items():
if field_name in instance:
continue
if field.allow_null:
continue
setattr(field, 'allow_null', True)
return super().to_representation(instance)

View File

@@ -7,15 +7,13 @@ from collections import defaultdict
from itertools import chain
from django.db.models.signals import m2m_changed
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from django.http import JsonResponse
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.settings import api_settings
from common.exceptions import JMSException
from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
from common.drf.serializers import IncludeDynamicMappingFieldSerializerMetaClass
from ..utils import lazyproperty
__all__ = [
@@ -31,7 +29,14 @@ class JSONResponseMixin(object):
return JsonResponse(context)
#
# GenericSerializerMixin
# ----------------------
class GenericSerializerMixin:
""" 根据用户请求动作的不同,获取不同的 `serializer_class `"""
serializer_classes = None
def get_serializer_class_by_view_action(self):
@@ -39,12 +44,16 @@ class GenericSerializerMixin:
return None
if not isinstance(self.serializer_classes, dict):
return None
draw = self.request.query_params.get('draw')
action = self.request.query_params.get('action')
serializer_class = None
if draw and self.action in ['list', 'metadata']:
serializer_class = self.serializer_classes.get('display')
if action:
# metadata方法 使用 action 参数获取
serializer_class = self.serializer_classes.get(action)
if serializer_class is None:
serializer_class = self.serializer_classes.get(self.action)
if serializer_class is None:
serializer_class = self.serializer_classes.get('display')
if serializer_class is None:
serializer_class = self.serializer_classes.get('default')
return serializer_class
@@ -56,156 +65,59 @@ class GenericSerializerMixin:
return serializer_class
class JSONFieldsModelSerializerMixin:
#
# IncludeDynamicMappingFieldSerializerViewMixin
# ---------------------------------------------
class IncludeDynamicMappingFieldSerializerViewMixin(GenericSerializerMixin):
"""
作用: 获取包含 JSONField 字段的序列类
动态创建 `view` 使用的 `serializer_class`,
class TestSerializer(serializers.Serializer):
pass
根据用户请求行为的不同, 构造出获取 `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']
json_fields_category_mapping = {
'json_field_1': {
'type': ('apply_asset', 'apply_application', 'login_confirm', ),
},
'json_field_2': {
'type': ('chrome', 'mysql', 'oracle', 'k8s', ),
'category': ('remote_app', 'db', 'cloud', ),
},
}
json_fields_serializer_classes = {
'json_field_1': {
'type': {
'apply_asset': {
'get': TestSerializer,
'post': TestSerializer,
'open':TestSerializer,
'approve': TestSerializer,
},
'apply_application': {
'get': TestSerializer,
'post': TestSerializer,
'put': TestSerializer,
},
'login_confirm': {
'get': TestSerializer,
'post': TestSerializer,
'put': TestSerializer,
}
},
'category': {}
},
'json_field_2': {},
'json_field_3': {}
}
"""
json_fields_category_mapping = {}
json_fields_serializer_classes = None
# 保存当前处理的JSONField名称
__field = None
serializer_class = None
def get_json_field_query_category(self, category):
query_category = self.request.query_params.get(category)
category_choices = self.json_fields_category_mapping[self.__field][category]
if query_category and query_category not in category_choices:
error = _(
'Please bring the query parameter `{}`, '
'the value is selected from the following options: {}'
''.format(query_category, category_choices)
)
raise JMSException({'query_params_error': error})
return query_category
def get_json_field_action_serializer_classes_by_query_category(self):
action_serializer_classes = None
category_collection = self.json_fields_category_mapping[self.__field]
for category in category_collection:
category_value = self.get_json_field_query_category(category)
if not category_value:
continue
category_serializer_classes = self.json_fields_serializer_classes[self.__field][category]
action_serializer_classes = category_serializer_classes.get(category_value)
if action_serializer_classes:
break
return action_serializer_classes
def get_json_field_action_serializer_classes(self):
category_collection = self.json_fields_category_mapping[self.__field]
if category_collection:
serializer_classes = self.get_json_field_action_serializer_classes_by_query_category()
else:
serializer_classes = self.json_fields_serializer_classes[self.__field]
return serializer_classes
def get_json_field_serializer_class_by_action(self, serializer_classes):
if serializer_classes is None:
return None
if self.action in ['metadata']:
action = self.request.query_params.get('action')
if not action:
raise JMSException('The `metadata` methods must carry query parameter `action`')
else:
action = self.action
serializer_class = serializer_classes.get(action)
return serializer_class
@lazyproperty
def default_json_field_serializer_class(self):
readonly_json_field_serializer_class = type(
'DefaultReadonlyJSONFieldSerializer', (serializers.JSONField,),
)
return readonly_json_field_serializer_class
def get_json_field_serializer(self):
serializer_classes = self.get_json_field_action_serializer_classes()
serializer_class = self.get_json_field_serializer_class_by_action(serializer_classes)
if serializer_class:
serializer = serializer_class()
return serializer
serializer_class = serializer_classes.get('default')
if serializer_class:
serializer = serializer_class(**{'read_only': True})
return serializer
return self.default_json_field_serializer_class(**{'read_only': True})
def get_json_fields_serializer_mapping(self):
def get_dynamic_mapping_fields_mapping_rule(self):
"""
return: {
'json_field_1': serializer1(),
'json_field_2': serializer2(),
return:
{
'meta': ['type', 'apply_asset', 'get'],
'meta2': 'category.login'
}
"""
fields_serializer_mapping = {}
fields = self.json_fields_serializer_classes.keys()
for field in fields:
self.__field = field
serializer = self.get_json_field_serializer()
fields_serializer_mapping[self.__field] = serializer
return fields_serializer_mapping
return {}
def build_include_json_fields_serializer_class(self, base, attrs):
serializer_class_name = ''.join([
field_serializer.__class__.__name__ for field_serializer in attrs.values()
])
serializer_class = type(serializer_class_name, (base,), attrs)
@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 not isinstance(self.json_fields_serializer_classes, dict):
fields_mapping_rule = self.get_dynamic_mapping_fields_mapping_rule()
if not fields_mapping_rule:
return serializer_class
fields_serializer_mapping = self.get_json_fields_serializer_mapping()
if not fields_serializer_mapping:
return serializer_class
serializer_class = self.build_include_json_fields_serializer_class(
base=serializer_class, attrs=fields_serializer_mapping
)
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(JSONFieldsModelSerializerMixin, GenericSerializerMixin):
class SerializerMixin(IncludeDynamicMappingFieldSerializerViewMixin):
pass

View File

@@ -1,6 +0,0 @@
"""
老的代码统一到 `apps/common/drf/serializers.py` 中,
之后此文件废弃
"""
from common.drf.serializers import AdaptedBulkListSerializer, CeleryTaskSerializer