mirror of https://github.com/jumpserver/jumpserver
feat: 优化工单模块 (#5361)
* feat: 优化工单模块1 * feat: 优化工单模块2 * feat: 优化工单模块3 Co-authored-by: Bai <bugatti_it@163.com>pull/5364/head
parent
3b056ff953
commit
430e20a49c
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -21,3 +21,6 @@ class Comment(CommonModelMixin):
|
|||
|
||||
class Meta:
|
||||
ordering = ('date_created', )
|
||||
|
||||
def set_display_fields(self):
|
||||
self.user_display = str(self.user)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .meta import *
|
|
@ -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
|
||||
)
|
|
@ -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,
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue