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") raise errors.LoginConfirmOtherError('', "Not found")
if ticket.status_open: if ticket.status_open:
raise errors.LoginConfirmWaitError(ticket.id) raise errors.LoginConfirmWaitError(ticket.id)
elif ticket.is_approved: elif ticket.action_approve:
self.request.session["auth_confirm"] = "1" self.request.session["auth_confirm"] = "1"
return return
elif ticket.is_rejected: elif ticket.action_reject:
raise errors.LoginConfirmOtherError( raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_action_display() ticket.id, ticket.get_action_display()
) )
elif ticket.is_closed: elif ticket.action_close:
raise errors.LoginConfirmOtherError( raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_action_display() ticket.id, ticket.get_action_display()
) )

View File

@ -71,7 +71,7 @@ class Action:
@classmethod @classmethod
def value_to_choices_display(cls, value): def value_to_choices_display(cls, value):
choices = cls.value_to_choices(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 @classmethod
def choices_to_value(cls, value): def choices_to_value(cls, value):

View File

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

View File

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

View File

@ -16,7 +16,7 @@ class TicketTypeChoices(TextChoices):
class TicketActionChoices(TextChoices): class TicketActionChoices(TextChoices):
apply = 'apply', _('Apply') open = 'open', _('Open')
approve = 'approve', _('Approve') approve = 'approve', _('Approve')
reject = 'reject', _('Reject') reject = 'reject', _('Reject')
close = 'close', _('Close') 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_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_system_user_group': [old_meta_system_user] if old_meta_system_user else [],
'apply_actions': old_meta.get('actions'), 'apply_actions': old_meta.get('actions'),
'apply_actions_display': [],
'apply_date_start': old_meta.get('date_start'), 'apply_date_start': old_meta.get('date_start'),
'apply_date_expired': old_meta.get('date_expired'), 'apply_date_expired': old_meta.get('date_expired'),
'approve_assets': old_meta.get('confirmed_assets', []), 'approve_assets': old_meta.get('confirmed_assets', []),
'approve_assets_snapshot': [],
'approve_system_users': old_meta.get('confirmed_system_users', []), 'approve_system_users': old_meta.get('confirmed_system_users', []),
'approve_system_users_snapshot': [],
'approve_actions': old_meta.get('actions'), 'approve_actions': old_meta.get('actions'),
'approve_actions_display': [],
'approve_date_start': old_meta.get('date_start'), 'approve_date_start': old_meta.get('date_start'),
'approve_date_expired': old_meta.get('date_expired'), 'approve_date_expired': old_meta.get('date_expired'),
} }
return new_meta return new_meta
ACTION_APPLY = 'apply' ACTION_OPEN = 'open'
ACTION_CLOSE = 'close' ACTION_CLOSE = 'close'
STATUS_OPEN = 'open' STATUS_OPEN = 'open'
STATUS_CLOSED = 'closed' STATUS_CLOSED = 'closed'
@ -46,7 +50,7 @@ def migrate_field_action(old_action, old_status):
if old_action: if old_action:
return old_action return old_action
if old_status == STATUS_OPEN: if old_status == STATUS_OPEN:
return ACTION_APPLY return ACTION_OPEN
if old_status == STATUS_CLOSED: if old_status == STATUS_CLOSED:
return ACTION_CLOSE return ACTION_CLOSE
@ -119,7 +123,8 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='ticket', model_name='ticket',
name='action', 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( migrations.AlterField(
model_name='ticket', model_name='ticket',
name='status', name='status',

View File

@ -21,3 +21,6 @@ class Comment(CommonModelMixin):
class Meta: class Meta:
ordering = ('date_created', ) 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 __ 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: class ConstructBodyMixin:
# applied body # applied body
def construct_applied_body(self): def construct_applied_body(self):
@ -32,7 +58,7 @@ class ConstructBodyMixin:
# meta body # meta body
def construct_meta_body(self): def construct_meta_body(self):
applied_body = self.construct_applied_body() applied_body = self.construct_applied_body()
if not self.is_approved: if not self.action_approve:
return applied_body return applied_body
approved_body = self.construct_approved_body() approved_body = self.construct_approved_body()
return applied_body + 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 applications.models import Application, Category
from assets.models import SystemUser from assets.models import SystemUser
from perms.models import ApplicationPermission 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: class ConstructBodyMixin:
def construct_apply_application_applied_body(self): def construct_apply_application_applied_body(self):
apply_category = self.meta['apply_category'] apply_category_display = self.meta['apply_category_display']
apply_category_display = dict(Category.choices)[apply_category] apply_type_display = self.meta['apply_type_display']
apply_type = self.meta['apply_type']
apply_type_display = dict(Category.get_type_choices(apply_category))[apply_type]
apply_application_group = self.meta['apply_application_group'] apply_application_group = self.meta['apply_application_group']
apply_system_user_group = self.meta['apply_system_user_group'] apply_system_user_group = self.meta['apply_system_user_group']
apply_date_start = self.meta['apply_date_start'] apply_date_start = self.meta['apply_date_start']
@ -34,13 +65,14 @@ class ConstructBodyMixin:
def construct_apply_application_approved_body(self): def construct_apply_application_approved_body(self):
# 审批信息 # 审批信息
approve_applications_id = self.meta['approve_applications'] approve_applications_snapshot = self.meta['approve_applications_snapshot']
approve_system_users_id = self.meta['approve_system_users'] approve_applications_snapshot_display = convert_model_data_field_name_to_verbose_name(
with tmp_to_org(self.org_id): Application, approve_applications_snapshot
approve_applications = Application.objects.filter(id__in=approve_applications_id) )
approve_system_users = SystemUser.objects.filter(id__in=approve_system_users_id) approve_system_users_snapshot = self.meta['approve_system_users_snapshot']
approve_applications_display = [str(application) for application in approve_applications] approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name(
approve_system_users_display = [str(system_user) for system_user in approve_system_users] SystemUser, approve_system_users_snapshot
)
approve_date_start = self.meta['approve_date_start'] approve_date_start = self.meta['approve_date_start']
approve_date_expired = self.meta['approve_date_expired'] approve_date_expired = self.meta['approve_date_expired']
approved_body = '''{}: {}, approved_body = '''{}: {},
@ -48,8 +80,8 @@ class ConstructBodyMixin:
{}: {}, {}: {},
{}: {}, {}: {},
'''.format( '''.format(
__('Approved applications'), ', '.join(approve_applications_display), __('Approved applications'), approve_applications_snapshot_display,
__('Approved system users'), ', '.join(approve_system_users_display), __('Approved system users'), approve_system_users_snapshot_display,
__('Approved date start'), approve_date_start, __('Approved date start'), approve_date_start,
__('Approved date expired'), approve_date_expired __('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 perms.models import AssetPermission, Action
from assets.models import Asset, SystemUser from assets.models import Asset, SystemUser
from orgs.utils import tmp_to_org, tmp_to_root_org 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: class ConstructBodyMixin:
@ -10,9 +49,7 @@ class ConstructBodyMixin:
apply_ip_group = self.meta['apply_ip_group'] apply_ip_group = self.meta['apply_ip_group']
apply_hostname_group = self.meta['apply_hostname_group'] apply_hostname_group = self.meta['apply_hostname_group']
apply_system_user_group = self.meta['apply_system_user_group'] apply_system_user_group = self.meta['apply_system_user_group']
apply_actions = self.meta['apply_actions'] apply_actions_display = self.meta['apply_actions_display']
apply_actions_display = Action.value_to_choices_display(apply_actions)
apply_actions_display = [str(action_display) for action_display in apply_actions_display]
apply_date_start = self.meta['apply_date_start'] apply_date_start = self.meta['apply_date_start']
apply_date_expired = self.meta['apply_date_expired'] apply_date_expired = self.meta['apply_date_expired']
applied_body = '''{}: {}, applied_body = '''{}: {},
@ -31,16 +68,15 @@ class ConstructBodyMixin:
return applied_body return applied_body
def construct_apply_asset_approved_body(self): def construct_apply_asset_approved_body(self):
approve_assets_id = self.meta['approve_assets'] approve_assets_snapshot = self.meta['approve_assets_snapshot']
approve_system_users_id = self.meta['approve_system_users'] approve_assets_snapshot_display = convert_model_data_field_name_to_verbose_name(
with tmp_to_org(self.org_id): Asset, approve_assets_snapshot
approve_assets = Asset.objects.filter(id__in=approve_assets_id) )
approve_system_users = SystemUser.objects.filter(id__in=approve_system_users_id) approve_system_users_snapshot = self.meta['approve_system_users_snapshot']
approve_assets_display = [str(asset) for asset in approve_assets] approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name(
approve_system_users_display = [str(system_user) for system_user in approve_system_users] SystemUser, approve_system_users_snapshot
approve_actions = self.meta['approve_actions'] )
approve_actions_display = Action.value_to_choices_display(approve_actions) approve_actions_display = self.meta['approve_actions_display']
approve_actions_display = [str(action_display) for action_display in approve_actions_display]
approve_date_start = self.meta['approve_date_start'] approve_date_start = self.meta['approve_date_start']
approve_date_expired = self.meta['approve_date_expired'] approve_date_expired = self.meta['approve_date_expired']
approved_body = '''{}: {}, approved_body = '''{}: {},
@ -49,8 +85,8 @@ class ConstructBodyMixin:
{}: {}, {}: {},
{}: {} {}: {}
'''.format( '''.format(
__('Approved assets'), ', '.join(approve_assets_display), __('Approved assets'), approve_assets_snapshot_display,
__('Approved system users'), ', '.join(approve_system_users_display), __('Approved system users'), approve_system_users_snapshot_display,
__('Approved actions'), ', '.join(approve_actions_display), __('Approved actions'), ', '.join(approve_actions_display),
__('Approved date start'), approve_date_start, __('Approved date start'), approve_date_start,
__('Approved date expired'), approve_date_expired, __('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'] __all__ = ['TicketModelMixin']
class TicketConstructBodyMixin( class TicketSetDisplayFieldMixin(meta.ConstructDisplayFieldMixin, base.SetDisplayFieldMixin):
base.ConstructBodyMixin, """ 设置 ticket display 字段(只设置,不保存)"""
apply_asset.ConstructBodyMixin,
apply_application.ConstructBodyMixin,
login_confirm.ConstructBodyMixin
):
pass pass
class TicketCreatePermissionMixin( class TicketConstructBodyMixin(meta.ConstructBodyMixin, base.ConstructBodyMixin):
base.CreatePermissionMixin, """ 构造 ticket body 信息 """
apply_asset.CreatePermissionMixin,
apply_application.CreatePermissionMixin
):
pass pass
class TicketCreateCommentMixin( class TicketCreatePermissionMixin(meta.CreatePermissionMixin, base.CreatePermissionMixin):
base.CreateCommentMixin """ 创建 ticket 相关授权规则"""
): pass
class TicketCreateCommentMixin(base.CreateCommentMixin):
""" 创建 ticket 评论"""
pass pass
class TicketModelMixin( class TicketModelMixin(
TicketConstructBodyMixin, TicketCreatePermissionMixin, TicketCreateCommentMixin TicketSetDisplayFieldMixin, TicketConstructBodyMixin, TicketCreatePermissionMixin,
TicketCreateCommentMixin
): ):
pass pass

View File

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

View File

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

View File

@ -14,16 +14,24 @@ __all__ = [
class TicketMetaApplyApplicationSerializer(BaseTicketMetaSerializer): class TicketMetaApplyApplicationSerializer(BaseTicketMetaSerializer):
# 申请信息 # 申请信息
apply_category = serializers.ChoiceField( 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( 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( 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( 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( apply_date_start = serializers.DateTimeField(
required=True, label=_('Date start') required=True, label=_('Date start')
@ -33,12 +41,20 @@ class TicketMetaApplyApplicationSerializer(BaseTicketMetaSerializer):
) )
# 审批信息 # 审批信息
approve_applications = serializers.ListField( approve_applications = serializers.ListField(
child=serializers.UUIDField(), required=True, required=True, child=serializers.UUIDField(), label=_('Approve applications')
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( approve_system_users = serializers.ListField(
child=serializers.UUIDField(), required=True, required=True, child=serializers.UUIDField(), label=_('Approve system users')
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( approve_date_start = serializers.DateTimeField(
required=True, label=_('Date start') required=True, label=_('Date start')
@ -52,9 +68,10 @@ class TicketMetaApplyApplicationApplySerializer(TicketMetaApplyApplicationSerial
class Meta: class Meta:
fields = [ fields = [
'apply_category', 'apply_type', 'apply_category', 'apply_category_display',
'apply_type', 'apply_type_display',
'apply_application_group', 'apply_system_user_group', '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): def validate_apply_type(self, tp):
@ -73,7 +90,8 @@ class TicketMetaApplyApplicationApproveSerializer(BaseTicketMetaApproveSerialize
class Meta: class Meta:
fields = { fields = {
'approve_applications', 'approve_system_users', 'approve_applications', 'approve_applications_snapshot',
'approve_system_users', 'approve_system_users_snapshot',
'approve_date_start', 'approve_date_expired' 'approve_date_start', 'approve_date_expired'
} }

View File

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

View File

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

View File

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

View File

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