reactor: 重构命令&录像存储模块的Serializer及相关模块 (#5392)

* reactor: 重构命令&录像存储模块的Serializer及相关模块


Co-authored-by: Bai <bugatti_it@163.com>
pull/5406/head
fit2bot 2021-01-12 18:06:42 +08:00 committed by GitHub
parent b3f359d47b
commit 86a055638c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 636 additions and 551 deletions

View File

@ -18,15 +18,19 @@ class ApplicationSerializerMixin(serializers.Serializer):
attrs = MethodSerializer() attrs = MethodSerializer()
def get_attrs_serializer(self): def get_attrs_serializer(self):
request = self.context['request'] serializer_class = None
query_type = request.query_params.get('type') if isinstance(self.instance, models.Application):
query_category = request.query_params.get('category') instance_type = self.instance.type
if query_type: serializer_class = type_serializer_classes_mapping.get(instance_type)
serializer_class = type_serializer_classes_mapping.get(query_type)
elif query_category:
serializer_class = category_serializer_classes_mapping.get(query_category)
else: else:
serializer_class = None request = self.context['request']
query_type = request.query_params.get('type')
query_category = request.query_params.get('category')
if query_type:
serializer_class = type_serializer_classes_mapping.get(query_type)
elif query_category:
serializer_class = category_serializer_classes_mapping.get(query_category)
if serializer_class is None: if serializer_class is None:
serializer_class = serializers.Serializer serializer_class = serializers.Serializer
serializer = serializer_class() serializer = serializer_class()

View File

@ -6,4 +6,4 @@ __all__ = ['CloudSerializer']
class CloudSerializer(serializers.Serializer): class CloudSerializer(serializers.Serializer):
cluster = serializers.CharField(max_length=1024, label=_('Cluster')) cluster = serializers.CharField(max_length=1024, label=_('Cluster'), allow_null=True)

View File

@ -8,9 +8,8 @@ __all__ = ['DBSerializer']
class DBSerializer(serializers.Serializer): class DBSerializer(serializers.Serializer):
host = serializers.CharField(max_length=128, label=_('Host')) host = serializers.CharField(max_length=128, label=_('Host'), allow_null=True)
port = serializers.IntegerField(label=_('Port')) port = serializers.IntegerField(label=_('Port'), allow_null=True)
# 添加allow_null=True兼容之前数据库中database字段为None的情况
database = serializers.CharField( database = serializers.CharField(
max_length=128, required=True, allow_null=True, label=_('Database') max_length=128, required=True, allow_null=True, label=_('Database')
) )

View File

@ -29,8 +29,12 @@ class CharPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
class RemoteAppSerializer(serializers.Serializer): class RemoteAppSerializer(serializers.Serializer):
asset_info = serializers.SerializerMethodField() asset_info = serializers.SerializerMethodField()
asset = CharPrimaryKeyRelatedField(queryset=Asset.objects, required=False, label=_("Asset")) asset = CharPrimaryKeyRelatedField(
path = serializers.CharField(max_length=128, label=_('Application path')) queryset=Asset.objects, required=False, label=_("Asset"), allow_null=True
)
path = serializers.CharField(
max_length=128, label=_('Application path'), allow_null=True
)
@staticmethod @staticmethod
def get_asset_info(obj): def get_asset_info(obj):

View File

@ -11,15 +11,16 @@ class ChromeSerializer(RemoteAppSerializer):
CHROME_PATH = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe' CHROME_PATH = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
path = serializers.CharField( path = serializers.CharField(
max_length=128, label=_('Application path'), default=CHROME_PATH max_length=128, label=_('Application path'), default=CHROME_PATH, allow_null=True,
) )
chrome_target = serializers.CharField( chrome_target = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Target URL') max_length=128, allow_blank=True, required=False, label=_('Target URL'), allow_null=True,
) )
chrome_username = serializers.CharField( chrome_username = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Username') max_length=128, allow_blank=True, required=False, label=_('Username'), allow_null=True,
) )
chrome_password = serializers.CharField( chrome_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password') max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'),
allow_null=True
) )

View File

@ -10,14 +10,18 @@ __all__ = ['CustomSerializer']
class CustomSerializer(RemoteAppSerializer): class CustomSerializer(RemoteAppSerializer):
custom_cmdline = serializers.CharField( custom_cmdline = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Operating parameter') max_length=128, allow_blank=True, required=False, label=_('Operating parameter'),
allow_null=True,
) )
custom_target = serializers.CharField( custom_target = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Target url') max_length=128, allow_blank=True, required=False, label=_('Target url'),
allow_null=True,
) )
custom_username = serializers.CharField( custom_username = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Username') max_length=128, allow_blank=True, required=False, label=_('Username'),
allow_null=True,
) )
custom_password = serializers.CharField( custom_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password') max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'),
allow_null=True,
) )

View File

@ -8,7 +8,7 @@ __all__ = ['MySQLSerializer']
class MySQLSerializer(DBSerializer): class MySQLSerializer(DBSerializer):
port = serializers.IntegerField(default=3306, label=_('Port')) port = serializers.IntegerField(default=3306, label=_('Port'), allow_null=True)

View File

@ -11,20 +11,26 @@ class MySQLWorkbenchSerializer(RemoteAppSerializer):
MYSQL_WORKBENCH_PATH = 'C:\Program Files\MySQL\MySQL Workbench 8.0 CE\MySQLWorkbench.exe' MYSQL_WORKBENCH_PATH = 'C:\Program Files\MySQL\MySQL Workbench 8.0 CE\MySQLWorkbench.exe'
path = serializers.CharField( path = serializers.CharField(
max_length=128, label=_('Application path'), default=MYSQL_WORKBENCH_PATH max_length=128, label=_('Application path'), default=MYSQL_WORKBENCH_PATH,
allow_null=True,
) )
mysql_workbench_ip = serializers.CharField( mysql_workbench_ip = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('IP') max_length=128, allow_blank=True, required=False, label=_('IP'),
allow_null=True,
) )
mysql_workbench_port = serializers.IntegerField( mysql_workbench_port = serializers.IntegerField(
required=False, label=_('Port') required=False, label=_('Port'),
allow_null=True,
) )
mysql_workbench_name = serializers.CharField( mysql_workbench_name = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Database') max_length=128, allow_blank=True, required=False, label=_('Database'),
allow_null=True,
) )
mysql_workbench_username = serializers.CharField( mysql_workbench_username = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Username') max_length=128, allow_blank=True, required=False, label=_('Username'),
allow_null=True,
) )
mysql_workbench_password = serializers.CharField( mysql_workbench_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password') max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'),
allow_null=True,
) )

View File

@ -8,5 +8,5 @@ __all__ = ['OracleSerializer']
class OracleSerializer(DBSerializer): class OracleSerializer(DBSerializer):
port = serializers.IntegerField(default=1521, label=_('Port')) port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True)

View File

@ -8,5 +8,5 @@ __all__ = ['PostgreSerializer']
class PostgreSerializer(DBSerializer): class PostgreSerializer(DBSerializer):
port = serializers.IntegerField(default=5432, label=_('Port')) port = serializers.IntegerField(default=5432, label=_('Port'), allow_null=True)

View File

@ -15,14 +15,18 @@ class VMwareClientSerializer(RemoteAppSerializer):
VMWARE_CLIENT_PATH = ''.join(PATH.split()) VMWARE_CLIENT_PATH = ''.join(PATH.split())
path = serializers.CharField( path = serializers.CharField(
max_length=128, label=_('Application path'), default=VMWARE_CLIENT_PATH max_length=128, label=_('Application path'), default=VMWARE_CLIENT_PATH,
allow_null=True
) )
vmware_target = serializers.CharField( vmware_target = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Target URL') max_length=128, allow_blank=True, required=False, label=_('Target URL'),
allow_null=True
) )
vmware_username = serializers.CharField( vmware_username = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Username') max_length=128, allow_blank=True, required=False, label=_('Username'),
allow_null=True
) )
vmware_password = serializers.CharField( vmware_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password') max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'),
allow_null=True
) )

View File

@ -208,12 +208,11 @@ class SystemUser(BaseUser):
def get_protocol_by_application_type(cls, app_type): def get_protocol_by_application_type(cls, app_type):
from applications.const import ApplicationTypeChoices from applications.const import ApplicationTypeChoices
if app_type in ApplicationTypeChoices.remote_app_types(): if app_type in ApplicationTypeChoices.remote_app_types():
return cls.PROTOCOL_RDP protocol = cls.PROTOCOL_RDP
protocol = None else:
other_types = [*ApplicationTypeChoices.db_types(), *ApplicationTypeChoices.cloud_types()]
if app_type in other_types and app_type in cls.APPLICATION_CATEGORY_PROTOCOLS:
protocol = app_type protocol = app_type
return protocol if protocol in cls.APPLICATION_CATEGORY_PROTOCOLS:
return protocol
class Meta: class Meta:
ordering = ['name'] ordering = ['name']

