perf: 工单优化(审批人可以填写工单对应的授权规则名称) (#5468)

* perf: 工单优化(审批人可以填写工单对应的授权规则名称)

* perf: 工单优化(优化推荐的资产、应用、系统用户等逻辑)

* perf: 工单优化(优化工单邮件内容)

* perf: MethodSerializer优化(优化当Serializer不需要时, 默认可以不传递对应字段)

Co-authored-by: Bai <bugatti_it@163.com>
pull/5482/head
fit2bot 2021-01-19 15:44:19 +08:00 committed by GitHub
parent 0842553f8a
commit 9126c7780d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 214 additions and 103 deletions

View File

@ -18,22 +18,28 @@ class ApplicationSerializerMixin(serializers.Serializer):
attrs = MethodSerializer() attrs = MethodSerializer()
def get_attrs_serializer(self): def get_attrs_serializer(self):
serializer_class = None default_serializer = serializers.Serializer(read_only=True)
if isinstance(self.instance, models.Application): if isinstance(self.instance, models.Application):
instance_type = self.instance.type _type = self.instance.type
serializer_class = type_serializer_classes_mapping.get(instance_type) _category = self.instance.category
else: else:
request = self.context['request'] _type = self.context['request'].query_params.get('type')
query_type = request.query_params.get('type') _category = self.context['request'].query_params.get('category')
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 _type:
serializer_class = serializers.Serializer serializer_class = type_serializer_classes_mapping.get(_type)
serializer = serializer_class() elif _category:
serializer_class = category_serializer_classes_mapping.get(_category)
else:
serializer_class = default_serializer
if not serializer_class:
serializer_class = default_serializer
if isinstance(serializer_class, type):
serializer = serializer_class()
else:
serializer = serializer_class
return serializer return serializer

View File

@ -113,16 +113,25 @@ class ReplayStorageSerializer(serializers.ModelSerializer):
return _meta return _meta
def get_meta_serializer(self): def get_meta_serializer(self):
serializer_class = None default_serializer = serializers.Serializer(read_only=True)
query_type = self.context['request'].query_params.get('type')
if query_type:
serializer_class = replay_storage_type_serializer_classes_mapping.get(query_type)
if isinstance(self.instance, ReplayStorage): if isinstance(self.instance, ReplayStorage):
instance_type = self.instance.type _type = self.instance.type
serializer_class = replay_storage_type_serializer_classes_mapping.get(instance_type) else:
if serializer_class is None: _type = self.context['request'].query_params.get('type')
serializer_class = serializers.Serializer
serializer = serializer_class() if _type:
serializer_class = replay_storage_type_serializer_classes_mapping.get(_type)
else:
serializer_class = default_serializer
if not serializer_class:
serializer_class = default_serializer
if isinstance(serializer_class, type):
serializer = serializer_class()
else:
serializer = serializer_class
return serializer return serializer
@ -187,14 +196,23 @@ class CommandStorageSerializer(serializers.ModelSerializer):
return _meta return _meta
def get_meta_serializer(self): def get_meta_serializer(self):
serializer_class = None default_serializer = serializers.Serializer(read_only=True)
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): if isinstance(self.instance, CommandStorage):
instance_type = self.instance.type _type = self.instance.type
serializer_class = command_storage_type_serializer_classes_mapping.get(instance_type) else:
if serializer_class is None: _type = self.context['request'].query_params.get('type')
serializer_class = serializers.Serializer
serializer = serializer_class() if _type:
serializer_class = command_storage_type_serializer_classes_mapping.get(_type)
else:
serializer_class = default_serializer
if not serializer_class:
serializer_class = default_serializer
if isinstance(serializer_class, type):
serializer = serializer_class()
else:
serializer = serializer_class
return serializer return serializer

View File

