feat: 优化工单模块 (#5361)

* feat: 优化工单模块1

* feat: 优化工单模块2

* feat: 优化工单模块3

Co-authored-by: Bai <bugatti_it@163.com>
pull/5364/head
fit2bot 2020-12-30 18:14:06 +08:00 committed by GitHub
parent 3b056ff953
commit 430e20a49c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 342 additions and 121 deletions

View File

@ -203,14 +203,14 @@ class AuthMixin:
raise errors.LoginConfirmOtherError('', "Not found")
if ticket.status_open:
raise errors.LoginConfirmWaitError(ticket.id)
elif ticket.is_approved:
elif ticket.action_approve:
self.request.session["auth_confirm"] = "1"
return
elif ticket.is_rejected:
elif ticket.action_reject:
raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_action_display()
)
elif ticket.is_closed:
elif ticket.action_close:
raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_action_display()
)

View File

@ -71,7 +71,7 @@ class Action:
@classmethod
def value_to_choices_display(cls, value):
choices = cls.value_to_choices(value)
return [dict(cls.choices())[i] for i in choices]
return [str(dict(cls.choices())[i]) for i in choices]
@classmethod
def choices_to_value(cls, value):

View File

@ -7,15 +7,15 @@ __all__ = ['TicketMetaSerializerViewMixin']
class TicketMetaSerializerViewMixin:
apply_asset_meta_serializer_classes = {
'apply': serializers.TicketMetaApplyAssetApplySerializer,
'open': serializers.TicketMetaApplyAssetApplySerializer,
'approve': serializers.TicketMetaApplyAssetApproveSerializer,
}
apply_application_meta_serializer_classes = {
'apply': serializers.TicketMetaApplyApplicationApplySerializer,
'open': serializers.TicketMetaApplyApplicationApplySerializer,
'approve': serializers.TicketMetaApplyApplicationApproveSerializer,
}
login_confirm_meta_serializer_classes = {
'apply': serializers.TicketMetaLoginConfirmApplySerializer,
'open': serializers.TicketMetaLoginConfirmApplySerializer,
}
meta_serializer_classes = {
const.TicketTypeChoices.apply_asset.value: apply_asset_meta_serializer_classes,
@ -37,7 +37,7 @@ class TicketMetaSerializerViewMixin:
return meta_class
def get_serializer_meta_field(self):
if self.action not in ['apply', 'approve']:
if self.action not in ['open', 'approve']:
return None
meta_class = self.get_serializer_meta_field_class()
if not meta_class:

View File

@ -23,7 +23,7 @@ class TicketViewSet(TicketMetaSerializerViewMixin, CommonApiMixin, viewsets.Mode
serializer_classes = {
'default': serializers.TicketDisplaySerializer,
'display': serializers.TicketDisplaySerializer,
'apply': serializers.TicketApplySerializer,
'open': serializers.TicketApplySerializer,
'approve': serializers.TicketApproveSerializer,
'reject': serializers.TicketRejectSerializer,
'close': serializers.TicketCloseSerializer,
@ -50,7 +50,7 @@ class TicketViewSet(TicketMetaSerializerViewMixin, CommonApiMixin, viewsets.Mode
return queryset
@action(detail=False, methods=[POST])
def apply(self, request, *args, **kwargs):
def open(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed])

View File

@ -16,7 +16,7 @@ class TicketTypeChoices(TextChoices):
class TicketActionChoices(TextChoices):
apply = 'apply', _('Apply')
open = 'open', _('Open')
approve = 'approve', _('Approve')
reject = 'reject', _('Reject')
close = 'close', _('Close')

View File

@ -24,19 +24,23 @@ def migrate_field_meta(tp, old_meta):
'apply_hostname_group': [old_meta_hostname] if old_meta_hostname else [],
'apply_system_user_group': [old_meta_system_user] if old_meta_system_user else [],
'apply_actions': old_meta.get('actions'),
'apply_actions_display': [],
'apply_date_start': old_meta.get('date_start'),
'apply_date_expired': old_meta.get('date_expired'),
'approve_assets': old_meta.get('confirmed_assets', []),
'approve_assets_snapshot': [],
'approve_system_users': old_meta.get('confirmed_system_users', []),
'approve_system_users_snapshot': [],
'approve_actions': old_meta.get('actions'),
'approve_actions_display': [],
'approve_date_start': old_meta.get('date_start'),
'approve_date_expired': old_meta.get('date_expired'),
}
return new_meta
ACTION_APPLY = 'apply'
ACTION_OPEN = 'open'
ACTION_CLOSE = 'close'
STATUS_OPEN = 'open'
STATUS_CLOSED = 'closed'
@ -46,7 +50,7 @@ def migrate_field_action(old_action, old_status):
if old_action:
return old_action
if old_status == STATUS_OPEN:
return ACTION_APPLY
return ACTION_OPEN
if old_status == STATUS_CLOSED:
return ACTION_CLOSE
@ -119,7 +123,8 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='ticket',
name='action',
field=models.CharField(choices=[('apply', 'Apply'), ('approve', 'Approve'), ('reject', 'Reject'), ('close', 'Close')], default='apply', max_length=16, verbose_name='Action')),
field=models.CharField(choices=[('open', 'Open'), ('approve', 'Approve'), ('reject', 'Reject'), ('close', 'Close')], default='open', max_length=16, verbose_name='Action'),
),
migrations.AlterField(
model_name='ticket',
name='status',

View File

@ -21,3 +21,6 @@ class Comment(CommonModelMixin):
class Meta:
ordering = ('date_created', )
def set_display_fields(self):
self.user_display = str(self.user)

View File

@ -2,6 +2,32 @@ import textwrap
from django.utils.translation import ugettext as __
class SetDisplayFieldMixin:
def set_applicant_display(self):
if self.has_applied:
self.applicant_display = str(self.applicant)
def set_assignees_display(self):
if self.has_applied:
assignees_display = [str(assignee) for assignee in self.assignees.all()]
self.assignees_display = ', '.join(assignees_display)
def set_processor_display(self):
if self.has_processed:
self.processor_display = str(self.processor)
def set_meta_display(self):
method_name = f'construct_meta_{self.type}_{self.action}_fields_display'
meta_display = getattr(self, method_name, lambda: {})()
self.meta.update(meta_display)
def set_display_fields(self):
self.set_applicant_display()
self.set_processor_display()
self.set_meta_display()
class ConstructBodyMixin:
# applied body
def construct_applied_body(self):
@ -32,7 +58,7 @@ class ConstructBodyMixin:
# meta body
def construct_meta_body(self):
applied_body = self.construct_applied_body()
if not self.is_approved:
if not self.action_approve:
return applied_body
approved_body = self.construct_approved_body()
return applied_body + approved_body

View File

@ -0,0 +1 @@
from .meta import *

View File

@ -3,15 +3,46 @@ from orgs.utils import tmp_to_org, tmp_to_root_org
from applications.models import Application, Category
from assets.models import SystemUser
from perms.models import ApplicationPermission
from tickets.utils import convert_model_data_field_name_to_verbose_name
class ConstructDisplayFieldMixin:
def construct_meta_apply_application_open_fields_display(self):
meta_display_fields = ['apply_category_display', 'apply_type_display']
apply_category = self.meta['apply_category']
apply_category_display = dict(Category.choices)[apply_category]
apply_type = self.meta['apply_type']
apply_type_display = dict(Category.get_type_choices(apply_category))[apply_type]
meta_display_values = [apply_category_display, apply_type_display]
meta_display = dict(zip(meta_display_fields, meta_display_values))
return meta_display
def construct_meta_apply_application_approve_fields_display(self):
meta_display_fields = ['approve_applications_snapshot', 'approve_system_users_snapshot']
approve_applications_id = self.meta['approve_applications']
approve_system_users_id = self.meta['approve_system_users']
with tmp_to_org(self.org_id):
approve_applications_snapshot = list(
Application.objects.filter(id__in=approve_applications_id).values(
'name', 'category', 'type'
)
)
approve_system_users_snapshot = list(
SystemUser.objects.filter(id__in=approve_system_users_id).values(
'name', 'username', 'username_same_with_user', 'protocol',
'auto_push', 'sudo', 'home', 'sftp_root'
)
)
meta_display_values = [approve_applications_snapshot, approve_system_users_snapshot]
meta_display = dict(zip(meta_display_fields, meta_display_values))
return meta_display
class ConstructBodyMixin:
def construct_apply_application_applied_body(self):
apply_category = self.meta['apply_category']
apply_category_display = dict(Category.choices)[apply_category]
apply_type = self.meta['apply_type']
apply_type_display = dict(Category.get_type_choices(apply_category))[apply_type]
apply_category_display = self.meta['apply_category_display']
apply_type_display = self.meta['apply_type_display']
apply_application_group = self.meta['apply_application_group']
apply_system_user_group = self.meta['apply_system_user_group']
apply_date_start = self.meta['apply_date_start']
@ -34,13 +65,14 @@ class ConstructBodyMixin:
def construct_apply_application_approved_body(self):
# 审批信息
approve_applications_id = self.meta['approve_applications']
approve_system_users_id = self.meta['approve_system_users']
with tmp_to_org(self.org_id):
approve_applications = Application.objects.filter(id__in=approve_applications_id)
approve_system_users = SystemUser.objects.filter(id__in=approve_system_users_id)
approve_applications_display = [str(application) for application in approve_applications]
approve_system_users_display = [str(system_user) for system_user in approve_system_users]
approve_applications_snapshot = self.meta['approve_applications_snapshot']
approve_applications_snapshot_display = convert_model_data_field_name_to_verbose_name(
Application, approve_applications_snapshot
)
approve_system_users_snapshot = self.meta['approve_system_users_snapshot']
approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name(
SystemUser, approve_system_users_snapshot
)
approve_date_start = self.meta['approve_date_start']
approve_date_expired = self.meta['approve_date_expired']
approved_body = '''{}: {},
@ -48,8 +80,8 @@ class ConstructBodyMixin:
{}: {},
{}: {},
'''.format(
__('Approved applications'), ', '.join(approve_applications_display),
__('Approved system users'), ', '.join(approve_system_users_display),
__('Approved applications'), approve_applications_snapshot_display,
__('Approved system users'), approve_system_users_snapshot_display,
__('Approved date start'), approve_date_start,
__('Approved date expired'), approve_date_expired
)

View File

@ -3,6 +3,45 @@ from django.utils.translation import ugettext as __
from perms.models import AssetPermission, Action
from assets.models import Asset, SystemUser
from orgs.utils import tmp_to_org, tmp_to_root_org
from tickets.utils import convert_model_data_field_name_to_verbose_name
class ConstructDisplayFieldMixin:
def construct_meta_apply_asset_open_fields_display(self):
meta_display_fields = ['apply_actions_display']
apply_actions = self.meta['apply_actions']
apply_actions_display = Action.value_to_choices_display(apply_actions)
meta_display_values = [apply_actions_display]
meta_display = dict(zip(meta_display_fields, meta_display_values))
return meta_display
def construct_meta_apply_asset_approve_fields_display(self):
meta_display_fields = [
'approve_actions_display', 'approve_assets_snapshot', 'approve_system_users_snapshot'
]
approve_actions = self.meta['approve_actions']
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)
with tmp_to_org(self.org_id):
approve_assets_snapshot = list(
Asset.objects.filter(id__in=approve_assets_id).values(
'hostname', 'ip', 'protocols', 'platform__name', 'public_ip'
)
)
approve_system_users_snapshot = list(
SystemUser.objects.filter(id__in=approve_system_users_id).values(
'name', 'username', 'username_same_with_user', 'protocol',
'auto_push', 'sudo', 'home', 'sftp_root'
)
)
meta_display_values = [
approve_actions_display, approve_assets_snapshot, approve_system_users_snapshot
]
meta_display = dict(zip(meta_display_fields, meta_display_values))
return meta_display
class ConstructBodyMixin:
@ -10,9 +49,7 @@ class ConstructBodyMixin:
apply_ip_group = self.meta['apply_ip_group']
apply_hostname_group = self.meta['apply_hostname_group']
apply_system_user_group = self.meta['apply_system_user_group']
apply_actions = self.meta['apply_actions']
apply_actions_display = Action.value_to_choices_display(apply_actions)
apply_actions_display = [str(action_display) for action_display in apply_actions_display]
apply_actions_display = self.meta['apply_actions_display']
apply_date_start = self.meta['apply_date_start']
apply_date_expired = self.meta['apply_date_expired']
applied_body = '''{}: {},
@ -31,16 +68,15 @@ class ConstructBodyMixin:
return applied_body
def construct_apply_asset_approved_body(self):
approve_assets_id = self.meta['approve_assets']
approve_system_users_id = self.meta['approve_system_users']
with tmp_to_org(self.org_id):
approve_assets = Asset.objects.filter(id__in=approve_assets_id)
approve_system_users = SystemUser.objects.filter(id__in=approve_system_users_id)
approve_assets_display = [str(asset) for asset in approve_assets]
approve_system_users_display = [str(system_user) for system_user in approve_system_users]
approve_actions = self.meta['approve_actions']
approve_actions_display = Action.value_to_choices_display(approve_actions)
approve_actions_display = [str(action_display) for action_display in approve_actions_display]
approve_assets_snapshot = self.meta['approve_assets_snapshot']
approve_assets_snapshot_display = convert_model_data_field_name_to_verbose_name(
Asset, approve_assets_snapshot
)
approve_system_users_snapshot = self.meta['approve_system_users_snapshot']
approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name(
SystemUser, approve_system_users_snapshot
)
approve_actions_display = self.meta['approve_actions_display']
approve_date_start = self.meta['approve_date_start']
approve_date_expired = self.meta['approve_date_expired']
approved_body = '''{}: {},
@ -49,8 +85,8 @@ class ConstructBodyMixin:
{}: {},
{}: {}
'''.format(
__('Approved assets'), ', '.join(approve_assets_display),
__('Approved system users'), ', '.join(approve_system_users_display),
__('Approved assets'), approve_assets_snapshot_display,
__('Approved system users'), approve_system_users_snapshot_display,
__('Approved actions'), ', '.join(approve_actions_display),
__('Approved date start'), approve_date_start,
__('Approved date expired'), approve_date_expired,

View File

@ -0,0 +1,35 @@
from . import apply_asset, apply_application, login_confirm
__all__ = ['ConstructDisplayFieldMixin', 'ConstructBodyMixin', 'CreatePermissionMixin']
modules = (apply_asset, apply_application, login_confirm)
construct_display_field_mixin_cls_name = 'ConstructDisplayFieldMixin'
construct_body_mixin_cls_name = 'ConstructBodyMixin'
create_permission_mixin_cls_name = 'CreatePermissionMixin'
def get_mixin_base_cls_list(base_cls_name):
return [
getattr(module, base_cls_name) for module in modules if hasattr(module, base_cls_name)
]
class ConstructDisplayFieldMixin(
*get_mixin_base_cls_list(construct_display_field_mixin_cls_name)
):
pass
class ConstructBodyMixin(
*get_mixin_base_cls_list(construct_body_mixin_cls_name)
):
pass
class CreatePermissionMixin(
*get_mixin_base_cls_list(create_permission_mixin_cls_name)
):
pass

View File

@ -1,32 +1,30 @@
from . import base, apply_asset, apply_application, login_confirm
from . import base, meta
__all__ = ['TicketModelMixin']
class TicketConstructBodyMixin(
base.ConstructBodyMixin,
apply_asset.ConstructBodyMixin,
apply_application.ConstructBodyMixin,
login_confirm.ConstructBodyMixin
):
class TicketSetDisplayFieldMixin(meta.ConstructDisplayFieldMixin, base.SetDisplayFieldMixin):
""" 设置 ticket display 字段(只设置,不保存)"""
pass
class TicketCreatePermissionMixin(
base.CreatePermissionMixin,
apply_asset.CreatePermissionMixin,
apply_application.CreatePermissionMixin
):
class TicketConstructBodyMixin(meta.ConstructBodyMixin, base.ConstructBodyMixin):
""" 构造 ticket body 信息 """
pass
class TicketCreateCommentMixin(
base.CreateCommentMixin
):
class TicketCreatePermissionMixin(meta.CreatePermissionMixin, base.CreatePermissionMixin):
""" 创建 ticket 相关授权规则"""
pass
class TicketCreateCommentMixin(base.CreateCommentMixin):
""" 创建 ticket 评论"""
pass
class TicketModelMixin(
TicketConstructBodyMixin, TicketCreatePermissionMixin, TicketCreateCommentMixin
TicketSetDisplayFieldMixin, TicketConstructBodyMixin, TicketCreatePermissionMixin,
TicketCreateCommentMixin
):
pass

View File

@ -24,6 +24,8 @@ class ModelJSONFieldEncoder(json.JSONEncoder):
return obj.strftime(settings.DATETIME_DISPLAY_FORMAT)
if isinstance(obj, uuid.UUID):
return str(obj)
if isinstance(obj, type(_("ugettext_lazy"))):
return str(obj)
else:
return super().default(obj)
@ -37,7 +39,7 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
meta = models.JSONField(encoder=ModelJSONFieldEncoder, verbose_name=_("Meta"))
action = models.CharField(
choices=const.TicketActionChoices.choices, max_length=16,
default=const.TicketActionChoices.apply.value, verbose_name=_("Action")
default=const.TicketActionChoices.open.value, verbose_name=_("Action")
)
status = models.CharField(
max_length=16, choices=const.TicketStatusChoices.choices,
@ -75,9 +77,18 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
def __str__(self):
return '{}({})'.format(self.title, self.applicant_display)
# type
@property
def type_apply_asset(self):
return self.type == const.TicketTypeChoices.apply_asset.value
def has_assignee(self, assignee):
return self.assignees.filter(id=assignee.id).exists()
@property
def type_apply_application(self):
return self.type == const.TicketTypeChoices.apply_application.value
@property
def type_login_confirm(self):
return self.type == const.TicketTypeChoices.login_confirm.value
# status
@property
@ -88,34 +99,46 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
def status_open(self):
return self.status == const.TicketStatusChoices.open.value
def set_status_closed(self):
self.status = const.TicketStatusChoices.closed.value
# action
@property
def is_applied(self):
return self.action == const.TicketActionChoices.apply.value
def action_open(self):
return self.action == const.TicketActionChoices.open.value
@property
def is_approved(self):
def action_approve(self):
return self.action == const.TicketActionChoices.approve.value
@property
def is_rejected(self):
def action_reject(self):
return self.action == const.TicketActionChoices.reject.value
@property
def is_closed(self):
def action_close(self):
return self.action == const.TicketActionChoices.close.value
@property
def is_processed(self):
return self.is_approved or self.is_rejected or self.is_closed
def has_applied(self):
return self.action_open
@property
def has_processed(self):
return self.action_approve or self.action_reject or self.action_close
def set_action_close(self):
self.action = const.TicketActionChoices.close.value
# perform action
def close(self, processor):
self.processor = processor
self.action = const.TicketActionChoices.close.value
self.set_action_close()
self.save()
# tickets
#
def has_assignee(self, assignee):
return self.assignees.filter(id=assignee.id).exists()
@classmethod
def all(cls):
with tmp_to_root_org():

View File

@ -9,4 +9,5 @@ class IsAssignee(permissions.BasePermission):
class NotClosed(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return True
return not obj.status_closed

View File

@ -14,16 +14,24 @@ __all__ = [
class TicketMetaApplyApplicationSerializer(BaseTicketMetaSerializer):
# 申请信息
apply_category = serializers.ChoiceField(
choices=Category.choices, required=True, label=_('Category')
required=True, choices=Category.choices, label=_('Category')
)
apply_category_display = serializers.CharField(
read_only=True, label=_('Category display')
)
apply_type = serializers.ChoiceField(
choices=Category.get_all_type_choices(), required=True, label=_('Type')
required=True, choices=Category.get_all_type_choices(), label=_('Type')
)
apply_type_display = serializers.CharField(
required=False, read_only=True, label=_('Type display')
)
apply_application_group = serializers.ListField(
child=serializers.CharField(), default=list, label=_('Application group')
required=False, child=serializers.CharField(), label=_('Application group'),
default=list,
)
apply_system_user_group = serializers.ListField(
child=serializers.CharField(), default=list, label=_('System user group')
required=False, child=serializers.CharField(), label=_('System user group'),
default=list,
)
apply_date_start = serializers.DateTimeField(
required=True, label=_('Date start')
@ -33,12 +41,20 @@ class TicketMetaApplyApplicationSerializer(BaseTicketMetaSerializer):
)
# 审批信息
approve_applications = serializers.ListField(
child=serializers.UUIDField(), required=True,
label=_('Approve applications')
required=True, child=serializers.UUIDField(), label=_('Approve applications')
)
approve_applications_snapshot = serializers.ListField(
required=False, read_only=True, child=serializers.CharField(),
label=_('Approve applications display'),
default=list
)
approve_system_users = serializers.ListField(
child=serializers.UUIDField(), required=True,
label=_('Approve system users')
required=True, child=serializers.UUIDField(), label=_('Approve system users')
)
approve_system_users_snapshot = serializers.ListField(
required=False, read_only=True, child=serializers.CharField(),
label=_('Approve system user display'),
default=list
)
approve_date_start = serializers.DateTimeField(
required=True, label=_('Date start')
@ -52,9 +68,10 @@ class TicketMetaApplyApplicationApplySerializer(TicketMetaApplyApplicationSerial
class Meta:
fields = [
'apply_category', 'apply_type',
'apply_category', 'apply_category_display',
'apply_type', 'apply_type_display',
'apply_application_group', 'apply_system_user_group',
'apply_date_start', 'apply_date_expired'
'apply_date_start', 'apply_date_expired',
]
def validate_apply_type(self, tp):
@ -73,7 +90,8 @@ class TicketMetaApplyApplicationApproveSerializer(BaseTicketMetaApproveSerialize
class Meta:
fields = {
'approve_applications', 'approve_system_users',
'approve_applications', 'approve_applications_snapshot',
'approve_system_users', 'approve_system_users_snapshot',
'approve_date_start', 'approve_date_expired'
}

View File

@ -1,7 +1,6 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from perms.serializers import ActionsField
from perms.models import Action
from assets.models import Asset, SystemUser
from .base import BaseTicketMetaSerializer, BaseTicketMetaApproveSerializerMixin
@ -15,16 +14,24 @@ __all__ = [
class TicketMetaApplyAssetSerializer(BaseTicketMetaSerializer):
# 申请信息
apply_ip_group = serializers.ListField(
child=serializers.IPAddressField(), default=list, label=_('IP group')
required=False, child=serializers.IPAddressField(), label=_('IP group'),
default=list,
)
apply_hostname_group = serializers.ListField(
child=serializers.CharField(), default=list, label=_('Hostname group')
required=False, child=serializers.CharField(), label=_('Hostname group'),
default=list,
)
apply_system_user_group = serializers.ListField(
child=serializers.CharField(), default=list, label=_('System user group')
required=False, child=serializers.CharField(), label=_('System user group'),
default=list,
)
apply_actions = ActionsField(
choices=Action.DB_CHOICES, default=Action.ALL
required=True
)
apply_actions_display = serializers.ListField(
required=False, read_only=True, child=serializers.CharField(),
label=_('Approve assets display'),
default=list,
)
apply_date_start = serializers.DateTimeField(
required=True, label=_('Date start')
@ -36,11 +43,26 @@ class TicketMetaApplyAssetSerializer(BaseTicketMetaSerializer):
approve_assets = serializers.ListField(
required=True, child=serializers.UUIDField(), label=_('Approve assets')
)
approve_assets_snapshot = serializers.ListField(
required=False, read_only=True, child=serializers.DictField(),
label=_('Approve assets display'),
default=list,
)
approve_system_users = serializers.ListField(
required=True, child=serializers.UUIDField(), label=_('Approve system users')
)
approve_system_users_snapshot = serializers.ListField(
required=False, read_only=True, child=serializers.DictField(),
label=_('Approve assets display'),
default=list,
)
approve_actions = ActionsField(
required=False, choices=Action.DB_CHOICES, default=Action.ALL
required=True
)
approve_actions_display = serializers.ListField(
required=False, read_only=True, child=serializers.CharField(),
label=_('Approve assets display'),
default=list,
)
approve_date_start = serializers.DateTimeField(
required=True, label=_('Date start')
@ -54,9 +76,10 @@ class TicketMetaApplyAssetApplySerializer(TicketMetaApplyAssetSerializer):
class Meta:
fields = [
'apply_ip_group', 'apply_hostname_group',
'apply_system_user_group', 'apply_actions',
'apply_date_start', 'apply_date_expired'
'apply_ip_group',
'apply_hostname_group', 'apply_system_user_group',
'apply_actions', 'apply_actions_display',
'apply_date_start', 'apply_date_expired',
]
@ -65,9 +88,10 @@ class TicketMetaApplyAssetApproveSerializer(BaseTicketMetaApproveSerializerMixin
class Meta:
fields = [
'approve_assets', 'approve_system_users',
'approve_actions', 'approve_date_start',
'approve_date_expired'
'approve_assets', 'approve_assets_snapshot',
'approve_system_users', 'approve_system_users_snapshot',
'approve_actions', 'approve_actions_display',
'approve_date_start', 'approve_date_expired',
]
def validate_approve_assets(self, approve_assets):

View File

@ -8,6 +8,9 @@ from assets.models import SystemUser
class BaseTicketMetaSerializer(serializers.Serializer):
class Meta:
fields = '__all__'
def get_fields(self):
fields = super().get_fields()
required_fields = self.Meta.fields
@ -20,9 +23,6 @@ class BaseTicketMetaSerializer(serializers.Serializer):
})
return fields
class Meta:
fields = '__all__'
class BaseTicketMetaApproveSerializerMixin:

View File

@ -41,7 +41,7 @@ class TicketDisplaySerializer(TicketSerializer):
class TicketActionSerializer(TicketSerializer):
action = ReadableHiddenField(default=const.TicketActionChoices.apply.value)
action = ReadableHiddenField(default=const.TicketActionChoices.open.value)
class Meta(TicketSerializer.Meta):
required_fields = ['action']
@ -94,7 +94,7 @@ class TicketApplySerializer(TicketActionSerializer):
@staticmethod
def validate_action(action):
return const.TicketActionChoices.apply.value
return const.TicketActionChoices.open.value
class TicketProcessSerializer(TicketActionSerializer):
@ -110,13 +110,11 @@ 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))
extra_kwargs = {
'meta': {'read_only': True}
}
def validate_meta(self, meta):
meta.update(self.instance.meta)
return meta
instance_meta = self.instance.meta
instance_meta.update(meta)
return instance_meta
@staticmethod
def validate_action(action):

View File

@ -17,20 +17,18 @@ logger = get_logger(__name__)
@receiver(pre_save, sender=Ticket)
def on_ticket_pre_save(sender, instance=None, **kwargs):
if instance.is_applied:
instance.applicant_display = str(instance.applicant)
if instance.is_processed:
instance.processor_display = str(instance.processor)
instance.status = const.TicketStatusChoices.closed.value
if instance.has_processed:
instance.set_status_closed()
instance.set_display_fields()
@receiver(post_save, sender=Ticket)
def on_ticket_processed(sender, instance=None, created=False, **kwargs):
if not instance.is_processed:
def on_ticket_processed(sender, instance=None, **kwargs):
if not instance.has_processed:
return
logger.debug('Ticket is processed, send mail: {}'.format(instance.id))
instance.create_action_comment()
if instance.is_approved:
if instance.action_approve:
instance.create_permission()
instance.create_approved_comment()
send_ticket_processed_mail_to_applicant(instance)
@ -43,18 +41,15 @@ def on_ticket_assignees_changed(sender, instance=None, action=None, reverse=Fals
if action != 'post_add':
return
ticket = instance
assignees_display = [str(assignee) for assignee in ticket.assignees.all()]
logger.debug(
'Receives ticket and assignees changed signal, ticket: {}, assignees: {}'
''.format(ticket.title, assignees_display)
)
ticket.assignees_display = ', '.join(assignees_display)
logger.debug('Receives ticket and assignees changed signal, ticket: {}'.format(ticket.title))
ticket.set_assignees_display()
ticket.save()
logger.debug('Send applied email to assignees: {}'.format(assignees_display))
assignees = model.objects.filter(pk__in=pk_set)
assignees_display = [str(assignee) for assignee in assignees]
logger.debug('Send applied email to assignees: {}'.format(assignees_display))
send_ticket_applied_mail_to_assignees(ticket, assignees)
@receiver(pre_save, sender=Comment)
def on_comment_create(sender, instance=None, created=False, **kwargs):
instance.user_display = str(instance.user)
instance.set_display_fields()

View File

@ -11,6 +11,32 @@ from . import const
logger = get_logger(__file__)
def convert_model_data_field_name_to_verbose_name(model, name_data):
"""将Model以field_name为key的数据转换为以field_verbose_name为key的数据"""
if isinstance(name_data, dict):
name_data = [name_data]
model_fields_name_verbose_name_mapping = {
field.name: field.verbose_name for field in model._meta.fields
}
def get_verbose_name(field_name):
verbose_name = model_fields_name_verbose_name_mapping.get(field_name)
if not verbose_name:
other_name = field_name.split('__', 1)[0]
verbose_name = model_fields_name_verbose_name_mapping.get(other_name)
if not verbose_name:
verbose_name = field_name
return verbose_name
verbose_name_data = [
{get_verbose_name(name): value for name, value in d.items()}
for d in name_data
]
return verbose_name_data
def send_ticket_applied_mail_to_assignees(ticket, assignees):
if not assignees:
logger.debug("Not found assignees, ticket: {}({}), assignees: {}".format(