View File

@ -1,12 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import copy
from rest_framework import serializers from rest_framework import serializers
__all__ = [ __all__ = [
'ReadableHiddenField', 'CustomMetaDictField', 'ReadableHiddenField',
] ]
@ -24,87 +23,3 @@ class ReadableHiddenField(serializers.HiddenField):
if hasattr(value, 'id'): if hasattr(value, 'id'):
return getattr(value, 'id') return getattr(value, 'id')
return value 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,3 +1,4 @@
import copy
from rest_framework import serializers from rest_framework import serializers
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
@ -38,10 +39,10 @@ class MethodSerializer(serializers.Serializer):
@cached_property @cached_property
def serializer(self) -> serializers.Serializer: def serializer(self) -> serializers.Serializer:
method = getattr(self.parent, self.method_name) method = getattr(self.parent, self.method_name)
return method() _serializer = method()
# 设置serializer的parent值否则在serializer实例中获取parent会出现断层
def get_fields(self): setattr(_serializer, 'parent', self.parent)
return self.serializer.get_fields() return _serializer
@cached_property @cached_property
def fields(self): def fields(self):
@ -50,10 +51,16 @@ class MethodSerializer(serializers.Serializer):
这样在调用 field.parent , 才会达到预期的结果 这样在调用 field.parent , 才会达到预期的结果
比如: serializers.SerializerMethodField 比如: serializers.SerializerMethodField
""" """
fields = BindingDict(self.serializer) return self.serializer.fields
for key, value in self.get_fields().items():
fields[key] = value def run_validation(self, data=serializers.empty):
return fields return self.serializer.run_validation(data)
def to_representation(self, instance):
return self.serializer.to_representation(instance)
def get_initial(self):
return self.serializer.get_initial()
# Other Serializer # Other Serializer

View File

@ -20,10 +20,10 @@ class BaseStorageViewSetMixin:
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
instance = self.get_object() instance = self.get_object()
if instance.in_defaults(): if instance.type_null_or_server:
data = {'msg': _('Deleting the default storage is not allowed')} data = {'msg': _('Deleting the default storage is not allowed')}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST) return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
if instance.is_using(): if instance.is_use():
data = {'msg': _('Cannot delete storage that is being used')} data = {'msg': _('Cannot delete storage that is being used')}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST) return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)
@ -67,11 +67,9 @@ class BaseStorageTestConnectiveMixin:
return Response(data) return Response(data)
class CommandStorageTestConnectiveApi(BaseStorageTestConnectiveMixin, class CommandStorageTestConnectiveApi(BaseStorageTestConnectiveMixin, generics.RetrieveAPIView):
generics.RetrieveAPIView):
queryset = CommandStorage.objects.all() queryset = CommandStorage.objects.all()
class ReplayStorageTestConnectiveApi(BaseStorageTestConnectiveMixin, class ReplayStorageTestConnectiveApi(BaseStorageTestConnectiveMixin, generics.RetrieveAPIView):
generics.RetrieveAPIView):
queryset = ReplayStorage.objects.all() queryset = ReplayStorage.objects.all()

View File

@ -148,7 +148,5 @@ class TerminalConfig(APIView):
permission_classes = (IsAppUser,) permission_classes = (IsAppUser,)
def get(self, request): def get(self, request):
user = request.user config = request.user.terminal.config
terminal = user.terminal return Response(config, status=200)
configs = terminal.config
return Response(configs, status=200)

View File

@ -8,4 +8,4 @@ class TerminalConfig(AppConfig):
def ready(self): def ready(self):
from . import signals_handler from . import signals_handler
return super().ready() return super().ready()

View File