@ -88,12 +88,13 @@ class Handler(BaseHandler):
apply_category = self.ticket.meta.get('apply_category') apply_category = self.ticket.meta.get('apply_category')
apply_type = self.ticket.meta.get('apply_type') apply_type = self.ticket.meta.get('apply_type')
approve_permission_name = self.ticket.meta.get('approve_permission_name', '')
approved_applications_id = self.ticket.meta.get('approve_applications', []) approved_applications_id = self.ticket.meta.get('approve_applications', [])
approve_system_users_id = self.ticket.meta.get('approve_system_users', []) approve_system_users_id = self.ticket.meta.get('approve_system_users', [])
approve_date_start = self.ticket.meta.get('approve_date_start') approve_date_start = self.ticket.meta.get('approve_date_start')
approve_date_expired = self.ticket.meta.get('approve_date_expired') approve_date_expired = self.ticket.meta.get('approve_date_expired')
permission_name = _('Created by ticket ({}) ({})'.format( permission_created_by = '{}:{}'.format(
self.ticket.title, str(self.ticket.id)[:4]) str(self.ticket.__class__.__name__), str(self.ticket.id)
) )
permission_comment = _( permission_comment = _(
'Created by the ticket, ' 'Created by the ticket, '
@ -101,18 +102,19 @@ class Handler(BaseHandler):
'ticket applicant: {}, ' 'ticket applicant: {}, '
'ticket processor: {}, ' 'ticket processor: {}, '
'ticket ID: {}' 'ticket ID: {}'
''.format( ).format(
self.ticket.title, self.ticket.applicant_display, self.ticket.title,
self.ticket.processor_display, str(self.ticket.id) self.ticket.applicant_display,
) self.ticket.processor_display,
str(self.ticket.id)
) )
permissions_data = { permissions_data = {
'id': self.ticket.id, 'id': self.ticket.id,
'name': str(permission_name), 'name': approve_permission_name,
'category': apply_category, 'category': apply_category,
'type': apply_type, 'type': apply_type,
'comment': str(permission_comment), 'comment': str(permission_comment),
'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.ticket.id)), 'created_by': permission_created_by,
'date_start': approve_date_start, 'date_start': approve_date_start,
'date_expired': approve_date_expired, 'date_expired': approve_date_expired,
} }

View File

@ -90,13 +90,14 @@ class Handler(BaseHandler):
if asset_permission: if asset_permission:
return asset_permission return asset_permission
approve_permission_name = self.ticket.meta.get('approve_permission_name', )
approve_assets_id = self.ticket.meta.get('approve_assets', []) approve_assets_id = self.ticket.meta.get('approve_assets', [])
approve_system_users_id = self.ticket.meta.get('approve_system_users', []) approve_system_users_id = self.ticket.meta.get('approve_system_users', [])
approve_actions = self.ticket.meta.get('approve_actions', Action.NONE) approve_actions = self.ticket.meta.get('approve_actions', Action.NONE)
approve_date_start = self.ticket.meta.get('approve_date_start') approve_date_start = self.ticket.meta.get('approve_date_start')
approve_date_expired = self.ticket.meta.get('approve_date_expired') approve_date_expired = self.ticket.meta.get('approve_date_expired')
permission_name = _('Created by ticket ({}) ({})'.format( permission_created_by = '{}:{}'.format(
self.ticket.title, str(self.ticket.id)[:4]) str(self.ticket.__class__.__name__), str(self.ticket.id)
) )
permission_comment = _( permission_comment = _(
'Created by the ticket, ' 'Created by the ticket, '
@ -104,18 +105,18 @@ class Handler(BaseHandler):
'ticket applicant: {}, ' 'ticket applicant: {}, '
'ticket processor: {}, ' 'ticket processor: {}, '
'ticket ID: {}' 'ticket ID: {}'
''.format( ).format(
self.ticket.title, self.ticket.title,
self.ticket.applicant_display, self.ticket.applicant_display,
self.ticket.processor_display, self.ticket.processor_display,
str(self.ticket.id) str(self.ticket.id)
)
) )
permission_data = { permission_data = {
'id': self.ticket.id, 'id': self.ticket.id,
'name': str(permission_name), 'name': approve_permission_name,
'comment': permission_comment, 'comment': str(permission_comment),
'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.ticket.id)), 'created_by': permission_created_by,
'actions': approve_actions, 'actions': approve_actions,
'date_start': approve_date_start, 'date_start': approve_date_start,
'date_expired': approve_date_expired, 'date_expired': approve_date_expired,

View File

@ -86,17 +86,17 @@ class BaseHandler(object):
{}: {}, {}: {},
{}: {}, {}: {},
{}: {}, {}: {},
{}: {},
{}: {} {}: {}
'''.format( '''.format(
_('Ticket title'), self.ticket.title, _('Ticket title'), self.ticket.title,
_('Ticket type'), self.ticket.get_type_display(), _('Ticket type'), self.ticket.get_type_display(),
_('Ticket status'), self.ticket.get_status_display(),
_('Ticket action'), self.ticket.get_action_display(),
_('Ticket applicant'), self.ticket.applicant_display, _('Ticket applicant'), self.ticket.applicant_display,
_('Ticket assignees'), self.ticket.assignees_display, _('Ticket assignees'), self.ticket.assignees_display,
_('Ticket processor'), self.ticket.processor_display or _('No'),
_('Ticket action'), self.ticket.get_action_display(),
_('Ticket status'), self.ticket.get_status_display()
) )
if self.ticket.status_closed:
basic_body += '''{}: {}'''.format(_('Ticket processor'), self.ticket.processor_display)
body = self.body_html_format.format(_("Ticket basic info"), basic_body) body = self.body_html_format.format(_("Ticket basic info"), basic_body)
return body return body

View File

@ -29,5 +29,6 @@ type_serializer_classes_mapping = {
const.TicketTypeChoices.login_confirm.value: { const.TicketTypeChoices.login_confirm.value: {
'default': login_confirm.LoginConfirmSerializer, 'default': login_confirm.LoginConfirmSerializer,
action_open: login_confirm.ApplySerializer, action_open: login_confirm.ApplySerializer,
action_approve: login_confirm.LoginConfirmSerializer(read_only=True),
} }
} }

