diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 4e9684685..0466a9ee2 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -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() ) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index df2041154..dd26a6ae5 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -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): diff --git a/apps/tickets/api/ticket/mixin.py b/apps/tickets/api/ticket/mixin.py index 69f748741..344a9d611 100644 --- a/apps/tickets/api/ticket/mixin.py +++ b/apps/tickets/api/ticket/mixin.py @@ -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: diff --git a/apps/tickets/api/ticket/ticket.py b/apps/tickets/api/ticket/ticket.py index a1ec4dc6c..3b2c8a0cd 100644 --- a/apps/tickets/api/ticket/ticket.py +++ b/apps/tickets/api/ticket/ticket.py @@ -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]) diff --git a/apps/tickets/const.py b/apps/tickets/const.py index 7c503a6c0..8bde7fc0d 100644 --- a/apps/tickets/const.py +++ b/apps/tickets/const.py @@ -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') diff --git a/apps/tickets/migrations/0007_auto_20201224_1821.py b/apps/tickets/migrations/0007_auto_20201224_1821.py index 5367e0356..7b19fcb58 100644 --- a/apps/tickets/migrations/0007_auto_20201224_1821.py +++ b/apps/tickets/migrations/0007_auto_20201224_1821.py @@ -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', diff --git a/apps/tickets/models/comment.py b/apps/tickets/models/comment.py index 4d45de98c..11ebcd546 100644 --- a/apps/tickets/models/comment.py +++ b/apps/tickets/models/comment.py @@ -21,3 +21,6 @@ class Comment(CommonModelMixin): class Meta: ordering = ('date_created', ) + + def set_display_fields(self): + self.user_display = str(self.user) diff --git a/apps/tickets/models/ticket/mixin/base.py b/apps/tickets/models/ticket/mixin/base.py index ea021f038..fdaeb50dc 100644 --- a/apps/tickets/models/ticket/mixin/base.py +++ b/apps/tickets/models/ticket/mixin/base.py @@ -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 diff --git a/apps/tickets/models/ticket/mixin/meta/__init__.py b/apps/tickets/models/ticket/mixin/meta/__init__.py new file mode 100644 index 000000000..7b5fbad28 --- /dev/null +++ b/apps/tickets/models/ticket/mixin/meta/__init__.py @@ -0,0 +1 @@ +from .meta import * diff --git a/apps/tickets/models/ticket/mixin/apply_application.py b/apps/tickets/models/ticket/mixin/meta/apply_application.py similarity index 65% rename from apps/tickets/models/ticket/mixin/apply_application.py rename to apps/tickets/models/ticket/mixin/meta/apply_application.py index df1eef7ac..f8722daae 100644 --- a/apps/tickets/models/ticket/mixin/apply_application.py +++ b/apps/tickets/models/ticket/mixin/meta/apply_application.py @@ -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 ) diff --git a/apps/tickets/models/ticket/mixin/apply_asset.py b/apps/tickets/models/ticket/mixin/meta/apply_asset.py similarity index 64% rename from apps/tickets/models/ticket/mixin/apply_asset.py rename to apps/tickets/models/ticket/mixin/meta/apply_asset.py index e9521e746..65b9f224e 100644 --- a/apps/tickets/models/ticket/mixin/apply_asset.py +++ b/apps/tickets/models/ticket/mixin/meta/apply_asset.py @@ -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, diff --git a/apps/tickets/models/ticket/mixin/login_confirm.py b/apps/tickets/models/ticket/mixin/meta/login_confirm.py similarity index 100% rename from apps/tickets/models/ticket/mixin/login_confirm.py rename to apps/tickets/models/ticket/mixin/meta/login_confirm.py diff --git a/apps/tickets/models/ticket/mixin/meta/meta.py b/apps/tickets/models/ticket/mixin/meta/meta.py new file mode 100644 index 000000000..5dddb950a --- /dev/null +++ b/apps/tickets/models/ticket/mixin/meta/meta.py @@ -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 diff --git a/apps/tickets/models/ticket/mixin/ticket.py b/apps/tickets/models/ticket/mixin/ticket.py index 1d051b714..1d53effd6 100644 --- a/apps/tickets/models/ticket/mixin/ticket.py +++ b/apps/tickets/models/ticket/mixin/ticket.py @@ -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 diff --git a/apps/tickets/models/ticket/ticket.py b/apps/tickets/models/ticket/ticket.py index fb5fe13ae..6ef695589 100644 --- a/apps/tickets/models/ticket/ticket.py +++ b/apps/tickets/models/ticket/ticket.py @@ -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(): diff --git a/apps/tickets/permissions/ticket.py b/apps/tickets/permissions/ticket.py index c16db9fe6..1ad00b8a5 100644 --- a/apps/tickets/permissions/ticket.py +++ b/apps/tickets/permissions/ticket.py @@ -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 diff --git a/apps/tickets/serializers/ticket/meta/apply_application.py b/apps/tickets/serializers/ticket/meta/apply_application.py index 1391d321c..531d74279 100644 --- a/apps/tickets/serializers/ticket/meta/apply_application.py +++ b/apps/tickets/serializers/ticket/meta/apply_application.py @@ -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' } diff --git a/apps/tickets/serializers/ticket/meta/apply_asset.py b/apps/tickets/serializers/ticket/meta/apply_asset.py index eba34b9f8..4a78f374d 100644 --- a/apps/tickets/serializers/ticket/meta/apply_asset.py +++ b/apps/tickets/serializers/ticket/meta/apply_asset.py @@ -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): diff --git a/apps/tickets/serializers/ticket/meta/base.py b/apps/tickets/serializers/ticket/meta/base.py index dc6f3a913..8bfec3a50 100644 --- a/apps/tickets/serializers/ticket/meta/base.py +++ b/apps/tickets/serializers/ticket/meta/base.py @@ -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: diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index cb8fc385a..12591a485 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -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): diff --git a/apps/tickets/signals_handler.py b/apps/tickets/signals_handler.py index 0a5889126..bc5dd95e7 100644 --- a/apps/tickets/signals_handler.py +++ b/apps/tickets/signals_handler.py @@ -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() diff --git a/apps/tickets/utils.py b/apps/tickets/utils.py index ebd4c22b8..d56186e04 100644 --- a/apps/tickets/utils.py +++ b/apps/tickets/utils.py @@ -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(