@ -3,7 +3,6 @@ from django.conf import settings
from django.utils.functional import LazyObject from django.utils.functional import LazyObject
from .command.serializers import SessionCommandSerializer from .command.serializers import SessionCommandSerializer
from ..const import COMMAND_STORAGE_TYPE_SERVER
TYPE_ENGINE_MAPPING = { TYPE_ENGINE_MAPPING = {
@ -30,13 +29,12 @@ def get_terminal_command_storages():
from ..models import CommandStorage from ..models import CommandStorage
storage_list = {} storage_list = {}
for s in CommandStorage.objects.all(): for s in CommandStorage.objects.all():
tp = s.type if s.type_server:
if tp == COMMAND_STORAGE_TYPE_SERVER:
storage = get_command_storage() storage = get_command_storage()
else: else:
if not TYPE_ENGINE_MAPPING.get(tp): if not TYPE_ENGINE_MAPPING.get(s.type):
continue continue
engine_class = import_module(TYPE_ENGINE_MAPPING[tp]) engine_class = import_module(TYPE_ENGINE_MAPPING[s.type])
storage = engine_class.CommandStore(s.config) storage = engine_class.CommandStore(s.config)
storage_list[s.name] = storage storage_list[s.name] = storage
return storage_list return storage_list

View File

@ -1,117 +1,31 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
ASSETS_CACHE_KEY = "terminal__session__assets"
USERS_CACHE_KEY = "terminal__session__users"
SYSTEM_USER_CACHE_KEY = "terminal__session__system_users"
# Replay Storage
REPLAY_STORAGE_TYPE_NULL = 'null'
REPLAY_STORAGE_TYPE_SERVER = 'server'
REPLAY_STORAGE_TYPE_S3 = 's3'
REPLAY_STORAGE_TYPE_CEPH = 'ceph'
REPLAY_STORAGE_TYPE_SWIFT = 'swift'
REPLAY_STORAGE_TYPE_OSS = 'oss'
REPLAY_STORAGE_TYPE_AZURE = 'azure'
REPLAY_STORAGE_TYPE_EMPTY_FIELDS = []
REPLAY_STORAGE_TYPE_S3_FIELDS = [
{'name': 'BUCKET'},
{'name': 'ACCESS_KEY', 'write_only': True},
{'name': 'SECRET_KEY', 'write_only': True},
{'name': 'ENDPOINT'}
]
REPLAY_STORAGE_TYPE_CEPH_FIELDS = [
{'name': 'BUCKET'},
{'name': 'ACCESS_KEY', 'write_only': True},
{'name': 'SECRET_KEY', 'write_only': True},
{'name': 'ENDPOINT'}
]
REPLAY_STORAGE_TYPE_SWIFT_FIELDS = [
{'name': 'BUCKET'},
{'name': 'ACCESS_KEY', 'write_only': True},
{'name': 'SECRET_KEY', 'write_only': True},
{'name': 'REGION'},
{'name': 'ENDPOINT'},
{'name': 'PROTOCOL'},
]
REPLAY_STORAGE_TYPE_OSS_FIELDS = [
{'name': 'BUCKET'},
{'name': 'ACCESS_KEY', 'write_only': True},
{'name': 'SECRET_KEY', 'write_only': True},
{'name': 'ENDPOINT'}
]
REPLAY_STORAGE_TYPE_AZURE_FIELDS = [
{'name': 'CONTAINER_NAME'},
{'name': 'ACCOUNT_NAME'},
{'name': 'ACCOUNT_KEY', 'write_only': True},
{'name': 'ENDPOINT_SUFFIX'}
]
REPLAY_STORAGE_TYPE_FIELDS_MAP = {
REPLAY_STORAGE_TYPE_NULL: REPLAY_STORAGE_TYPE_EMPTY_FIELDS,
REPLAY_STORAGE_TYPE_SERVER: REPLAY_STORAGE_TYPE_EMPTY_FIELDS,
REPLAY_STORAGE_TYPE_S3: REPLAY_STORAGE_TYPE_S3_FIELDS,
REPLAY_STORAGE_TYPE_CEPH: REPLAY_STORAGE_TYPE_CEPH_FIELDS,
REPLAY_STORAGE_TYPE_SWIFT: REPLAY_STORAGE_TYPE_SWIFT_FIELDS,
REPLAY_STORAGE_TYPE_OSS: REPLAY_STORAGE_TYPE_OSS_FIELDS,
REPLAY_STORAGE_TYPE_AZURE: REPLAY_STORAGE_TYPE_AZURE_FIELDS
}
REPLAY_STORAGE_TYPE_CHOICES_DEFAULT = [
(REPLAY_STORAGE_TYPE_NULL, 'Null'),
(REPLAY_STORAGE_TYPE_SERVER, 'Server'),
]
REPLAY_STORAGE_TYPE_CHOICES_EXTENDS = [
(REPLAY_STORAGE_TYPE_S3, 'S3'),
(REPLAY_STORAGE_TYPE_CEPH, 'Ceph'),
(REPLAY_STORAGE_TYPE_SWIFT, 'Swift'),
(REPLAY_STORAGE_TYPE_OSS, 'OSS'),
(REPLAY_STORAGE_TYPE_AZURE, 'Azure')
]
REPLAY_STORAGE_TYPE_CHOICES = REPLAY_STORAGE_TYPE_CHOICES_DEFAULT + \
REPLAY_STORAGE_TYPE_CHOICES_EXTENDS
# Command Storage
COMMAND_STORAGE_TYPE_NULL = 'null'
COMMAND_STORAGE_TYPE_SERVER = 'server'
COMMAND_STORAGE_TYPE_ES = 'es'
COMMAND_STORAGE_TYPE_EMPTY_FIELDS = []
COMMAND_STORAGE_TYPE_ES_FIELDS = [
{'name': 'HOSTS'},
{'name': 'INDEX'},
{'name': 'DOC_TYPE'}
]
COMMAND_STORAGE_TYPE_FIELDS_MAP = {
COMMAND_STORAGE_TYPE_NULL: COMMAND_STORAGE_TYPE_EMPTY_FIELDS,
COMMAND_STORAGE_TYPE_SERVER: COMMAND_STORAGE_TYPE_EMPTY_FIELDS,
COMMAND_STORAGE_TYPE_ES: COMMAND_STORAGE_TYPE_ES_FIELDS,
}
COMMAND_STORAGE_TYPE_CHOICES_DEFAULT = [
(COMMAND_STORAGE_TYPE_NULL, 'Null'),
(COMMAND_STORAGE_TYPE_SERVER, 'Server'),
]
COMMAND_STORAGE_TYPE_CHOICES_EXTENDS = [
(COMMAND_STORAGE_TYPE_ES, 'Elasticsearch')
]
COMMAND_STORAGE_TYPE_CHOICES = COMMAND_STORAGE_TYPE_CHOICES_DEFAULT + \
COMMAND_STORAGE_TYPE_CHOICES_EXTENDS
from django.db.models import TextChoices from django.db.models import TextChoices
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
# Replay & Command Storage Choices
# --------------------------------
class ReplayStorageTypeChoices(TextChoices):
null = 'null', 'Null',
server = 'server', 'Server'
s3 = 's3', 'S3'
ceph = 'ceph', 'Ceph'
swift = 'swift', 'Swift'
oss = 'oss', 'OSS'
azure = 'azure', 'Azure'
class CommandStorageTypeChoices(TextChoices):
null = 'null', 'Null',
server = 'server', 'Server'
es = 'es', 'Elasticsearch'
# Component Status Choices
# ------------------------
class ComponentStatusChoices(TextChoices): class ComponentStatusChoices(TextChoices):
critical = 'critical', _('Critical') critical = 'critical', _('Critical')

View File

@ -2,102 +2,113 @@ from __future__ import unicode_literals
import os import os
import jms_storage import jms_storage
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from common.mixins import CommonModelMixin from common.mixins import CommonModelMixin
from common.fields.model import EncryptJsonDictTextField from common.fields.model import EncryptJsonDictTextField
from .. import const
from .terminal import Terminal from .terminal import Terminal
from .. import const
class CommandStorage(CommonModelMixin): class CommandStorage(CommonModelMixin):
TYPE_CHOICES = const.COMMAND_STORAGE_TYPE_CHOICES
TYPE_DEFAULTS = dict(const.REPLAY_STORAGE_TYPE_CHOICES_DEFAULT).keys()
TYPE_SERVER = const.COMMAND_STORAGE_TYPE_SERVER
name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True) name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True)
type = models.CharField( type = models.CharField(
max_length=16, choices=TYPE_CHOICES, verbose_name=_('Type'), max_length=16, choices=const.CommandStorageTypeChoices.choices,
default=TYPE_SERVER default=const.CommandStorageTypeChoices.server.value, verbose_name=_('Type'),
) )
meta = EncryptJsonDictTextField(default={}) meta = EncryptJsonDictTextField(default={})
comment = models.TextField( comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
def __str__(self): def __str__(self):
return self.name return self.name
@property
def type_null(self):
return self.type == const.CommandStorageTypeChoices.null.value
@property
def type_server(self):
return self.type == const.CommandStorageTypeChoices.server.value
@property
def type_null_or_server(self):
return self.type_null or self.type_server
@property @property
def config(self): def config(self):
config = self.meta config = self.meta
config.update({'TYPE': self.type}) config.update({'TYPE': self.type})
return config return config
def in_defaults(self):
return self.type in self.TYPE_DEFAULTS
def is_valid(self): def is_valid(self):
if self.in_defaults(): if self.type_null_or_server:
return True return True
storage = jms_storage.get_log_storage(self.config) storage = jms_storage.get_log_storage(self.config)
return storage.ping() return storage.ping()
def is_using(self): def is_use(self):
return Terminal.objects.filter(command_storage=self.name).exists() return Terminal.objects.filter(command_storage=self.name).exists()
class ReplayStorage(CommonModelMixin): class ReplayStorage(CommonModelMixin):
TYPE_CHOICES = const.REPLAY_STORAGE_TYPE_CHOICES
TYPE_SERVER = const.REPLAY_STORAGE_TYPE_SERVER
TYPE_DEFAULTS = dict(const.REPLAY_STORAGE_TYPE_CHOICES_DEFAULT).keys()
name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True) name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True)
type = models.CharField( type = models.CharField(
max_length=16, choices=TYPE_CHOICES, verbose_name=_('Type'), max_length=16, choices=const.ReplayStorageTypeChoices.choices,
default=TYPE_SERVER default=const.ReplayStorageTypeChoices.server.value, verbose_name=_('Type')
) )
meta = EncryptJsonDictTextField(default={}) meta = EncryptJsonDictTextField(default={})
comment = models.TextField( comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
def __str__(self): def __str__(self):
return self.name return self.name
def convert_type(self): @property
s3_type_list = [const.REPLAY_STORAGE_TYPE_CEPH] def type_null(self):
tp = self.type return self.type == const.ReplayStorageTypeChoices.null.value
if tp in s3_type_list:
tp = const.REPLAY_STORAGE_TYPE_S3
return tp
def get_extra_config(self): @property
extra_config = {'TYPE': self.convert_type()} def type_server(self):
if self.type == const.REPLAY_STORAGE_TYPE_SWIFT: return self.type == const.ReplayStorageTypeChoices.server.value
extra_config.update({'signer': 'S3SignerType'})
return extra_config @property
def type_null_or_server(self):
return self.type_null or self.type_server
@property
def type_swift(self):
return self.type == const.ReplayStorageTypeChoices.swift.value
@property
def type_ceph(self):
return self.type == const.ReplayStorageTypeChoices.ceph.value
@property @property
def config(self): def config(self):
config = self.meta _config = {}
extra_config = self.get_extra_config()
config.update(extra_config)
return config
def in_defaults(self): # add type config
return self.type in self.TYPE_DEFAULTS if self.type_ceph:
_type = const.ReplayStorageTypeChoices.s3.value
else:
_type = self.type
_config.update({'TYPE': _type})
# add special config
if self.type_swift:
_config.update({'signer': 'S3SignerType'})
# add meta config
_config.update(self.meta)
return _config
def is_valid(self): def is_valid(self):
if self.in_defaults(): if self.type_null_or_server:
return True return True
storage = jms_storage.get_object_storage(self.config) storage = jms_storage.get_object_storage(self.config)
target = 'tests.py' target = 'tests.py'
src = os.path.join(settings.BASE_DIR, 'common', target) src = os.path.join(settings.BASE_DIR, 'common', target)
return storage.is_valid(src, target) return storage.is_valid(src, target)
def is_using(self): def is_use(self):
return Terminal.objects.filter(replay_storage=self.name).exists() return Terminal.objects.filter(replay_storage=self.name).exists()

View File

@ -2,74 +2,197 @@
# #
import copy import copy
from rest_framework import serializers from rest_framework import serializers
from urllib.parse import urlparse
from common.drf.fields import CustomMetaDictField from django.utils.translation import ugettext_lazy as _
from django.db.models import TextChoices
from common.drf.serializers import MethodSerializer
from ..models import ReplayStorage, CommandStorage from ..models import ReplayStorage, CommandStorage
from .. import const from .. import const
class ReplayStorageMetaDictField(CustomMetaDictField): # Replay storage serializers
type_fields_map = const.REPLAY_STORAGE_TYPE_FIELDS_MAP # --------------------------
default_type = const.REPLAY_STORAGE_TYPE_SERVER
convert_key_remove_type_prefix = True
convert_key_to_upper = True
class BaseStorageSerializerMixin: class ReplayStorageTypeBaseSerializer(serializers.Serializer):
type_fields_map = None BUCKET = serializers.CharField(
required=True, max_length=1024, label=_('Bucket'), allow_null=True
def process_meta(self, instance, validated_data): )
new_meta = copy.deepcopy(validated_data.get('meta', {})) ACCESS_KEY = serializers.CharField(
tp = validated_data.get('type', '') max_length=1024, required=False, allow_blank=True, write_only=True, label=_('Access key'),
allow_null=True,
if tp != instance.type: )
return new_meta SECRET_KEY = serializers.CharField(
max_length=1024, required=False, allow_blank=True, write_only=True, label=_('Secret key'),
old_meta = instance.meta allow_null=True,
fields = self.type_fields_map.get(instance.type, []) )
for field in fields: ENDPOINT = serializers.CharField(
if not field.get('write_only', False): required=True, max_length=1024, label=_('Endpoint'), allow_null=True,
continue )
field_name = field['name']
new_value = new_meta.get(field_name, '')
old_value = old_meta.get(field_name, '')
field_value = new_value if new_value else old_value
new_meta[field_name] = field_value
return new_meta
def update(self, instance, validated_data):
meta = self.process_meta(instance, validated_data)
validated_data['meta'] = meta
return super().update(instance, validated_data)
class ReplayStorageSerializer(BaseStorageSerializerMixin, class ReplayStorageTypeS3Serializer(ReplayStorageTypeBaseSerializer):
serializers.ModelSerializer): endpoint_help_text = '''
S3 format: http://s3.{REGION_NAME}.amazonaws.com
S3(China) format: http://s3.{REGION_NAME}.amazonaws.com.cn
Such as: http://s3.cn-north-1.amazonaws.com.cn
'''
ENDPOINT = serializers.CharField(
required=True, max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text),
allow_null=True,
)
meta = ReplayStorageMetaDictField()
type_fields_map = const.REPLAY_STORAGE_TYPE_FIELDS_MAP class ReplayStorageTypeCephSerializer(ReplayStorageTypeBaseSerializer):
pass
class ReplayStorageTypeSwiftSerializer(ReplayStorageTypeBaseSerializer):
class ProtocolChoices(TextChoices):
http = 'http', 'http'
https = 'https', 'https'
REGION = serializers.CharField(
required=True, max_length=1024, label=_('Region'), allow_null=True
)
PROTOCOL = serializers.ChoiceField(
choices=ProtocolChoices.choices, default=ProtocolChoices.http.value, label=_('Protocol'),
allow_null=True,
)
class ReplayStorageTypeOSSSerializer(ReplayStorageTypeBaseSerializer):
endpoint_help_text = '''
OSS format: http://{REGION_NAME}.aliyuncs.com
Such as: http://oss-cn-hangzhou.aliyuncs.com
'''
ENDPOINT = serializers.CharField(
max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text), allow_null=True,
)
class ReplayStorageTypeAzureSerializer(serializers.Serializer):
class EndpointSuffixChoices(TextChoices):
china = 'core.chinacloudapi.cn', 'core.chinacloudapi.cn'
international = 'core.windows.net', 'core.windows.net'
CONTAINER_NAME = serializers.CharField(max_length=1024, label=_('Container'), allow_null=True)
ACCOUNT_NAME = serializers.CharField(max_length=1024, label=_('Account name'), allow_null=True)
ACCOUNT_KEY = serializers.CharField(max_length=1024, label=_('Account key'), allow_null=True)
ENDPOINT_SUFFIX = serializers.ChoiceField(
choices=EndpointSuffixChoices.choices, default=EndpointSuffixChoices.china.value,
label=_('Endpoint suffix'), allow_null=True,
)
# mapping
replay_storage_type_serializer_classes_mapping = {
const.ReplayStorageTypeChoices.s3.value: ReplayStorageTypeS3Serializer,
const.ReplayStorageTypeChoices.ceph.value: ReplayStorageTypeCephSerializer,
const.ReplayStorageTypeChoices.swift.value: ReplayStorageTypeSwiftSerializer,
const.ReplayStorageTypeChoices.oss.value: ReplayStorageTypeOSSSerializer,
const.ReplayStorageTypeChoices.azure.value: ReplayStorageTypeAzureSerializer
}
# ReplayStorageSerializer
class ReplayStorageSerializer(serializers.ModelSerializer):
meta = MethodSerializer()
class Meta: class Meta:
model = ReplayStorage model = ReplayStorage
fields = ['id', 'name', 'type', 'meta', 'comment'] fields = ['id', 'name', 'type', 'meta', 'comment']
def validate_meta(self, meta):
_meta = self.instance.meta if self.instance else {}
_meta.update(meta)
return _meta
class CommandStorageMetaDictField(CustomMetaDictField): def get_meta_serializer(self):
type_fields_map = const.COMMAND_STORAGE_TYPE_FIELDS_MAP serializer_class = None
default_type = const.COMMAND_STORAGE_TYPE_SERVER query_type = self.context['request'].query_params.get('type')
convert_key_remove_type_prefix = True if query_type:
convert_key_to_upper = True serializer_class = replay_storage_type_serializer_classes_mapping.get(query_type)
if isinstance(self.instance, ReplayStorage):
instance_type = self.instance.type
serializer_class = replay_storage_type_serializer_classes_mapping.get(instance_type)
if serializer_class is None:
serializer_class = serializers.Serializer
serializer = serializer_class()
return serializer
class CommandStorageSerializer(BaseStorageSerializerMixin, # Command storage serializers
serializers.ModelSerializer): # ---------------------------
meta = CommandStorageMetaDictField()
type_fields_map = const.COMMAND_STORAGE_TYPE_FIELDS_MAP def es_host_format_validator(host):
h = urlparse(host)
default_error_msg = _('The address format is incorrect')
if h.scheme not in ['http', 'https']:
raise serializers.ValidationError(default_error_msg)
if ':' not in h.netloc:
raise serializers.ValidationError(default_error_msg)
_host, _port = h.netloc.split(':')
if not _host:
error_msg = _('Host invalid')
raise serializers.ValidationError(error_msg)
if not _port.isdigit():
error_msg = _('Port invalid')
raise serializers.ValidationError(error_msg)
return host
class CommandStorageTypeESSerializer(serializers.Serializer):
hosts_help_text = '''
Tip: If there are multiple hosts, use a comma (,) to separate them.
(eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com)
'''
HOSTS = serializers.ListField(
child=serializers.CharField(validators=[es_host_format_validator]), label=_('Hosts'),
help_text=_(hosts_help_text), allow_null=True
)
INDEX = serializers.CharField(
max_length=1024, default='jumpserver', label=_('Index'), allow_null=True
)
DOC_TYPE = serializers.CharField(
max_length=1024, read_only=True, default='command', label=_('Doc type'), allow_null=True
)
# mapping
command_storage_type_serializer_classes_mapping = {
const.CommandStorageTypeChoices.es.value: CommandStorageTypeESSerializer
}
# CommandStorageSerializer
class CommandStorageSerializer(serializers.ModelSerializer):
meta = MethodSerializer()
class Meta: class Meta:
model = CommandStorage model = CommandStorage
fields = ['id', 'name', 'type', 'meta', 'comment'] fields = ['id', 'name', 'type', 'meta', 'comment']
def validate_meta(self, meta):
_meta = self.instance.meta if self.instance else {}
_meta.update(meta)
return _meta
def get_meta_serializer(self):
serializer_class = None
query_type = self.context['request'].query_params.get('type')
if query_type:
serializer_class = command_storage_type_serializer_classes_mapping.get(query_type)
if isinstance(self.instance, CommandStorage):
instance_type = self.instance.type
serializer_class = command_storage_type_serializer_classes_mapping.get(instance_type)
if serializer_class is None:
serializer_class = serializers.Serializer
serializer = serializer_class()
return serializer