View File

@ -1,11 +1,13 @@
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 django.db.models import Q
from perms.models import ApplicationPermission
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 orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from tickets.models import Ticket from tickets.models import Ticket
from .common import DefaultPermissionName
__all__ = [ __all__ = [
'ApplyApplicationSerializer', 'ApplySerializer', 'ApproveSerializer', 'ApplyApplicationSerializer', 'ApplySerializer', 'ApproveSerializer',
@ -47,6 +49,9 @@ class ApplySerializer(serializers.Serializer):
class ApproveSerializer(serializers.Serializer): class ApproveSerializer(serializers.Serializer):
# 审批信息 # 审批信息
approve_permission_name = serializers.CharField(
max_length=128, default=DefaultPermissionName(), label=_('Permission name')
)
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 allow_null=True
@ -72,6 +77,19 @@ class ApproveSerializer(serializers.Serializer):
required=True, label=_('Date expired'), allow_null=True required=True, label=_('Date expired'), allow_null=True
) )
def validate_approve_permission_name(self, permission_name):
if not isinstance(self.root.instance, Ticket):
return permission_name
with tmp_to_org(self.root.instance.org_id):
already_exists = ApplicationPermission.objects.filter(name=permission_name).exists()
if not already_exists:
return permission_name
raise serializers.ValidationError(_(
'Permission named `{}` already exists'.format(permission_name)
))
def validate_approve_applications(self, approve_applications): def validate_approve_applications(self, approve_applications):
if not isinstance(self.root.instance, Ticket): if not isinstance(self.root.instance, Ticket):
return [] return []
@ -118,6 +136,9 @@ class ApplyApplicationSerializer(ApplySerializer, ApproveSerializer):
return [] return []
apply_application_group = value.get('apply_application_group', []) apply_application_group = value.get('apply_application_group', [])
if not apply_application_group:
return []
apply_type = value.get('apply_type') apply_type = value.get('apply_type')
queries = Q() queries = Q()
for application in apply_application_group: for application in apply_application_group:
@ -133,8 +154,11 @@ class ApplyApplicationSerializer(ApplySerializer, ApproveSerializer):
if not isinstance(self.root.instance, Ticket): if not isinstance(self.root.instance, Ticket):
return [] return []
apply_type = value.get('apply_type')
apply_system_user_group = value.get('apply_system_user_group', []) apply_system_user_group = value.get('apply_system_user_group', [])
if not apply_system_user_group:
return []
apply_type = value.get('apply_type')
protocol = SystemUser.get_protocol_by_application_type(apply_type) protocol = SystemUser.get_protocol_by_application_type(apply_type)
queries = Q() queries = Q()
for system_user in apply_system_user_group: for system_user in apply_system_user_group:

