mirror of https://github.com/jumpserver/jumpserver
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>pull/5372/head
parent
5c483084b7
commit
cef93abb2f
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from .. import models
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.fields.serializer import CustomMetaDictField
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.fields import CustomMetaDictField
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from assets.models import Asset
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from ..models import Node, AdminUser
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import AuthBook, Asset
|
||||
from ..backends import AssetUserManager
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
import re
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields import ChoiceDisplayField
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
||||
|
@ -26,7 +25,6 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
|||
|
||||
|
||||
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
|
||||
# serializer_choice_field = ChoiceDisplayField
|
||||
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display')
|
||||
action_display = serializers.ReadOnlyField(source='get_action_display')
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.validators import NoSpecialChars
|
||||
from ..models import Domain, Gateway
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..models import FavoriteAsset
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
||||
from ..models import Label
|
||||
|
|
|
@ -2,7 +2,7 @@ from rest_framework import serializers
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import Count
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from common.mixins.serializers import BulkSerializerMixin
|
||||
from common.utils import ssh_pubkey_gen
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
|
|
@ -5,7 +5,7 @@ from rest_framework import serializers
|
|||
from django.db.models import F
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from terminal.models import Session
|
||||
from ops.models import CommandExecution
|
||||
from . import models
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .form import *
|
||||
from .model import *
|
||||
from .serializer import *
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
"""
|
||||
老的代码统一到 `apps/common/drf/serializers.py` 中,
|
||||
之后此文件废弃
|
||||
"""
|
||||
|
||||
from common.drf.serializers import AdaptedBulkListSerializer, CeleryTaskSerializer
|
|
@ -7,7 +7,7 @@ from rest_framework.views import Response
|
|||
|
||||
from common.drf.api import JMSBulkModelViewSet
|
||||
from common.permissions import IsOrgAdmin
|
||||
from common.serializers import CeleryTaskSerializer
|
||||
from common.drf.serializers import CeleryTaskSerializer
|
||||
from ..models import Task, AdHoc, AdHocExecution
|
||||
from ..serializers import (
|
||||
TaskSerializer,
|
||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.models.user import User
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import BulkModelSerializer
|
||||
from common.db.models import concated_display as display
|
||||
from .models import Organization, OrganizationMember, ROLE
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from perms.models import ApplicationPermission
|
||||
|
||||
__all__ = [
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from assets.models import Asset, Node
|
||||
from perms.models import AssetPermission
|
||||
from users.models import User
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.db.models import Count
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from .. import models
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from perms.serializers.base import PermissionAllUserSerializer
|
|||
from rest_framework import serializers
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from .. import models
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
|||
from django.db.models import Count
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import RemoteAppPermission
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from ..models import RemoteAppPermission
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from rest_framework import serializers
|
|||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from ..models import Session
|
||||
|
||||
__all__ = [
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import copy
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields.serializer import CustomMetaDictField
|
||||
from common.drf.fields import CustomMetaDictField
|
||||
from ..models import ReplayStorage, CommandStorage
|
||||
from .. import const
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
from tickets import const, serializers
|
||||
|
||||
|
||||
__all__ = ['TicketJSONFieldsModelSerializerViewMixin']
|
||||
|
||||
|
||||
class TicketJSONFieldsModelSerializerViewMixin:
|
||||
json_fields_category_mapping = {
|
||||
'meta': {
|
||||
'type': const.TicketTypeChoices.values,
|
||||
},
|
||||
}
|
||||
json_fields_serializer_classes = {
|
||||
'meta': {
|
||||
'type': {
|
||||
const.TicketTypeChoices.apply_asset.value: {
|
||||
'default': serializers.TicketMetaApplyAssetSerializer,
|
||||
'open': serializers.TicketMetaApplyAssetApplySerializer,
|
||||
'approve': serializers.TicketMetaApplyAssetApproveSerializer
|
||||
},
|
||||
const.TicketTypeChoices.apply_application.value: {
|
||||
'default': serializers.TicketMetaApplyApplicationSerializer,
|
||||
'open': serializers.TicketMetaApplyApplicationApplySerializer,
|
||||
'approve': serializers.TicketMetaApplyApplicationApproveSerializer,
|
||||
},
|
||||
const.TicketTypeChoices.login_confirm.value: {
|
||||
'default': serializers.TicketMetaLoginConfirmSerializer,
|
||||
'open': serializers.TicketMetaLoginConfirmApplySerializer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,16 +8,15 @@ from rest_framework.exceptions import MethodNotAllowed
|
|||
from common.mixins.api import CommonApiMixin
|
||||
from common.permissions import IsValidUser, IsOrgAdmin
|
||||
from common.const.http import POST, PUT
|
||||
from tickets import serializers
|
||||
from tickets import serializers, const
|
||||
from tickets.permissions.ticket import IsAssignee, NotClosed
|
||||
from tickets.models import Ticket
|
||||
from tickets.api.ticket.mixin import TicketJSONFieldsModelSerializerViewMixin
|
||||
|
||||
|
||||
__all__ = ['TicketViewSet']
|
||||
|
||||
|
||||
class TicketViewSet(TicketJSONFieldsModelSerializerViewMixin, CommonApiMixin, viewsets.ModelViewSet):
|
||||
class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = serializers.TicketSerializer
|
||||
serializer_classes = {
|
||||
|
@ -64,3 +63,11 @@ class TicketViewSet(TicketJSONFieldsModelSerializerViewMixin, CommonApiMixin, vi
|
|||
@action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed])
|
||||
def close(self, request, *args, **kwargs):
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
def get_dynamic_mapping_fields_mapping_rule(self):
|
||||
from tickets.serializers.ticket.meta import get_meta_field_mapping_rule_by_view
|
||||
meta_field_mapping_rule = get_meta_field_mapping_rule_by_view(self)
|
||||
fields_mapping_rule = {
|
||||
'meta': meta_field_mapping_rule,
|
||||
}
|
||||
return fields_mapping_rule
|
||||
|
|
|
@ -55,6 +55,13 @@ def migrate_field_action(old_action, old_status):
|
|||
return ACTION_CLOSE
|
||||
|
||||
|
||||
def migrate_field_assignees_display(assignees_display):
|
||||
if not assignees_display:
|
||||
return []
|
||||
assignees_display = assignees_display.split(', ')
|
||||
return assignees_display
|
||||
|
||||
|
||||
def migrate_tickets_fields_name(apps, schema_editor):
|
||||
ticket_model = apps.get_model("tickets", "Ticket")
|
||||
tickets = ticket_model.origin_objects.all()
|
||||
|
@ -64,6 +71,7 @@ def migrate_tickets_fields_name(apps, schema_editor):
|
|||
ticket.applicant_display = ticket.user_display
|
||||
ticket.processor = ticket.assignee
|
||||
ticket.processor_display = ticket.assignee_display
|
||||
ticket.assignees_display_new = migrate_field_assignees_display(ticket.assignees_display)
|
||||
ticket.action = migrate_field_action(ticket.action, ticket.status)
|
||||
ticket.type = migrate_field_type(ticket.type)
|
||||
ticket.meta = migrate_field_meta(ticket.type, ticket.meta)
|
||||
|
@ -100,16 +108,16 @@ class Migration(migrations.Migration):
|
|||
name='processor_display',
|
||||
field=models.CharField(blank=True, default='No', max_length=256, null=True, verbose_name='Processor display'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='assignees_display_new',
|
||||
field=models.JSONField(default=list, encoder=tickets.models.ticket.model.ModelJSONFieldEncoder, verbose_name='Assignees display'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ticket',
|
||||
name='assignees',
|
||||
field=models.ManyToManyField(related_name='assigned_tickets', to=settings.AUTH_USER_MODEL, verbose_name='Assignees'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ticket',
|
||||
name='assignees_display',
|
||||
field=models.TextField(blank=True, default='No', verbose_name='Assignees display'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ticket',
|
||||
name='meta',
|
||||
|
@ -151,6 +159,15 @@ class Migration(migrations.Migration):
|
|||
model_name='ticket',
|
||||
name='body',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='ticket',
|
||||
name='assignees_display',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='ticket',
|
||||
old_name='assignees_display_new',
|
||||
new_name='assignees_display',
|
||||
),
|
||||
migrations.AlterModelManagers(
|
||||
name='ticket',
|
||||
managers=[
|
||||
|
|
|
@ -10,8 +10,7 @@ class SetDisplayFieldMixin:
|
|||
|
||||
def set_assignees_display(self):
|
||||
if self.has_applied:
|
||||
assignees_display = [str(assignee) for assignee in self.assignees.all()]
|
||||
self.assignees_display = ', '.join(assignees_display)
|
||||
self.assignees_display = [str(assignee) for assignee in self.assignees.all()]
|
||||
|
||||
def set_processor_display(self):
|
||||
if self.has_processed:
|
||||
|
|
|
@ -65,8 +65,8 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
|
|||
assignees = models.ManyToManyField(
|
||||
'users.User', related_name='assigned_tickets', verbose_name=_("Assignees")
|
||||
)
|
||||
assignees_display = models.TextField(
|
||||
blank=True, default='No', verbose_name=_("Assignees display")
|
||||
assignees_display = models.JSONField(
|
||||
encoder=ModelJSONFieldEncoder, default=list, verbose_name=_('Assignees display')
|
||||
)
|
||||
# 评论
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
from ..models import Comment
|
||||
from common.fields.serializer import ReadableHiddenField
|
||||
from common.drf.fields import ReadableHiddenField
|
||||
|
||||
__all__ = ['CommentSerializer']
|
||||
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
from .apply_asset import *
|
||||
from .apply_application import *
|
||||
from .login_confirm import *
|
||||
from .base import *
|
||||
|
|
|
@ -3,18 +3,14 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from applications.models import Category, Application
|
||||
from assets.models import SystemUser
|
||||
from .base import BaseTicketMetaApproveSerializerMixin
|
||||
from common.fields.serializer import JSONFieldModelSerializer
|
||||
from tickets.models import Ticket
|
||||
from .mixin import BaseApproveSerializerMixin
|
||||
|
||||
__all__ = [
|
||||
'TicketMetaApplyApplicationSerializer',
|
||||
'TicketMetaApplyApplicationApplySerializer',
|
||||
'TicketMetaApplyApplicationApproveSerializer',
|
||||
'ApplyApplicationTypeSerializer', 'ApplySerializer', 'ApproveSerializer',
|
||||
]
|
||||
|
||||
|
||||
class TicketMetaApplyApplicationSerializer(JSONFieldModelSerializer):
|
||||
class ApplySerializer(serializers.Serializer):
|
||||
# 申请信息
|
||||
apply_category = serializers.ChoiceField(
|
||||
required=True, choices=Category.choices, label=_('Category')
|
||||
|
@ -42,6 +38,19 @@ class TicketMetaApplyApplicationSerializer(JSONFieldModelSerializer):
|
|||
apply_date_expired = serializers.DateTimeField(
|
||||
required=True, label=_('Date expired')
|
||||
)
|
||||
|
||||
def validate_apply_type(self, tp):
|
||||
category = self.root.initial_data['meta'].get('apply_category')
|
||||
if not category:
|
||||
return tp
|
||||
valid_type_types = list((dict(Category.get_type_choices(category)).keys()))
|
||||
if tp in valid_type_types:
|
||||
return tp
|
||||
error = _('Type `{}` is not a valid choice `({}){}`'.format(tp, category, valid_type_types))
|
||||
raise serializers.ValidationError(error)
|
||||
|
||||
|
||||
class ApproveSerializer(BaseApproveSerializerMixin, serializers.Serializer):
|
||||
# 审批信息
|
||||
approve_applications = serializers.ListField(
|
||||
required=True, child=serializers.UUIDField(), label=_('Approve applications')
|
||||
|
@ -66,57 +75,6 @@ class TicketMetaApplyApplicationSerializer(JSONFieldModelSerializer):
|
|||
required=True, label=_('Date expired')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
model_field = Ticket.meta
|
||||
fields = [
|
||||
'apply_category', 'apply_category_display',
|
||||
'apply_type', 'apply_type_display',
|
||||
'apply_application_group', 'apply_system_user_group',
|
||||
'apply_date_start', 'apply_date_expired',
|
||||
|
||||
'approve_applications', 'approve_applications_snapshot',
|
||||
'approve_system_users', 'approve_system_users_snapshot',
|
||||
'approve_date_start', 'approve_date_expired'
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class TicketMetaApplyApplicationApplySerializer(TicketMetaApplyApplicationSerializer):
|
||||
|
||||
class Meta(TicketMetaApplyApplicationSerializer.Meta):
|
||||
required_fields = [
|
||||
'apply_category', 'apply_type',
|
||||
'apply_application_group', 'apply_system_user_group',
|
||||
'apply_date_start', 'apply_date_expired',
|
||||
]
|
||||
read_only_fields = list(
|
||||
set(TicketMetaApplyApplicationSerializer.Meta.fields) - set(required_fields)
|
||||
)
|
||||
|
||||
def validate_apply_type(self, tp):
|
||||
category = self.root.initial_data['meta'].get('apply_category')
|
||||
if not category:
|
||||
return tp
|
||||
valid_type_types = list((dict(Category.get_type_choices(category)).keys()))
|
||||
if tp in valid_type_types:
|
||||
return tp
|
||||
error = _('Type `{}` is not a valid choice `({}){}`'.format(tp, category, valid_type_types))
|
||||
raise serializers.ValidationError(error)
|
||||
|
||||
|
||||
class TicketMetaApplyApplicationApproveSerializer(BaseTicketMetaApproveSerializerMixin,
|
||||
TicketMetaApplyApplicationSerializer):
|
||||
|
||||
class Meta:
|
||||
required_fields = {
|
||||
'approve_applications', 'approve_system_users',
|
||||
'approve_date_start', 'approve_date_expired'
|
||||
}
|
||||
read_only_fields = list(
|
||||
set(TicketMetaApplyApplicationSerializer.Meta.fields) - set(required_fields)
|
||||
)
|
||||
|
||||
def validate_approve_applications(self, approve_applications):
|
||||
application_type = self.root.instance.meta['apply_type']
|
||||
queries = {'type': application_type}
|
||||
|
@ -131,3 +89,9 @@ class TicketMetaApplyApplicationApproveSerializer(BaseTicketMetaApproveSerialize
|
|||
queries = {'protocol': protocol}
|
||||
system_users_id = self.filter_approve_system_users(approve_system_users, queries)
|
||||
return system_users_id
|
||||
|
||||
|
||||
class ApplyApplicationTypeSerializer(ApplySerializer, ApproveSerializer):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -2,20 +2,16 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from rest_framework import serializers
|
||||
from perms.serializers import ActionsField
|
||||
from assets.models import Asset, SystemUser
|
||||
from .base import BaseTicketMetaApproveSerializerMixin
|
||||
from tickets.models import Ticket
|
||||
|
||||
from common.fields.serializer import JSONFieldModelSerializer
|
||||
from .mixin import BaseApproveSerializerMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'TicketMetaApplyAssetSerializer',
|
||||
'TicketMetaApplyAssetApplySerializer',
|
||||
'TicketMetaApplyAssetApproveSerializer',
|
||||
'ApplyAssetTypeSerializer', 'ApplySerializer', 'ApproveSerializer',
|
||||
]
|
||||
|
||||
|
||||
class TicketMetaApplyAssetSerializer(JSONFieldModelSerializer):
|
||||
class ApplySerializer(serializers.Serializer):
|
||||
# 申请信息
|
||||
apply_ip_group = serializers.ListField(
|
||||
required=False, child=serializers.IPAddressField(), label=_('IP group'),
|
||||
|
@ -43,6 +39,9 @@ class TicketMetaApplyAssetSerializer(JSONFieldModelSerializer):
|
|||
apply_date_expired = serializers.DateTimeField(
|
||||
required=True, label=_('Date expired')
|
||||
)
|
||||
|
||||
|
||||
class ApproveSerializer(BaseApproveSerializerMixin, serializers.Serializer):
|
||||
# 审批信息
|
||||
approve_assets = serializers.ListField(
|
||||
required=True, child=serializers.UUIDField(), label=_('Approve assets')
|
||||
|
@ -75,47 +74,6 @@ class TicketMetaApplyAssetSerializer(JSONFieldModelSerializer):
|
|||
required=True, label=_('Date expired')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
model_field = Ticket.meta
|
||||
fields = [
|
||||
'apply_ip_group',
|
||||
'apply_hostname_group', 'apply_system_user_group',
|
||||
'apply_actions', 'apply_actions_display',
|
||||
'apply_date_start', 'apply_date_expired',
|
||||
|
||||
'approve_assets', 'approve_assets_snapshot',
|
||||
'approve_system_users', 'approve_system_users_snapshot',
|
||||
'approve_actions', 'approve_actions_display',
|
||||
'approve_date_start', 'approve_date_expired',
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class TicketMetaApplyAssetApplySerializer(TicketMetaApplyAssetSerializer):
|
||||
|
||||
class Meta(TicketMetaApplyAssetSerializer.Meta):
|
||||
required_fields = [
|
||||
'apply_ip_group', 'apply_hostname_group', 'apply_system_user_group',
|
||||
'apply_actions', 'apply_date_start', 'apply_date_expired',
|
||||
]
|
||||
read_only_fields = list(
|
||||
set(TicketMetaApplyAssetSerializer.Meta.fields) - set(required_fields)
|
||||
)
|
||||
|
||||
|
||||
class TicketMetaApplyAssetApproveSerializer(BaseTicketMetaApproveSerializerMixin,
|
||||
TicketMetaApplyAssetSerializer):
|
||||
|
||||
class Meta(TicketMetaApplyAssetSerializer.Meta):
|
||||
required_fields = [
|
||||
'approve_assets', 'approve_system_users', 'approve_actions',
|
||||
'approve_date_start', 'approve_date_expired',
|
||||
]
|
||||
read_only_fields = list(
|
||||
set(TicketMetaApplyAssetSerializer.Meta.fields) - set(required_fields)
|
||||
)
|
||||
|
||||
def validate_approve_assets(self, approve_assets):
|
||||
assets_id = self.filter_approve_resources(resource_model=Asset, resources_id=approve_assets)
|
||||
return assets_id
|
||||
|
@ -124,3 +82,7 @@ class TicketMetaApplyAssetApproveSerializer(BaseTicketMetaApproveSerializerMixin
|
|||
queries = {'protocol__in': SystemUser.ASSET_CATEGORY_PROTOCOLS}
|
||||
system_users_id = self.filter_approve_system_users(approve_system_users, queries)
|
||||
return system_users_id
|
||||
|
||||
|
||||
class ApplyAssetTypeSerializer(ApplySerializer, ApproveSerializer):
|
||||
pass
|
||||
|
|
|
@ -1,42 +1,104 @@
|
|||
import copy
|
||||
from collections import OrderedDict
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import ALL_FIELDS
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.utils import tmp_to_org
|
||||
from assets.models import SystemUser
|
||||
from common.exceptions import JMSException
|
||||
from tickets import const
|
||||
from . import apply_asset, apply_application, login_confirm
|
||||
|
||||
__all__ = [
|
||||
'meta_dynamic_mapping_fields_mapping_rules',
|
||||
'get_meta_field_mapping_rule_by_view',
|
||||
]
|
||||
|
||||
#
|
||||
# ticket type
|
||||
# -----------
|
||||
|
||||
|
||||
class BaseTicketMetaApproveSerializerMixin:
|
||||
types = const.TicketTypeChoices.values
|
||||
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 type
|
||||
# -----------
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
#
|
||||
# define meta field `DynamicMappingField` mapping_rules
|
||||
# -----------------------------------------------------
|
||||
|
||||
|
||||
meta_dynamic_mapping_fields_mapping_rules = {
|
||||
'default': serializers.ReadOnlyField,
|
||||
'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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# get meta dynamic field mapping rule by view
|
||||
# -------------------------------------------
|
||||
|
||||
|
||||
def get_meta_field_mapping_rule_by_view(view):
|
||||
mapping_rules = copy.deepcopy(meta_dynamic_mapping_fields_mapping_rules)
|
||||
request = view.request
|
||||
|
||||
# type
|
||||
tp = request.query_params.get('type')
|
||||
if not tp:
|
||||
return ['default']
|
||||
if tp not in types:
|
||||
error = _('Query parameter `type` ({}) not in choices: {}'.format(tp, types))
|
||||
raise JMSException(error)
|
||||
if tp not in mapping_rules['type']:
|
||||
return ['default']
|
||||
|
||||
# action
|
||||
action = view.action
|
||||
if action in ['metadata']:
|
||||
# options
|
||||
action = request.query_params.get('action')
|
||||
if not action:
|
||||
error = _('Please carry query parameter `action`')
|
||||
raise JMSException(error)
|
||||
if action not in actions:
|
||||
error = _('Query parameter `action` ({}) not in choices: {}'.format(action, actions))
|
||||
raise JMSException(error)
|
||||
if action not in mapping_rules['type'][tp]:
|
||||
return ['default']
|
||||
|
||||
# display
|
||||
if action in ['list', 'retrieve']:
|
||||
return ['default']
|
||||
|
||||
if not mapping_rules['type'][tp].get(action):
|
||||
return ['default']
|
||||
|
||||
return ['type', tp, action]
|
||||
|
||||
|
||||
def _filter_approve_resources_by_org(self, model, resources_id):
|
||||
with tmp_to_org(self.root.instance.org_id):
|
||||
org_resources = model.objects.filter(id__in=resources_id)
|
||||
if not org_resources:
|
||||
error = _('None of the approved `{}` belong to Organization `{}`'
|
||||
''.format(model.__name__, self.root.instance.org_name))
|
||||
raise serializers.ValidationError(error)
|
||||
return org_resources
|
||||
|
||||
@staticmethod
|
||||
def _filter_approve_resources_by_queries(model, resources, queries=None):
|
||||
if queries:
|
||||
resources = resources.filter(**queries)
|
||||
if not resources:
|
||||
error = _('None of the approved `{}` does not comply with the filtering rules `{}`'
|
||||
''.format(model.__name__, queries))
|
||||
raise serializers.ValidationError(error)
|
||||
return resources
|
||||
|
||||
def filter_approve_resources(self, resource_model, resources_id, queries=None):
|
||||
resources = self._filter_approve_resources_by_org(resource_model, resources_id)
|
||||
resources = self._filter_approve_resources_by_queries(resource_model, resources, queries)
|
||||
resources_id = list(resources.values_list('id', flat=True))
|
||||
return resources_id
|
||||
|
||||
def filter_approve_system_users(self, system_users_id, queries=None):
|
||||
system_users_id = self.filter_approve_resources(
|
||||
resource_model=SystemUser, resources_id=system_users_id, queries=queries
|
||||
)
|
||||
return system_users_id
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common.fields.serializer import JSONFieldModelSerializer
|
||||
from tickets.models import Ticket
|
||||
|
||||
__all__ = [
|
||||
'TicketMetaLoginConfirmSerializer', 'TicketMetaLoginConfirmApplySerializer',
|
||||
'ApplySerializer',
|
||||
]
|
||||
|
||||
|
||||
class TicketMetaLoginConfirmSerializer(JSONFieldModelSerializer):
|
||||
class ApplySerializer(serializers.Serializer):
|
||||
# 申请信息
|
||||
apply_login_ip = serializers.IPAddressField(
|
||||
required=True, label=_('Login ip')
|
||||
)
|
||||
|
@ -20,21 +20,3 @@ class TicketMetaLoginConfirmSerializer(JSONFieldModelSerializer):
|
|||
required=True, label=_('Login datetime')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
model_field = Ticket.meta
|
||||
fields = [
|
||||
'apply_login_ip', 'apply_login_city', 'apply_login_datetime'
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class TicketMetaLoginConfirmApplySerializer(TicketMetaLoginConfirmSerializer):
|
||||
|
||||
class Meta(TicketMetaLoginConfirmSerializer.Meta):
|
||||
required_fields = [
|
||||
'apply_login_ip', 'apply_login_city', 'apply_login_datetime'
|
||||
]
|
||||
read_only_fields = list(
|
||||
set(TicketMetaLoginConfirmSerializer.Meta.fields) - set(required_fields)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.utils import tmp_to_org
|
||||
from assets.models import SystemUser
|
||||
|
||||
|
||||
class BaseApproveSerializerMixin:
|
||||
|
||||
def _filter_approve_resources_by_org(self, model, resources_id):
|
||||
with tmp_to_org(self.root.instance.org_id):
|
||||
org_resources = model.objects.filter(id__in=resources_id)
|
||||
if not org_resources:
|
||||
error = _('None of the approved `{}` belong to Organization `{}`'
|
||||
''.format(model.__name__, self.root.instance.org_name))
|
||||
raise serializers.ValidationError(error)
|
||||
return org_resources
|
||||
|
||||
@staticmethod
|
||||
def _filter_approve_resources_by_queries(model, resources, queries=None):
|
||||
if queries:
|
||||
resources = resources.filter(**queries)
|
||||
if not resources:
|
||||
error = _('None of the approved `{}` does not comply with the filtering rules `{}`'
|
||||
''.format(model.__name__, queries))
|
||||
raise serializers.ValidationError(error)
|
||||
return resources
|
||||
|
||||
def filter_approve_resources(self, resource_model, resources_id, queries=None):
|
||||
resources = self._filter_approve_resources_by_org(resource_model, resources_id)
|
||||
resources = self._filter_approve_resources_by_queries(resource_model, resources, queries)
|
||||
resources_id = list(resources.values_list('id', flat=True))
|
||||
return resources_id
|
||||
|
||||
def filter_approve_system_users(self, system_users_id, queries=None):
|
||||
system_users_id = self.filter_approve_resources(
|
||||
resource_model=SystemUser, resources_id=system_users_id, queries=queries
|
||||
)
|
||||
return system_users_id
|
|
@ -2,12 +2,13 @@
|
|||
#
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from common.fields.serializer import ReadableHiddenField
|
||||
from common.drf.fields import ReadableHiddenField, DynamicMappingField
|
||||
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_dynamic_mapping_fields_mapping_rules
|
||||
|
||||
__all__ = [
|
||||
'TicketSerializer', 'TicketDisplaySerializer',
|
||||
|
@ -18,8 +19,9 @@ __all__ = [
|
|||
|
||||
class TicketSerializer(OrgResourceModelSerializerMixin):
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status'))
|
||||
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status'))
|
||||
meta = DynamicMappingField(mapping_rules=meta_dynamic_mapping_fields_mapping_rules)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
"""
|
||||
说明:
|
||||
View 获取 serializer_class 的架构设计
|
||||
|
||||
问题:
|
||||
View 所需的 Serializer 中有一个 JSONField 字段,而字段的值并不固定,是由 View 的行为 (比如action) 而决定的.
|
||||
使用 View 默认的 get_serializer_class 方法不能解决
|
||||
|
||||
|
||||
例如:
|
||||
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()
|
||||
|
||||
分析:
|
||||
问题关键在于数据的映射
|
||||
使用 dict 可以解决,但操作起来比较复杂
|
||||
所以使用 tree 的结构来实现
|
||||
|
||||
|
||||
方案:
|
||||
view:
|
||||
使用元类 MetaClass: 在 View 中动态创建所需要的 Serializer
|
||||
|
||||
serializer:
|
||||
实现 DictSerializer: 在 Serializer 中定义 JSONField 字段的映射关系
|
||||
实现 TreeSerializer: 将 DictSerializer 中定义的映射关系转换为 Tree 的结构
|
||||
实现 DictSerializerMetaClass: 在 View 中用来动态创建 Serializer
|
||||
|
||||
实现:
|
||||
1. 重写 View 的 get_serializer_class 方法, 实现动态创建 Serializer
|
||||
2. 实现 TreeSerializer, 将 DictSerializer 中的 dict 数据结构转化为 tree 的数据结构
|
||||
3. 实现 DictSerializer, 使用 dict 类型来定义映射关系 (*注意: 继承 TreeSerializer)
|
||||
4. 实现 DictSerializerMetaClass, 定义如何创建包含字段类型为 TreeSerializer 的 Serializer
|
||||
|
||||
"""
|
||||
|
||||
from rest_framework.serializers import Serializer
|
|
@ -0,0 +1,224 @@
|
|||
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)
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
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)
|
|
@ -0,0 +1,196 @@
|
|||
# 测试通过 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)
|
|
@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.db.models import Prefetch
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from django.db.models import Count
|
||||
from ..models import User, UserGroup
|
||||
|
|
|
@ -108,3 +108,4 @@ adal==1.2.5
|
|||
openpyxl==3.0.5
|
||||
pyexcel==0.6.6
|
||||
pyexcel-xlsx==0.6.0
|
||||
data-tree==0.0.1
|
||||
|
|
Loading…
Reference in New Issue