View File

@ -73,7 +73,6 @@ def clean_expired_session_period():
logger.info("Clean session replay done") logger.info("Clean session replay done")
@shared_task @shared_task
def upload_session_replay_to_external_storage(session_id): def upload_session_replay_to_external_storage(session_id):
logger.info(f'Start upload session to external storage: {session_id}') logger.info(f'Start upload session to external storage: {session_id}')

View File

@ -39,7 +39,7 @@ def download_session_replay(session):
configs = { configs = {
storage.name: storage.config storage.name: storage.config
for storage in replay_storages for storage in replay_storages
if not storage.in_defaults() if not storage.type_null_or_server
} }
if settings.SERVER_REPLAY_STORAGE: if settings.SERVER_REPLAY_STORAGE:
configs['SERVER_REPLAY_STORAGE'] = settings.SERVER_REPLAY_STORAGE configs['SERVER_REPLAY_STORAGE'] = settings.SERVER_REPLAY_STORAGE

View File

@ -19,7 +19,7 @@ __all__ = ['TicketViewSet']
class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
serializer_class = serializers.TicketSerializer serializer_class = serializers.TicketDisplaySerializer
serializer_classes = { serializer_classes = {
'default': serializers.TicketDisplaySerializer, 'default': serializers.TicketDisplaySerializer,
'display': serializers.TicketDisplaySerializer, 'display': serializers.TicketDisplaySerializer,

View File

@ -10,9 +10,9 @@ from tickets.utils import convert_model_instance_data_field_name_to_verbose_name
class ConstructDisplayFieldMixin: class ConstructDisplayFieldMixin:
def construct_meta_apply_application_open_fields_display(self): def construct_meta_apply_application_open_fields_display(self):
meta_display_fields = ['apply_category_display', 'apply_type_display'] meta_display_fields = ['apply_category_display', 'apply_type_display']
apply_category = self.meta['apply_category'] apply_category = self.meta.get('apply_category')
apply_category_display = ApplicationCategoryChoices.get_label(apply_category) apply_category_display = ApplicationCategoryChoices.get_label(apply_category)
apply_type = self.meta['apply_type'] apply_type = self.meta.get('apply_type')
apply_type_display = ApplicationTypeChoices.get_label(apply_type) apply_type_display = ApplicationTypeChoices.get_label(apply_type)
meta_display_values = [apply_category_display, apply_type_display] meta_display_values = [apply_category_display, apply_type_display]
meta_display = dict(zip(meta_display_fields, meta_display_values)) meta_display = dict(zip(meta_display_fields, meta_display_values))
@ -20,8 +20,8 @@ class ConstructDisplayFieldMixin:
def construct_meta_apply_application_approve_fields_display(self): def construct_meta_apply_application_approve_fields_display(self):
meta_display_fields = ['approve_applications_snapshot', 'approve_system_users_snapshot'] meta_display_fields = ['approve_applications_snapshot', 'approve_system_users_snapshot']
approve_applications_id = self.meta['approve_applications'] approve_applications_id = self.meta.get('approve_applications', [])
approve_system_users_id = self.meta['approve_system_users'] approve_system_users_id = self.meta.get('approve_system_users', [])
with tmp_to_org(self.org_id): with tmp_to_org(self.org_id):
approve_applications_snapshot = list( approve_applications_snapshot = list(
Application.objects.filter(id__in=approve_applications_id).values( Application.objects.filter(id__in=approve_applications_id).values(
@ -42,12 +42,12 @@ class ConstructDisplayFieldMixin:
class ConstructBodyMixin: class ConstructBodyMixin:
def construct_apply_application_applied_body(self): def construct_apply_application_applied_body(self):
apply_category_display = self.meta['apply_category_display'] apply_category_display = self.meta.get('apply_category_display')
apply_type_display = self.meta['apply_type_display'] apply_type_display = self.meta.get('apply_type_display')
apply_application_group = self.meta['apply_application_group'] apply_application_group = self.meta.get('apply_application_group', [])
apply_system_user_group = self.meta['apply_system_user_group'] apply_system_user_group = self.meta.get('apply_system_user_group', [])
apply_date_start = self.meta['apply_date_start'] apply_date_start = self.meta.get('apply_date_start')
apply_date_expired = self.meta['apply_date_expired'] apply_date_expired = self.meta.get('apply_date_expired')
applied_body = '''{}: {}, applied_body = '''{}: {},
{}: {}, {}: {},
{}: {}, {}: {},
@ -66,16 +66,16 @@ class ConstructBodyMixin:
def construct_apply_application_approved_body(self): def construct_apply_application_approved_body(self):
# 审批信息 # 审批信息
approve_applications_snapshot = self.meta['approve_applications_snapshot'] approve_applications_snapshot = self.meta.get('approve_applications_snapshot', [])
approve_applications_snapshot_display = convert_model_instance_data_field_name_to_verbose_name( approve_applications_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
Application, approve_applications_snapshot Application, approve_applications_snapshot
) )
approve_system_users_snapshot = self.meta['approve_system_users_snapshot'] approve_system_users_snapshot = self.meta.get('approve_system_users_snapshot', [])
approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name( approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
SystemUser, approve_system_users_snapshot SystemUser, approve_system_users_snapshot
) )
approve_date_start = self.meta['approve_date_start'] approve_date_start = self.meta.get('approve_date_start')
approve_date_expired = self.meta['approve_date_expired'] approve_date_expired = self.meta.get('approve_date_expired')
approved_body = '''{}: {}, approved_body = '''{}: {},
{}: {}, {}: {},
{}: {}, {}: {},
@ -97,12 +97,12 @@ class CreatePermissionMixin:
if application_permission: if application_permission:
return application_permission return application_permission
apply_category = self.meta['apply_category'] apply_category = self.meta.get('apply_category')
apply_type = self.meta['apply_type'] apply_type = self.meta.get('apply_type')
approved_applications_id = self.meta['approve_applications'] approved_applications_id = self.meta.get('approve_applications', [])
approve_system_users_id = self.meta['approve_system_users'] approve_system_users_id = self.meta.get('approve_system_users', [])
approve_date_start = self.meta['approve_date_start'] approve_date_start = self.meta.get('approve_date_start')
approve_date_expired = self.meta['approve_date_expired'] approve_date_expired = self.meta.get('approve_date_expired')
permission_name = '{}({})'.format( permission_name = '{}({})'.format(
__('Created by ticket ({})'.format(self.title)), str(self.id)[:4] __('Created by ticket ({})'.format(self.title)), str(self.id)[:4]
) )

View File

@ -10,7 +10,7 @@ class ConstructDisplayFieldMixin:
def construct_meta_apply_asset_open_fields_display(self): def construct_meta_apply_asset_open_fields_display(self):
meta_display_fields = ['apply_actions_display'] meta_display_fields = ['apply_actions_display']
apply_actions = self.meta['apply_actions'] apply_actions = self.meta.get('apply_actions', Action.NONE)
apply_actions_display = Action.value_to_choices_display(apply_actions) apply_actions_display = Action.value_to_choices_display(apply_actions)
meta_display_values = [apply_actions_display] meta_display_values = [apply_actions_display]
@ -21,10 +21,10 @@ class ConstructDisplayFieldMixin:
meta_display_fields = [ meta_display_fields = [
'approve_actions_display', 'approve_assets_snapshot', 'approve_system_users_snapshot' 'approve_actions_display', 'approve_assets_snapshot', 'approve_system_users_snapshot'
] ]
approve_actions = self.meta['approve_actions'] approve_actions = self.meta.get('approve_actions', Action.NONE)
approve_assets_id = self.meta['approve_assets']
approve_system_users_id = self.meta['approve_system_users']
approve_actions_display = Action.value_to_choices_display(approve_actions) approve_actions_display = Action.value_to_choices_display(approve_actions)
approve_assets_id = self.meta.get('approve_assets', [])
approve_system_users_id = self.meta.get('approve_system_users', [])
with tmp_to_org(self.org_id): with tmp_to_org(self.org_id):
approve_assets_snapshot = list( approve_assets_snapshot = list(
Asset.objects.filter(id__in=approve_assets_id).values( Asset.objects.filter(id__in=approve_assets_id).values(
@ -46,12 +46,12 @@ class ConstructDisplayFieldMixin:
class ConstructBodyMixin: class ConstructBodyMixin:
def construct_apply_asset_applied_body(self): def construct_apply_asset_applied_body(self):
apply_ip_group = self.meta['apply_ip_group'] apply_ip_group = self.meta.get('apply_ip_group', [])
apply_hostname_group = self.meta['apply_hostname_group'] apply_hostname_group = self.meta.get('apply_hostname_group', [])
apply_system_user_group = self.meta['apply_system_user_group'] apply_system_user_group = self.meta.get('apply_system_user_group', [])
apply_actions_display = self.meta['apply_actions_display'] apply_actions_display = self.meta.get('apply_actions_display', [])
apply_date_start = self.meta['apply_date_start'] apply_date_start = self.meta.get('apply_date_start')
apply_date_expired = self.meta['apply_date_expired'] apply_date_expired = self.meta.get('apply_date_expired')
applied_body = '''{}: {}, applied_body = '''{}: {},
{}: {}, {}: {},
{}: {}, {}: {},
@ -68,17 +68,17 @@ class ConstructBodyMixin:
return applied_body return applied_body
def construct_apply_asset_approved_body(self): def construct_apply_asset_approved_body(self):
approve_assets_snapshot = self.meta['approve_assets_snapshot'] approve_assets_snapshot = self.meta.get('approve_assets_snapshot', [])
approve_assets_snapshot_display = convert_model_instance_data_field_name_to_verbose_name( approve_assets_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
Asset, approve_assets_snapshot Asset, approve_assets_snapshot
) )
approve_system_users_snapshot = self.meta['approve_system_users_snapshot'] approve_system_users_snapshot = self.meta.get('approve_system_users_snapshot', [])
approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name( approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
SystemUser, approve_system_users_snapshot SystemUser, approve_system_users_snapshot
) )
approve_actions_display = self.meta['approve_actions_display'] approve_actions_display = self.meta.get('approve_actions_display', [])
approve_date_start = self.meta['approve_date_start'] approve_date_start = self.meta.get('approve_date_start')
approve_date_expired = self.meta['approve_date_expired'] approve_date_expired = self.meta.get('approve_date_expired')
approved_body = '''{}: {}, approved_body = '''{}: {},
{}: {}, {}: {},
{}: {}, {}: {},
@ -101,11 +101,11 @@ class CreatePermissionMixin:
if asset_permission: if asset_permission:
return asset_permission return asset_permission
approve_assets_id = self.meta['approve_assets'] approve_assets_id = self.meta.get('approve_assets', [])
approve_system_users_id = self.meta['approve_system_users'] approve_system_users_id = self.meta.get('approve_system_users', [])
approve_actions = self.meta['approve_actions'] approve_actions = self.meta.get('approve_actions', Action.NONE)
approve_date_start = self.meta['approve_date_start'] approve_date_start = self.meta.get('approve_date_start')
approve_date_expired = self.meta['approve_date_expired'] approve_date_expired = self.meta.get('approve_date_expired')
permission_name = '{}({})'.format( permission_name = '{}({})'.format(
__('Created by ticket ({})'.format(self.title)), str(self.id)[:4] __('Created by ticket ({})'.format(self.title)), str(self.id)[:4]
) )

View File

@ -4,9 +4,9 @@ from django.utils.translation import ugettext as __
class ConstructBodyMixin: class ConstructBodyMixin:
def construct_login_confirm_applied_body(self): def construct_login_confirm_applied_body(self):
apply_login_ip = self.meta['apply_login_ip'] apply_login_ip = self.meta.get('apply_login_ip')
apply_login_city = self.meta['apply_login_city'] apply_login_city = self.meta.get('apply_login_city')
apply_login_datetime = self.meta['apply_login_datetime'] apply_login_datetime = self.meta.get('apply_login_datetime')
applied_body = '''{}: {}, applied_body = '''{}: {},
{}: {}, {}: {},
{}: {} {}: {}

View File

@ -17,14 +17,17 @@ action_approve = const.TicketActionChoices.approve.value
type_serializer_classes_mapping = { type_serializer_classes_mapping = {
const.TicketTypeChoices.apply_asset.value: { const.TicketTypeChoices.apply_asset.value: {
'default': apply_asset.ApplyAssetSerializer,
action_open: apply_asset.ApplySerializer, action_open: apply_asset.ApplySerializer,
action_approve: apply_asset.ApproveSerializer, action_approve: apply_asset.ApproveSerializer,
}, },
const.TicketTypeChoices.apply_application.value: { const.TicketTypeChoices.apply_application.value: {
'default': apply_application.ApplyApplicationSerializer,
action_open: apply_application.ApplySerializer, action_open: apply_application.ApplySerializer,
action_approve: apply_application.ApproveSerializer, action_approve: apply_application.ApproveSerializer,
}, },
const.TicketTypeChoices.login_confirm.value: { const.TicketTypeChoices.login_confirm.value: {
'default': login_confirm.LoginConfirmSerializer,
action_open: login_confirm.ApplySerializer, action_open: login_confirm.ApplySerializer,
} }
} }

View File

@ -1,88 +1,148 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
from applications.models import Application from applications.models import Application
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
from assets.models import SystemUser from assets.models import SystemUser
from .mixin import BaseApproveSerializerMixin from orgs.utils import tmp_to_org
from tickets.models import Ticket
__all__ = [ __all__ = [
'ApplyApplicationTypeSerializer', 'ApplySerializer', 'ApproveSerializer', 'ApplyApplicationSerializer', 'ApplySerializer', 'ApproveSerializer',
] ]
class ApplySerializer(serializers.Serializer): class ApplySerializer(serializers.Serializer):
# 申请信息 # 申请信息
apply_category = serializers.ChoiceField( apply_category = serializers.ChoiceField(
required=True, choices=ApplicationCategoryChoices.choices, label=_('Category') required=True, choices=ApplicationCategoryChoices.choices, label=_('Category'),
allow_null=True,
) )
apply_category_display = serializers.CharField( apply_category_display = serializers.CharField(
read_only=True, label=_('Category display') read_only=True, label=_('Category display'), allow_null=True,
) )
apply_type = serializers.ChoiceField( apply_type = serializers.ChoiceField(
required=True, choices=ApplicationTypeChoices.choices, label=_('Type') required=True, choices=ApplicationTypeChoices.choices, label=_('Type'),
allow_null=True
) )
apply_type_display = serializers.CharField( apply_type_display = serializers.CharField(
required=False, read_only=True, label=_('Type display') required=False, read_only=True, label=_('Type display'),
allow_null=True
) )
apply_application_group = serializers.ListField( apply_application_group = serializers.ListField(
required=False, child=serializers.CharField(), label=_('Application group'), required=False, child=serializers.CharField(), label=_('Application group'),
default=list, default=list, allow_null=True
) )
apply_system_user_group = serializers.ListField( apply_system_user_group = serializers.ListField(
required=False, child=serializers.CharField(), label=_('System user group'), required=False, child=serializers.CharField(), label=_('System user group'),
default=list, default=list, allow_null=True
) )
apply_date_start = serializers.DateTimeField( apply_date_start = serializers.DateTimeField(
required=True, label=_('Date start') required=True, label=_('Date start'), allow_null=True
) )
apply_date_expired = serializers.DateTimeField( apply_date_expired = serializers.DateTimeField(
required=True, label=_('Date expired') required=True, label=_('Date expired'), allow_null=True
) )
class ApproveSerializer(BaseApproveSerializerMixin, serializers.Serializer): class ApproveSerializer(serializers.Serializer):
# 审批信息 # 审批信息
approve_applications = serializers.ListField( approve_applications = serializers.ListField(
required=True, child=serializers.UUIDField(), label=_('Approve applications') required=True, child=serializers.UUIDField(), label=_('Approve applications'),
allow_null=True
) )
approve_applications_snapshot = serializers.ListField( approve_applications_snapshot = serializers.ListField(
required=False, read_only=True, child=serializers.CharField(), required=False, read_only=True, child=serializers.CharField(),
label=_('Approve applications display'), label=_('Approve applications display'), allow_null=True,
default=list default=list
) )
approve_system_users = serializers.ListField( approve_system_users = serializers.ListField(
required=True, child=serializers.UUIDField(), label=_('Approve system users') required=True, child=serializers.UUIDField(), label=_('Approve system users'),
allow_null=True
) )
approve_system_users_snapshot = serializers.ListField( approve_system_users_snapshot = serializers.ListField(
required=False, read_only=True, child=serializers.CharField(), required=False, read_only=True, child=serializers.CharField(),
label=_('Approve system user display'), label=_('Approve system user display'), allow_null=True,
default=list default=list
) )
approve_date_start = serializers.DateTimeField( approve_date_start = serializers.DateTimeField(
required=True, label=_('Date start') required=True, label=_('Date start'), allow_null=True
) )
approve_date_expired = serializers.DateTimeField( approve_date_expired = serializers.DateTimeField(
required=True, label=_('Date expired') required=True, label=_('Date expired'), allow_null=True
) )
def validate_approve_applications(self, approve_applications): def validate_approve_applications(self, approve_applications):
application_type = self.root.instance.meta['apply_type'] if not isinstance(self.root.instance, Ticket):
queries = {'type': application_type} return []
applications_id = self.filter_approve_resources(
resource_model=Application, resources_id=approve_applications, queries=queries with tmp_to_org(self.root.instance.org_id):
) apply_type = self.root.instance.meta.get('apply_type')
return applications_id queries = Q(type=apply_type)
queries &= Q(id__in=approve_applications)
applications_id = Application.objects.filter(queries).values_list('id', flat=True)
applications_id = [str(application_id) for application_id in applications_id]
if applications_id:
return applications_id
raise serializers.ValidationError(_(
'No `Application` are found under Organization `{}`'.format(self.root.instance.org_name)
))
def validate_approve_system_users(self, approve_system_users): def validate_approve_system_users(self, approve_system_users):
application_type = self.root.instance.meta['apply_type'] if not isinstance(self.root.instance, Ticket):
protocol = SystemUser.get_protocol_by_application_type(application_type) return []
queries = {'protocol': protocol}
system_users_id = self.filter_approve_system_users(approve_system_users, queries) with tmp_to_org(self.root.instance.org_id):
return system_users_id apply_type = self.root.instance.meta.get('apply_type')
protocol = SystemUser.get_protocol_by_application_type(apply_type)
queries = Q(protocol=protocol)
queries &= Q(id__in=approve_system_users)
system_users_id = SystemUser.objects.filter(queries).values_list('id', flat=True)
system_users_id = [str(system_user_id) for system_user_id in system_users_id]
if system_users_id:
return system_users_id
raise serializers.ValidationError(_(
'No `SystemUser` are found under Organization `{}`'.format(self.root.instance.org_name)
))
class ApplyApplicationTypeSerializer(ApplySerializer, ApproveSerializer): class ApplyApplicationSerializer(ApplySerializer, ApproveSerializer):
pass # 推荐信息
recommend_applications = serializers.SerializerMethodField()
recommend_system_users = serializers.SerializerMethodField()
def get_recommend_applications(self, value):
if not isinstance(self.root.instance, Ticket):
return []
apply_application_group = value.get('apply_application_group', [])
apply_type = value.get('apply_type')
queries = Q()
for application in apply_application_group:
queries |= Q(name__icontains=application)
queries &= Q(type=apply_type)
with tmp_to_org(self.root.instance.org_id):
applications_id = Application.objects.filter(queries).values_list('id', flat=True)[:5]
applications_id = [str(application_id) for application_id in applications_id]
return applications_id
def get_recommend_system_users(self, value):
if not isinstance(self.root.instance, Ticket):
return []
apply_type = value.get('apply_type')
apply_system_user_group = value.get('apply_system_user_group', [])
protocol = SystemUser.get_protocol_by_application_type(apply_type)
queries = Q()
for system_user in apply_system_user_group:
queries |= Q(username__icontains=system_user)
queries |= Q(name__icontains=system_user)
queries &= Q(protocol=protocol)
with tmp_to_org(self.root.instance.org_id):
system_users_id = SystemUser.objects.filter(queries).values_list('id', flat=True)[:5]
system_users_id = [str(system_user_id) for system_user_id in system_users_id]
return system_users_id

View File

@ -1,8 +1,10 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
from rest_framework import serializers from rest_framework import serializers
from perms.serializers import ActionsField from perms.serializers import ActionsField
from assets.models import Asset, SystemUser from assets.models import Asset, SystemUser
from .mixin import BaseApproveSerializerMixin from orgs.utils import tmp_to_org
from tickets.models import Ticket
__all__ = [ __all__ = [
@ -14,74 +16,129 @@ class ApplySerializer(serializers.Serializer):
# 申请信息 # 申请信息
apply_ip_group = serializers.ListField( apply_ip_group = serializers.ListField(
required=False, child=serializers.IPAddressField(), label=_('IP group'), required=False, child=serializers.IPAddressField(), label=_('IP group'),
default=list, default=list, allow_null=True,
) )
apply_hostname_group = serializers.ListField( apply_hostname_group = serializers.ListField(
required=False, child=serializers.CharField(), label=_('Hostname group'), required=False, child=serializers.CharField(), label=_('Hostname group'),
default=list, default=list, allow_null=True,
) )
apply_system_user_group = serializers.ListField( apply_system_user_group = serializers.ListField(
required=False, child=serializers.CharField(), label=_('System user group'), required=False, child=serializers.CharField(), label=_('System user group'),
default=list, default=list, allow_null=True
) )
apply_actions = ActionsField( apply_actions = ActionsField(
required=True required=True, allow_null=True
) )
apply_actions_display = serializers.ListField( apply_actions_display = serializers.ListField(
required=False, read_only=True, child=serializers.CharField(), required=False, read_only=True, child=serializers.CharField(),
label=_('Approve assets display'), label=_('Approve assets display'), allow_null=True,
default=list, default=list,
) )
apply_date_start = serializers.DateTimeField( apply_date_start = serializers.DateTimeField(
required=True, label=_('Date start') required=True, label=_('Date start'), allow_null=True,
) )
apply_date_expired = serializers.DateTimeField( apply_date_expired = serializers.DateTimeField(
required=True, label=_('Date expired') required=True, label=_('Date expired'), allow_null=True,
) )
class ApproveSerializer(BaseApproveSerializerMixin, serializers.Serializer): class ApproveSerializer(serializers.Serializer):
# 审批信息 # 审批信息
approve_assets = serializers.ListField( approve_assets = serializers.ListField(
required=True, child=serializers.UUIDField(), label=_('Approve assets') required=True, allow_null=True, child=serializers.UUIDField(), label=_('Approve assets')
) )
approve_assets_snapshot = serializers.ListField( approve_assets_snapshot = serializers.ListField(
required=False, read_only=True, child=serializers.DictField(), required=False, read_only=True, child=serializers.DictField(),
label=_('Approve assets display'), label=_('Approve assets display'), allow_null=True,
default=list, default=list,
) )
approve_system_users = serializers.ListField( approve_system_users = serializers.ListField(
required=True, child=serializers.UUIDField(), label=_('Approve system users') required=True, allow_null=True, child=serializers.UUIDField(),
label=_('Approve system users')
) )
approve_system_users_snapshot = serializers.ListField( approve_system_users_snapshot = serializers.ListField(
required=False, read_only=True, child=serializers.DictField(), required=False, read_only=True, child=serializers.DictField(),
label=_('Approve assets display'), label=_('Approve assets display'), allow_null=True,
default=list, default=list,
) )
approve_actions = ActionsField( approve_actions = ActionsField(
required=True required=True, allow_null=True,
) )
approve_actions_display = serializers.ListField( approve_actions_display = serializers.ListField(
required=False, read_only=True, child=serializers.CharField(), required=False, read_only=True, child=serializers.CharField(),
label=_('Approve assets display'), label=_('Approve assets display'), allow_null=True,
default=list, default=list,
) )
approve_date_start = serializers.DateTimeField( approve_date_start = serializers.DateTimeField(
required=True, label=_('Date start') required=True, label=_('Date start'), allow_null=True,
) )
approve_date_expired = serializers.DateTimeField( approve_date_expired = serializers.DateTimeField(
required=True, label=_('Date expired') required=True, label=_('Date expired'), allow_null=True
) )
def validate_approve_assets(self, approve_assets): def validate_approve_assets(self, approve_assets):
assets_id = self.filter_approve_resources(resource_model=Asset, resources_id=approve_assets) if not isinstance(self.root.instance, Ticket):
return assets_id return []
with tmp_to_org(self.root.instance.org_id):
assets_id = Asset.objects.filter(id__in=approve_assets).values_list('id', flat=True)
assets_id = [str(asset_id) for asset_id in assets_id]
if assets_id:
return assets_id
raise serializers.ValidationError(_(
'No `Asset` are found under Organization `{}`'.format(self.root.instance.org_name)
))
def validate_approve_system_users(self, approve_system_users): def validate_approve_system_users(self, approve_system_users):
queries = {'protocol__in': SystemUser.ASSET_CATEGORY_PROTOCOLS} if not isinstance(self.root.instance, Ticket):
system_users_id = self.filter_approve_system_users(approve_system_users, queries) return []
return system_users_id
with tmp_to_org(self.root.instance.org_id):
queries = Q(protocol__in=SystemUser.ASSET_CATEGORY_PROTOCOLS)
queries &= Q(id__in=approve_system_users)
system_users_id = SystemUser.objects.filter(queries).values_list('id', flat=True)
system_users_id = [str(system_user_id) for system_user_id in system_users_id]
if system_users_id:
return system_users_id
raise serializers.ValidationError(_(
'No `Asset` are found under Organization `{}`'.format(self.root.instance.org_name)
))
class ApplyAssetSerializer(ApplySerializer, ApproveSerializer): class ApplyAssetSerializer(ApplySerializer, ApproveSerializer):
pass # 推荐信息
recommend_assets = serializers.SerializerMethodField()
recommend_system_users = serializers.SerializerMethodField()
def get_recommend_assets(self, value):
if not isinstance(self.root.instance, Ticket):
return []
apply_ip_group = value.get('apply_ip_group', [])
apply_hostname_group = value.get('apply_hostname_group', [])
queries = Q(ip__in=apply_ip_group)
for hostname in apply_hostname_group:
queries |= Q(hostname__icontains=hostname)
with tmp_to_org(self.root.instance.org_id):
assets_id = Asset.objects.filter(queries).values_list('id', flat=True)[:5]
assets_id = [str(asset_id) for asset_id in assets_id]
return assets_id
def get_recommend_system_users(self, value):
if not isinstance(self.root.instance, Ticket):
return []
apply_system_user_group = value.get('apply_system_user_group', [])
queries = Q()
for system_user in apply_system_user_group:
queries |= Q(username__icontains=system_user)
queries |= Q(name__icontains=system_user)
queries &= Q(protocol__in=SystemUser.ASSET_CATEGORY_PROTOCOLS)
with tmp_to_org(self.root.instance.org_id):
system_users_id = SystemUser.objects.filter(queries).values_list('id', flat=True)[:5]
system_users_id = [str(system_user_id) for system_user_id in system_users_id]
return system_users_id

View File

@ -4,19 +4,22 @@ from django.utils.translation import ugettext_lazy as _
__all__ = [ __all__ = [
'ApplySerializer', 'ApplySerializer', 'LoginConfirmSerializer',
] ]
class ApplySerializer(serializers.Serializer): class ApplySerializer(serializers.Serializer):
# 申请信息 # 申请信息
apply_login_ip = serializers.IPAddressField( apply_login_ip = serializers.IPAddressField(
required=True, label=_('Login ip') required=True, label=_('Login ip'), allow_null=True
) )
apply_login_city = serializers.CharField( apply_login_city = serializers.CharField(
required=True, max_length=64, label=_('Login city') required=True, max_length=64, label=_('Login city'), allow_null=True
) )
apply_login_datetime = serializers.DateTimeField( apply_login_datetime = serializers.DateTimeField(
required=True, label=_('Login datetime') required=True, label=_('Login datetime'), allow_null=True
) )
class LoginConfirmSerializer(ApplySerializer):
pass

View File

@ -1,39 +0,0 @@
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

View File

@ -22,6 +22,9 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type')) type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status')) status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status'))
action = ReadableHiddenField(default=const.TicketActionChoices.open.value)
applicant = ReadableHiddenField(default=serializers.CurrentUserDefault())
processor = ReadableHiddenField(default=serializers.CurrentUserDefault())
meta = MethodSerializer() meta = MethodSerializer()
class Meta: class Meta:
@ -38,48 +41,55 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
def get_meta_serializer(self): def get_meta_serializer(self):
request = self.context['request'] request = self.context['request']
view = self.context['view'] default_serializer_class = serializers.Serializer
query_type = request.query_params.get('type') if isinstance(self.instance, Ticket):
query_action = request.query_params.get('action') _type = self.instance.type
view_action = view.action
action = query_action if query_action else view_action
if query_type:
serializer_class = type_serializer_classes_mapping.get(query_type, {}).get(action)
else: else:
serializer_class = None _type = request.query_params.get('type')
if serializer_class is None:
serializer_class = serializers.Serializer if not _type:
serializer = serializer_class() return default_serializer_class()
return serializer
action_serializer_classes_mapping = type_serializer_classes_mapping.get(_type)
if not action_serializer_classes_mapping:
return default_serializer_class()
query_action = request.query_params.get('action')
_action = query_action if query_action else self.context['view'].action
serializer_class = action_serializer_classes_mapping.get(_action)
if serializer_class:
return serializer_class()
serializer_class = action_serializer_classes_mapping.get('default')
if serializer_class:
return serializer_class()
return default_serializer_class()
class TicketDisplaySerializer(TicketSerializer): class TicketDisplaySerializer(TicketSerializer):
class Meta(TicketSerializer.Meta): class Meta:
model = Ticket
fields = TicketSerializer.Meta.fields
read_only_fields = TicketSerializer.Meta.fields read_only_fields = TicketSerializer.Meta.fields
class TicketActionSerializer(TicketSerializer): class TicketApplySerializer(TicketSerializer):
action = ReadableHiddenField(default=const.TicketActionChoices.open.value)
class Meta(TicketSerializer.Meta):
required_fields = ['action']
read_only_fields = list(set(TicketDisplaySerializer.Meta.fields) - set(required_fields))
class TicketApplySerializer(TicketActionSerializer):
applicant = ReadableHiddenField(default=serializers.CurrentUserDefault())
org_id = serializers.CharField( org_id = serializers.CharField(
max_length=36, allow_blank=True, required=True, label=_("Organization") max_length=36, allow_blank=True, required=True, label=_("Organization")
) )
class Meta(TicketActionSerializer.Meta): class Meta:
required_fields = TicketActionSerializer.Meta.required_fields + [ model = Ticket
'id', 'title', 'type', 'applicant', 'meta', 'assignees', 'comment', 'org_id' fields = TicketSerializer.Meta.fields
required_fields = [
'id', 'title', 'type', 'applicant', 'action', 'meta', 'assignees', 'comment', 'org_id'
] ]
read_only_fields = list(set(TicketDisplaySerializer.Meta.fields) - set(required_fields)) read_only_fields = list(set(fields) - set(required_fields))
extra_kwargs = { extra_kwargs = {
'type': {'required': True}, 'type': {'required': True},
'org_id': {'required': True},
} }
def validate_type(self, tp): def validate_type(self, tp):
@ -115,37 +125,43 @@ class TicketApplySerializer(TicketActionSerializer):
return const.TicketActionChoices.open.value return const.TicketActionChoices.open.value
class TicketProcessSerializer(TicketActionSerializer): class TicketApproveSerializer(TicketSerializer):
processor = ReadableHiddenField(default=serializers.CurrentUserDefault())
class Meta(TicketActionSerializer.Meta): class Meta:
required_fields = TicketActionSerializer.Meta.required_fields + ['processor'] model = Ticket
read_only_fields = list(set(TicketDisplaySerializer.Meta.fields) - set(required_fields)) fields = TicketSerializer.Meta.fields
required_fields = ['processor', 'action', 'meta']
read_only_fields = list(set(fields) - set(required_fields))
class TicketApproveSerializer(TicketProcessSerializer):
class Meta(TicketProcessSerializer.Meta):
required_fields = TicketProcessSerializer.Meta.required_fields + ['meta']
read_only_fields = list(set(TicketDisplaySerializer.Meta.fields) - set(required_fields))
def validate_meta(self, meta): def validate_meta(self, meta):
instance_meta = self.instance.meta _meta = self.instance.meta if self.instance else {}
instance_meta.update(meta) _meta.update(meta)
return instance_meta return _meta
@staticmethod @staticmethod
def validate_action(action): def validate_action(action):
return const.TicketActionChoices.approve.value return const.TicketActionChoices.approve.value
class TicketRejectSerializer(TicketProcessSerializer): class TicketRejectSerializer(TicketSerializer):
meta = MethodSerializer(read_only=True)
class Meta:
model = Ticket
fields = TicketSerializer.Meta.fields
read_only_fields = fields
def validate_action(self, action): def validate_action(self, action):
return const.TicketActionChoices.reject.value return const.TicketActionChoices.reject.value
class TicketCloseSerializer(TicketProcessSerializer): class TicketCloseSerializer(TicketSerializer):
meta = MethodSerializer(read_only=True)
class Meta:
model = Ticket
fields = TicketSerializer.Meta.fields
read_only_fields = fields
def validate_action(self, action): def validate_action(self, action):
return const.TicketActionChoices.close.value return const.TicketActionChoices.close.value

View File

@ -17,26 +17,27 @@ logger = get_logger(__name__)
@receiver(pre_save, sender=Ticket) @receiver(pre_save, sender=Ticket)
def on_ticket_pre_save(sender, instance=None, **kwargs): def on_ticket_pre_save(sender, instance=None, **kwargs):
instance.set_display_fields()
if instance.has_processed: if instance.has_processed:
instance.set_status_closed() instance.set_status_closed()
instance.set_display_fields()
@receiver(post_save, sender=Ticket) @receiver(post_save, sender=Ticket)
def on_ticket_post_save(sender, instance=None, **kwargs): def on_ticket_post_save(sender, instance=None, created=False, **kwargs):
instance.create_action_comment()
if instance.action_open: if created and instance.action_open:
instance.create_action_comment()
instance.create_applied_comment() instance.create_applied_comment()
return
if instance.action_approve: if not created and instance.has_processed:
instance.create_permission() instance.create_action_comment()
instance.create_approved_comment() msg = 'Ticket () has processed, send mail to applicant: {}'
logger.debug( logger.debug(msg.format(instance.title, instance.applicant_display))
'Ticket () has processed, send mail to applicant: {}'.format( send_ticket_processed_mail_to_applicant(instance)
instance.title, instance.applicant_display if instance.action_approve:
) instance.create_permission()
) instance.create_approved_comment()
send_ticket_processed_mail_to_applicant(instance)
@receiver(m2m_changed, sender=Ticket.assignees.through) @receiver(m2m_changed, sender=Ticket.assignees.through)