View File

@ -2,9 +2,11 @@ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q 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 perms.models import AssetPermission
from assets.models import Asset, SystemUser from assets.models import Asset, SystemUser
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from tickets.models import Ticket from tickets.models import Ticket
from .common import DefaultPermissionName
__all__ = [ __all__ = [
@ -44,6 +46,9 @@ class ApplySerializer(serializers.Serializer):
class ApproveSerializer(serializers.Serializer): class ApproveSerializer(serializers.Serializer):
# 审批信息 # 审批信息
approve_permission_name = serializers.CharField(
max_length=128, default=DefaultPermissionName(), label=_('Permission name')
)
approve_assets = serializers.ListField( approve_assets = serializers.ListField(
required=True, allow_null=True, child=serializers.UUIDField(), label=_('Approve assets') required=True, allow_null=True, child=serializers.UUIDField(), label=_('Approve assets')
) )
@ -76,6 +81,19 @@ class ApproveSerializer(serializers.Serializer):
required=True, label=_('Date expired'), allow_null=True required=True, label=_('Date expired'), allow_null=True
) )
def validate_approve_permission_name(self, permission_name):
if not isinstance(self.root.instance, Ticket):
return permission_name
with tmp_to_org(self.root.instance.org_id):
already_exists = AssetPermission.objects.filter(name=permission_name).exists()
if not already_exists:
return permission_name
raise serializers.ValidationError(_(
'Permission named `{}` already exists'.format(permission_name)
))
def validate_approve_assets(self, approve_assets): def validate_approve_assets(self, approve_assets):
if not isinstance(self.root.instance, Ticket): if not isinstance(self.root.instance, Ticket):
return [] return []
@ -118,10 +136,13 @@ class ApplyAssetSerializer(ApplySerializer, ApproveSerializer):
apply_ip_group = value.get('apply_ip_group', []) apply_ip_group = value.get('apply_ip_group', [])
apply_hostname_group = value.get('apply_hostname_group', []) apply_hostname_group = value.get('apply_hostname_group', [])
queries = Q(ip__in=apply_ip_group) queries = Q()
if apply_ip_group:
queries |= Q(ip__in=apply_ip_group)
for hostname in apply_hostname_group: for hostname in apply_hostname_group:
queries |= Q(hostname__icontains=hostname) queries |= Q(hostname__icontains=hostname)
if not queries:
return []
with tmp_to_org(self.root.instance.org_id): with tmp_to_org(self.root.instance.org_id):
assets_id = Asset.objects.filter(queries).values_list('id', flat=True)[:5] assets_id = Asset.objects.filter(queries).values_list('id', flat=True)[:5]
assets_id = [str(asset_id) for asset_id in assets_id] assets_id = [str(asset_id) for asset_id in assets_id]
@ -132,6 +153,9 @@ class ApplyAssetSerializer(ApplySerializer, ApproveSerializer):
return [] return []
apply_system_user_group = value.get('apply_system_user_group', []) apply_system_user_group = value.get('apply_system_user_group', [])
if not apply_system_user_group:
return []
queries = Q() queries = Q()
for system_user in apply_system_user_group: for system_user in apply_system_user_group:
queries |= Q(username__icontains=system_user) queries |= Q(username__icontains=system_user)

View File

@ -0,0 +1,30 @@
from django.utils.translation import ugettext as _
from tickets.models import Ticket
__all__ = ['DefaultPermissionName', 'get_default_permission_name']
def get_default_permission_name(ticket):
name = ''
if isinstance(ticket, Ticket):
name = _('Created by ticket ({}-{})').format(ticket.title, str(ticket.id)[:4])
return name
class DefaultPermissionName(object):
default = None
@staticmethod
def _construct_default_permission_name(serializer_field):
permission_name = ''
ticket = serializer_field.root.instance
if isinstance(ticket, Ticket):
permission_name = get_default_permission_name(ticket)
return permission_name
def set_context(self, serializer_field):
self.default = self._construct_default_permission_name(serializer_field)
def __call__(self):
return self.default

View File

@ -38,31 +38,34 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
] ]
def get_meta_serializer(self): def get_meta_serializer(self):
request = self.context['request'] default_serializer = serializers.Serializer(read_only=True)
default_serializer_class = serializers.Serializer
if isinstance(self.instance, Ticket): if isinstance(self.instance, Ticket):
_type = self.instance.type _type = self.instance.type
else: else:
_type = request.query_params.get('type') _type = self.context['request'].query_params.get('type')
if not _type: if _type:
return default_serializer_class() action_serializer_classes_mapping = type_serializer_classes_mapping.get(_type)
if action_serializer_classes_mapping:
query_action = self.context['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 not serializer_class:
serializer_class = action_serializer_classes_mapping.get('default')
else:
serializer_class = default_serializer
else:
serializer_class = default_serializer
action_serializer_classes_mapping = type_serializer_classes_mapping.get(_type) if not serializer_class:
if not action_serializer_classes_mapping: serializer_class = default_serializer
return default_serializer_class()
query_action = request.query_params.get('action') if isinstance(serializer_class, type):
_action = query_action if query_action else self.context['view'].action serializer = serializer_class()
serializer_class = action_serializer_classes_mapping.get(_action) else:
if serializer_class: serializer = serializer_class
return serializer_class()
serializer_class = action_serializer_classes_mapping.get('default') return serializer
if serializer_class:
return serializer_class()
return default_serializer_class()
class TicketDisplaySerializer(TicketSerializer): class TicketDisplaySerializer(TicketSerializer):

View File

@ -10,6 +10,20 @@ from . import const
logger = get_logger(__file__) logger = get_logger(__file__)
EMAIL_TEMPLATE = '''
<div>
<p>
{title}
<a href={ticket_detail_url}>
<strong>{ticket_detail_url_description}</strong>
</a>
</p>
<div>
{body}
</div>
</div>
'''
def send_ticket_applied_mail_to_assignees(ticket): def send_ticket_applied_mail_to_assignees(ticket):
if not ticket.assignees: if not ticket.assignees:
@ -18,22 +32,13 @@ def send_ticket_applied_mail_to_assignees(ticket):
) )
return return
subject = _('New Ticket: {} ({})'.format(ticket.title, ticket.get_type_display())) ticket_detail_url = urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id)))
ticket_detail_url = urljoin( subject = _('New Ticket - {} ({})').format(ticket.title, ticket.get_type_display())
settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id)) message = EMAIL_TEMPLATE.format(
) title=_('Your has a new ticket, applicant - {}').format(str(ticket.applicant_display)),
message = """ ticket_detail_url=ticket_detail_url,
<div> ticket_detail_url_description=_('click here to review'),
<p>{title} <a href={ticket_detail_url}>{ticket_detail_url_description}</a></p> body=ticket.body.replace('\n', '<br/>'),
<div>
{body}
</div>
</div>
""".format(
title=_('Your has a new ticket, from applicant - {}').format(str(ticket.applicant)),
ticket_detail_url=ticket_detail_url,
ticket_detail_url_description=_('click here to review'),
body=ticket.body.replace('\n', '<br/>'),
) )
if settings.DEBUG: if settings.DEBUG:
logger.debug(message) logger.debug(message)
@ -45,18 +50,15 @@ def send_ticket_processed_mail_to_applicant(ticket):
if not ticket.applicant: if not ticket.applicant:
logger.error("Not found applicant: {}({})".format(ticket.title, ticket.id)) logger.error("Not found applicant: {}({})".format(ticket.title, ticket.id))
return return
subject = _('Ticket has processed: {} ({})').format(ticket.title, ticket.processor_display)
message = """ ticket_detail_url = urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id)))
<div> subject = _('Ticket has processed - {} ({})').format(ticket.title, ticket.processor_display)
<p>{title}</p> message = EMAIL_TEMPLATE.format(
<div> title=_('Your ticket has been processed, processor - {}').format(ticket.processor_display),
{body} ticket_detail_url=ticket_detail_url,
</div> ticket_detail_url_description=_('click here to review'),
</div> body=ticket.body.replace('\n', '<br/>'),
""".format( )
title=_('Your ticket has been ({}) processed').format(ticket.processor_display),
body=ticket.body.replace('\n', '<br/>'),
)
if settings.DEBUG: if settings.DEBUG:
logger.debug(message) logger.debug(message)
recipient_list = [ticket.applicant.email] recipient_list = [ticket.applicant.email]