diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 738c95986..5b800311b 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -207,12 +207,13 @@ class SystemUser(BaseUser): @classmethod def get_protocol_by_application_type(cls, app_type): from applications.const import ApplicationTypeChoices - if app_type in ApplicationTypeChoices.remote_app_types(): + if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS: + protocol = app_type + elif app_type in ApplicationTypeChoices.remote_app_types(): protocol = cls.PROTOCOL_RDP else: - protocol = app_type - if protocol in cls.APPLICATION_CATEGORY_PROTOCOLS: - return protocol + protocol = None + return protocol class Meta: ordering = ['name'] diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 3b0d0f9fa..8bc7377e5 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -254,152 +254,3 @@ def get_disk_usage(): mount_points = [p.mountpoint for p in partitions] usages = {p: psutil.disk_usage(p) for p in mount_points} return usages - - -# Verify that `value` is in `choices` and throw an `JMSException` -# --------------------------------------------------------------- - - -def check_value_in_choices(value, choices, **kwargs): - # get raise parameters from kwargs - raise_exception = kwargs.get('raise_exception', False) - raise_error_msg = kwargs.get('raise_error_msg', None) - raise_reverse = kwargs.get('raise_reverse', False) - - def should_raise(): - """ - Simplify the following logic: - - if raise_exception: - if raise_reverse and value_in_choices: - return True - else: - return False - - if not raise_reverse and not value_in_choices: - return True - else: - return False - else: - return False - """ - return raise_exception and raise_reverse == value_in_choices - - value_in_choices = True if value in choices else False - - if not should_raise(): - return value_in_choices - - if raise_error_msg is None: - raise_error_msg = _('Value `{}` is not in Choices: `{}`'.format(value, choices)) - - raise JMSException(raise_error_msg) - - -# Quick lookup dict -# ----------------- - - -class QuickLookupDict(object): - """ - 说明: - dict 类型数据的快速查找 - 作用: - 可根据指定 key 的深度 path 快速查找出对应的 value 值 - 依赖: - data-tree==0.0.1 - 实现: - 通过对 data-tree 库的封装来实现 - """ - - def __init__(self, data, key_delimiter='.'): - self._check_data_type(data, type_choices=(dict, ), error='Expected `data` type is dict') - self.data = data - self.key_delimiter = key_delimiter - self._data_tree = self._get_data_tree(data, key_delimiter) - - # Method encapsulated of `data-tree` - # ---------------------------------- - - @staticmethod - def _get_data_tree(data, key_delimiter): - tree = data_tree.Data_tree_node( - arg_data=data, arg_string_delimiter_for_path=key_delimiter - ) - return tree - - def _get_data_tree_node(self, path): - return self._data_tree.get(arg_path=path) - - @staticmethod - def _get_data_tree_node_original_data(tree_node): - if isinstance(tree_node, data_tree.Data_tree_node): - data = tree_node.get_data_in_format_for_export() - else: - data = tree_node - return data - - # Method called internally - # ------------------------ - - @staticmethod - def _check_data_type(data, type_choices, error=None): - error = error or '`data` type error, {} => {}'.format(type(data), type_choices) - assert isinstance(data, type_choices), error - - @staticmethod - def _check_object_callable(_object): - if _object is None: - return False - if not callable(_object): - return False - return True - - # Method called externally - # ------------------------ - - def get(self, key_path, default=None): - error = 'key_path - can be either a list of keys, or a delimited string.' - self._check_data_type(key_path, (list, str,), error=error) - - tree_node = self._get_data_tree_node(key_path) - if tree_node is None: - return default - value = self._get_data_tree_node_original_data(tree_node) - return value - - def get_many(self, key_paths, default=None): - values = [ - self.get(key_path, default=default) for key_path in key_paths - ] - return values - - def find_one(self, key_paths, default=None, callable_filter=None): - """ - 按照 key_paths 顺序查找,返回第一个满足 `callable_filter` 规则的值 - """ - - def get_data_filter(): - if self._check_object_callable(callable_filter): - return callable_filter - return self.__default_find_callable_filter - - _filter = get_data_filter() - - for key_path in key_paths: - value = self.get(key_path=key_path) - if _filter(key_path, value): - return value - - return default - - # Method default - # -------------- - - @staticmethod - def __default_find_callable_filter(key_path, value): - return value is not None - - - - diff --git a/apps/terminal/migrations/0031_auto_20210113_1356.py b/apps/terminal/migrations/0031_auto_20210113_1356.py new file mode 100644 index 000000000..6866b0c2a --- /dev/null +++ b/apps/terminal/migrations/0031_auto_20210113_1356.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1 on 2021-01-13 05:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0030_terminal_type'), + ] + + operations = [ + migrations.AlterField( + model_name='commandstorage', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='replaystorage', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + ] diff --git a/apps/terminal/models/storage.py b/apps/terminal/models/storage.py index e3b7113c2..b74feae40 100644 --- a/apps/terminal/models/storage.py +++ b/apps/terminal/models/storage.py @@ -18,7 +18,7 @@ class CommandStorage(CommonModelMixin): default=const.CommandStorageTypeChoices.server.value, verbose_name=_('Type'), ) meta = EncryptJsonDictTextField(default={}) - comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) def __str__(self): return self.name @@ -58,7 +58,7 @@ class ReplayStorage(CommonModelMixin): default=const.ReplayStorageTypeChoices.server.value, verbose_name=_('Type') ) meta = EncryptJsonDictTextField(default={}) - comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) def __str__(self): return self.name diff --git a/apps/tickets/api/comment.py b/apps/tickets/api/comment.py index a9f79e5f3..dc7740d1a 100644 --- a/apps/tickets/api/comment.py +++ b/apps/tickets/api/comment.py @@ -2,7 +2,6 @@ # from rest_framework import viewsets, mixins -from django.shortcuts import get_object_or_404 from common.exceptions import JMSException from common.utils import lazyproperty from tickets import serializers @@ -22,11 +21,10 @@ class CommentViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet): if getattr(self, 'swagger_fake_view', False): return None ticket_id = self.request.query_params.get('ticket_id') - try: - ticket = get_object_or_404(Ticket, pk=ticket_id) - return ticket - except Exception as e: - raise JMSException(str(e)) + ticket = Ticket.all().filter(pk=ticket_id).first() + if not ticket: + raise JMSException('Not found Ticket object about `id={}`'.format(ticket_id)) + return ticket def get_serializer_context(self): context = super().get_serializer_context() diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 775baccba..79d49b37d 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -4,6 +4,7 @@ from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.exceptions import MethodNotAllowed +from rest_framework.response import Response from common.const.http import POST, PUT from common.mixins.api import CommonApiMixin @@ -21,12 +22,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): permission_classes = (IsValidUser,) serializer_class = serializers.TicketDisplaySerializer serializer_classes = { - 'default': serializers.TicketDisplaySerializer, - 'display': serializers.TicketDisplaySerializer, 'open': serializers.TicketApplySerializer, 'approve': serializers.TicketApproveSerializer, - 'reject': serializers.TicketRejectSerializer, - 'close': serializers.TicketCloseSerializer, } filterset_fields = [ 'id', 'title', 'type', 'action', 'status', 'applicant', 'applicant_display', 'processor', @@ -49,19 +46,31 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): queryset = Ticket.get_user_related_tickets(self.request.user) return queryset + def perform_create(self, serializer): + instance = serializer.save() + instance.open(applicant=self.request.user) + @action(detail=False, methods=[POST]) def open(self, request, *args, **kwargs): return super().create(request, *args, **kwargs) @action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed]) def approve(self, request, *args, **kwargs): - return super().update(request, *args, **kwargs) + response = super().update(request, *args, **kwargs) + instance = self.get_object() + instance.approve(processor=self.request.user) + return response @action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed]) def reject(self, request, *args, **kwargs): - return super().update(request, *args, **kwargs) + instance = self.get_object() + serializer = self.get_serializer(instance) + instance.reject(processor=request.user) + return Response(serializer.data) @action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed]) def close(self, request, *args, **kwargs): - return super().update(request, *args, **kwargs) - + instance = self.get_object() + serializer = self.get_serializer(instance) + instance.close(processor=request.user) + return Response(serializer.data) diff --git a/apps/tickets/handler/__init__.py b/apps/tickets/handler/__init__.py new file mode 100644 index 000000000..42ef0d871 --- /dev/null +++ b/apps/tickets/handler/__init__.py @@ -0,0 +1,7 @@ +from django.utils.module_loading import import_string + + +def get_ticket_handler(ticket): + handler_class_path = 'tickets.handler.{}.Handler'.format(ticket.type) + handler_class = import_string(handler_class_path) + return handler_class(ticket=ticket) diff --git a/apps/tickets/models/ticket/mixin/meta/apply_application.py b/apps/tickets/handler/apply_application.py similarity index 56% rename from apps/tickets/models/ticket/mixin/meta/apply_application.py rename to apps/tickets/handler/apply_application.py index 000cd7338..399f71782 100644 --- a/apps/tickets/models/ticket/mixin/meta/apply_application.py +++ b/apps/tickets/handler/apply_application.py @@ -4,25 +4,32 @@ from applications.models import Application from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices from assets.models import SystemUser from perms.models import ApplicationPermission -from tickets.utils import convert_model_instance_data_field_name_to_verbose_name +from tickets.utils import convert_model_data_field_name_to_verbose_name +from .base import BaseHandler -class ConstructDisplayFieldMixin: - def construct_meta_apply_application_open_fields_display(self): +class Handler(BaseHandler): + + def _on_approve(self): + super()._on_approve() + self._create_application_permission() + + # display + def _construct_meta_display_of_open(self): meta_display_fields = ['apply_category_display', 'apply_type_display'] - apply_category = self.meta.get('apply_category') + apply_category = self.ticket.meta.get('apply_category') apply_category_display = ApplicationCategoryChoices.get_label(apply_category) - apply_type = self.meta.get('apply_type') + apply_type = self.ticket.meta.get('apply_type') apply_type_display = ApplicationTypeChoices.get_label(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): + def _construct_meta_display_of_approve(self): meta_display_fields = ['approve_applications_snapshot', 'approve_system_users_snapshot'] - approve_applications_id = self.meta.get('approve_applications', []) - approve_system_users_id = self.meta.get('approve_system_users', []) - with tmp_to_org(self.org_id): + approve_applications_id = self.ticket.meta.get('approve_applications', []) + approve_system_users_id = self.ticket.meta.get('approve_system_users', []) + with tmp_to_org(self.ticket.org_id): approve_applications_snapshot = list( Application.objects.filter(id__in=approve_applications_id).values( 'name', 'category', 'type' @@ -38,16 +45,14 @@ class ConstructDisplayFieldMixin: meta_display = dict(zip(meta_display_fields, meta_display_values)) return meta_display - -class ConstructBodyMixin: - - def construct_apply_application_applied_body(self): - apply_category_display = self.meta.get('apply_category_display') - apply_type_display = self.meta.get('apply_type_display') - apply_application_group = self.meta.get('apply_application_group', []) - apply_system_user_group = self.meta.get('apply_system_user_group', []) - apply_date_start = self.meta.get('apply_date_start') - apply_date_expired = self.meta.get('apply_date_expired') + # body + def _construct_meta_body_of_open(self): + apply_category_display = self.ticket.meta.get('apply_category_display') + apply_type_display = self.ticket.meta.get('apply_type_display') + apply_application_group = self.ticket.meta.get('apply_application_group', []) + apply_system_user_group = self.ticket.meta.get('apply_system_user_group', []) + apply_date_start = self.ticket.meta.get('apply_date_start') + apply_date_expired = self.ticket.meta.get('apply_date_expired') applied_body = '''{}: {}, {}: {}, {}: {}, @@ -64,18 +69,18 @@ class ConstructBodyMixin: ) return applied_body - def construct_apply_application_approved_body(self): + def _construct_meta_body_of_approve(self): # 审批信息 - approve_applications_snapshot = self.meta.get('approve_applications_snapshot', []) - approve_applications_snapshot_display = convert_model_instance_data_field_name_to_verbose_name( - Application, approve_applications_snapshot + approve_applications_snapshot = self.ticket.meta.get('approve_applications_snapshot', []) + approve_applications_snapshot_display = convert_model_data_field_name_to_verbose_name( + model=Application, data=approve_applications_snapshot ) - approve_system_users_snapshot = self.meta.get('approve_system_users_snapshot', []) - approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name( - SystemUser, approve_system_users_snapshot + approve_system_users_snapshot = self.ticket.meta.get('approve_system_users_snapshot', []) + approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name( + model=SystemUser, data=approve_system_users_snapshot ) - approve_date_start = self.meta.get('approve_date_start') - approve_date_expired = self.meta.get('approve_date_expired') + approve_date_start = self.ticket.meta.get('approve_date_start') + approve_date_expired = self.ticket.meta.get('approve_date_expired') approved_body = '''{}: {}, {}: {}, {}: {}, @@ -88,23 +93,21 @@ class ConstructBodyMixin: ) return approved_body - -class CreatePermissionMixin: - - def create_apply_application_permission(self): + # permission + def _create_application_permission(self): with tmp_to_root_org(): - application_permission = ApplicationPermission.objects.filter(id=self.id).first() + application_permission = ApplicationPermission.objects.filter(id=self.ticket.id).first() if application_permission: return application_permission - apply_category = self.meta.get('apply_category') - apply_type = self.meta.get('apply_type') - approved_applications_id = self.meta.get('approve_applications', []) - approve_system_users_id = self.meta.get('approve_system_users', []) - approve_date_start = self.meta.get('approve_date_start') - approve_date_expired = self.meta.get('approve_date_expired') + apply_category = self.ticket.meta.get('apply_category') + apply_type = self.ticket.meta.get('apply_type') + approved_applications_id = self.ticket.meta.get('approve_applications', []) + approve_system_users_id = self.ticket.meta.get('approve_system_users', []) + approve_date_start = self.ticket.meta.get('approve_date_start') + approve_date_expired = self.ticket.meta.get('approve_date_expired') permission_name = '{}({})'.format( - __('Created by ticket ({})'.format(self.title)), str(self.id)[:4] + __('Created by ticket ({})'.format(self.ticket.title)), str(self.ticket.id)[:4] ) permission_comment = __( 'Created by the ticket, ' @@ -112,21 +115,24 @@ class CreatePermissionMixin: 'ticket applicant: {}, ' 'ticket processor: {}, ' 'ticket ID: {}' - ''.format(self.title, self.applicant_display, self.processor_display, str(self.id)) + ''.format( + self.ticket.title, self.ticket.applicant_display, + self.ticket.processor_display, str(self.ticket.id) + ) ) permissions_data = { - 'id': self.id, + 'id': self.ticket.id, 'name': permission_name, 'category': apply_category, 'type': apply_type, 'comment': permission_comment, - 'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.id)), + 'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.ticket.id)), 'date_start': approve_date_start, 'date_expired': approve_date_expired, } - with tmp_to_org(self.org_id): + with tmp_to_org(self.ticket.org_id): application_permission = ApplicationPermission.objects.create(**permissions_data) - application_permission.users.add(self.applicant) + application_permission.users.add(self.ticket.applicant) application_permission.applications.set(approved_applications_id) application_permission.system_users.set(approve_system_users_id) diff --git a/apps/tickets/models/ticket/mixin/meta/apply_asset.py b/apps/tickets/handler/apply_asset.py similarity index 55% rename from apps/tickets/models/ticket/mixin/meta/apply_asset.py rename to apps/tickets/handler/apply_asset.py index eeae032e9..d40c635b9 100644 --- a/apps/tickets/models/ticket/mixin/meta/apply_asset.py +++ b/apps/tickets/handler/apply_asset.py @@ -1,31 +1,36 @@ +from .base import BaseHandler 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_instance_data_field_name_to_verbose_name +from tickets.utils import convert_model_data_field_name_to_verbose_name -class ConstructDisplayFieldMixin: - def construct_meta_apply_asset_open_fields_display(self): +class Handler(BaseHandler): + + def _on_approve(self): + super()._on_approve() + self._create_asset_permission() + + # display + def _construct_meta_display_of_open(self): meta_display_fields = ['apply_actions_display'] - - apply_actions = self.meta.get('apply_actions', Action.NONE) + apply_actions = self.ticket.meta.get('apply_actions', Action.NONE) 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): + def _construct_meta_display_of_approve(self): meta_display_fields = [ 'approve_actions_display', 'approve_assets_snapshot', 'approve_system_users_snapshot' ] - approve_actions = self.meta.get('approve_actions', Action.NONE) + approve_actions = self.ticket.meta.get('approve_actions', Action.NONE) approve_actions_display = Action.value_to_choices_display(approve_actions) - approve_assets_id = self.meta.get('approve_assets', []) - approve_system_users_id = self.meta.get('approve_system_users', []) - with tmp_to_org(self.org_id): + approve_assets_id = self.ticket.meta.get('approve_assets', []) + approve_system_users_id = self.ticket.meta.get('approve_system_users', []) + with tmp_to_org(self.ticket.org_id): approve_assets_snapshot = list( Asset.objects.filter(id__in=approve_assets_id).values( 'hostname', 'ip', 'protocols', 'platform__name', 'public_ip' @@ -43,15 +48,14 @@ class ConstructDisplayFieldMixin: meta_display = dict(zip(meta_display_fields, meta_display_values)) return meta_display - -class ConstructBodyMixin: - def construct_apply_asset_applied_body(self): - apply_ip_group = self.meta.get('apply_ip_group', []) - apply_hostname_group = self.meta.get('apply_hostname_group', []) - apply_system_user_group = self.meta.get('apply_system_user_group', []) - apply_actions_display = self.meta.get('apply_actions_display', []) - apply_date_start = self.meta.get('apply_date_start') - apply_date_expired = self.meta.get('apply_date_expired') + # body + def _construct_meta_body_of_open(self): + apply_ip_group = self.ticket.meta.get('apply_ip_group', []) + apply_hostname_group = self.ticket.meta.get('apply_hostname_group', []) + apply_system_user_group = self.ticket.meta.get('apply_system_user_group', []) + apply_actions_display = self.ticket.meta.get('apply_actions_display', []) + apply_date_start = self.ticket.meta.get('apply_date_start') + apply_date_expired = self.ticket.meta.get('apply_date_expired') applied_body = '''{}: {}, {}: {}, {}: {}, @@ -67,18 +71,18 @@ class ConstructBodyMixin: ) return applied_body - def construct_apply_asset_approved_body(self): - approve_assets_snapshot = self.meta.get('approve_assets_snapshot', []) - approve_assets_snapshot_display = convert_model_instance_data_field_name_to_verbose_name( - Asset, approve_assets_snapshot + def _construct_meta_body_of_approve(self): + approve_assets_snapshot = self.ticket.meta.get('approve_assets_snapshot', []) + approve_assets_snapshot_display = convert_model_data_field_name_to_verbose_name( + model=Asset, data=approve_assets_snapshot ) - approve_system_users_snapshot = self.meta.get('approve_system_users_snapshot', []) - approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name( - SystemUser, approve_system_users_snapshot + approve_system_users_snapshot = self.ticket.meta.get('approve_system_users_snapshot', []) + approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name( + model=SystemUser, data=approve_system_users_snapshot ) - approve_actions_display = self.meta.get('approve_actions_display', []) - approve_date_start = self.meta.get('approve_date_start') - approve_date_expired = self.meta.get('approve_date_expired') + approve_actions_display = self.ticket.meta.get('approve_actions_display', []) + approve_date_start = self.ticket.meta.get('approve_date_start') + approve_date_expired = self.ticket.meta.get('approve_date_expired') approved_body = '''{}: {}, {}: {}, {}: {}, @@ -93,21 +97,20 @@ class ConstructBodyMixin: ) return approved_body - -class CreatePermissionMixin: - def create_apply_asset_permission(self): + # permission + def _create_asset_permission(self): with tmp_to_root_org(): - asset_permission = AssetPermission.objects.filter(id=self.id).first() + asset_permission = AssetPermission.objects.filter(id=self.ticket.id).first() if asset_permission: return asset_permission - approve_assets_id = self.meta.get('approve_assets', []) - approve_system_users_id = self.meta.get('approve_system_users', []) - approve_actions = self.meta.get('approve_actions', Action.NONE) - approve_date_start = self.meta.get('approve_date_start') - approve_date_expired = self.meta.get('approve_date_expired') + approve_assets_id = self.ticket.meta.get('approve_assets', []) + approve_system_users_id = self.ticket.meta.get('approve_system_users', []) + approve_actions = self.ticket.meta.get('approve_actions', Action.NONE) + approve_date_start = self.ticket.meta.get('approve_date_start') + approve_date_expired = self.ticket.meta.get('approve_date_expired') permission_name = '{}({})'.format( - __('Created by ticket ({})'.format(self.title)), str(self.id)[:4] + __('Created by ticket ({})'.format(self.ticket.title)), str(self.ticket.id)[:4] ) permission_comment = __( 'Created by the ticket, ' @@ -115,20 +118,23 @@ class CreatePermissionMixin: 'ticket applicant: {}, ' 'ticket processor: {}, ' 'ticket ID: {}' - ''.format(self.title, self.applicant_display, self.processor_display, str(self.id)) + ''.format( + self.ticket.title, self.ticket.applicant_display, self.ticket.processor_display, + str(self.ticket.id) + ) ) permission_data = { - 'id': self.id, + 'id': self.ticket.id, 'name': permission_name, 'comment': permission_comment, - 'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.id)), + 'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.ticket.id)), 'actions': approve_actions, 'date_start': approve_date_start, 'date_expired': approve_date_expired, } - with tmp_to_org(self.org_id): + with tmp_to_org(self.ticket.org_id): asset_permission = AssetPermission.objects.create(**permission_data) - asset_permission.users.add(self.applicant) + asset_permission.users.add(self.ticket.applicant) asset_permission.assets.set(approve_assets_id) asset_permission.system_users.set(approve_system_users_id) diff --git a/apps/tickets/handler/base.py b/apps/tickets/handler/base.py new file mode 100644 index 000000000..e1820dc0b --- /dev/null +++ b/apps/tickets/handler/base.py @@ -0,0 +1,122 @@ +from django.utils.translation import ugettext as __ +from common.utils import get_logger +from tickets.utils import send_ticket_processed_mail_to_applicant + + +logger = get_logger(__name__) + + +class BaseHandler(object): + + def __init__(self, ticket): + self.ticket = ticket + + # on action + def _on_open(self): + self.ticket.applicant_display = str(self.ticket.applicant) + meta_display = getattr(self, '_construct_meta_display_of_open', lambda: {})() + self.ticket.meta.update(meta_display) + self.ticket.save() + + def _on_approve(self): + meta_display = getattr(self, '_construct_meta_display_of_approve', lambda: {})() + self.ticket.meta.update(meta_display) + self.__on_process() + + def _on_reject(self): + self.__on_process() + + def _on_close(self): + self.__on_process() + + def __on_process(self): + self.ticket.processor_display = str(self.ticket.processor) + self.ticket.set_status_closed() + self._send_processed_mail_to_applicant() + self.ticket.save() + + def dispatch(self, action): + self._create_comment_on_action() + method = getattr(self, f'_on_{action}', lambda: None) + return method() + + # email + def _send_processed_mail_to_applicant(self): + msg = 'Ticket ({}) has processed, send mail to applicant ({})'.format( + self.ticket.title, self.ticket.applicant_display + ) + logger.debug(msg) + send_ticket_processed_mail_to_applicant(self.ticket) + + # comments + def _create_comment_on_action(self): + user = self.ticket.applicant if self.ticket.action_open else self.ticket.processor + user_display = str(user) + action_display = self.ticket.get_action_display() + data = { + 'body': __('User {} {} the ticket'.format(user_display, action_display)), + 'user': user, + 'user_display': user_display + } + return self.ticket.comments.create(**data) + + # body + def get_body(self): + old_body = self.ticket.meta.get('body') + if old_body: + # 之前版本的body + return old_body + basic_body = self._construct_basic_body() + meta_body = self._construct_meta_body() + return basic_body + meta_body + + def _construct_basic_body(self): + body = ''' + {}: + {}: {}, + {}: {}, + {}: {}, + {}: {}, + {}: {}, + {}: {}, + {}: {} + '''.format( + __("Ticket basic info"), + __('Ticket title'), self.ticket.title, + __('Ticket type'), self.ticket.get_type_display(), + __('Ticket applicant'), self.ticket.applicant_display, + __('Ticket assignees'), self.ticket.assignees_display, + __('Ticket processor'), self.ticket.processor_display, + __('Ticket action'), self.ticket.get_action_display(), + __('Ticket status'), self.ticket.get_status_display() + ) + return body + + def _construct_meta_body(self): + body = '' + open_body = self._base_construct_meta_body_of_open() + body += open_body + if self.ticket.action_approve: + approve_body = self._base_construct_meta_body_of_approve() + body += approve_body + return body + + def _base_construct_meta_body_of_open(self): + open_body = ''' + {}: + {} + '''.format( + __('Ticket applied info'), + getattr(self, '_construct_meta_body_of_open', lambda: 'No')() + ) + return open_body + + def _base_construct_meta_body_of_approve(self): + approve_body = ''' + {}: + {} + '''.format( + __('Ticket approved info'), + getattr(self, '_construct_meta_body_of_approve', lambda: 'No')() + ) + return approve_body diff --git a/apps/tickets/handler/general.py b/apps/tickets/handler/general.py new file mode 100644 index 000000000..e4df867a3 --- /dev/null +++ b/apps/tickets/handler/general.py @@ -0,0 +1,5 @@ +from .base import BaseHandler + + +class Handler(BaseHandler): + pass diff --git a/apps/tickets/models/ticket/mixin/meta/login_confirm.py b/apps/tickets/handler/login_confirm.py similarity index 52% rename from apps/tickets/models/ticket/mixin/meta/login_confirm.py rename to apps/tickets/handler/login_confirm.py index 681739d26..21419022f 100644 --- a/apps/tickets/models/ticket/mixin/meta/login_confirm.py +++ b/apps/tickets/handler/login_confirm.py @@ -1,12 +1,14 @@ from django.utils.translation import ugettext as __ +from .base import BaseHandler -class ConstructBodyMixin: - - def construct_login_confirm_applied_body(self): - apply_login_ip = self.meta.get('apply_login_ip') - apply_login_city = self.meta.get('apply_login_city') - apply_login_datetime = self.meta.get('apply_login_datetime') +class Handler(BaseHandler): + + # body + def _construct_meta_body_of_open(self): + apply_login_ip = self.ticket.meta.get('apply_login_ip') + apply_login_city = self.ticket.meta.get('apply_login_city') + apply_login_datetime = self.ticket.meta.get('apply_login_datetime') applied_body = '''{}: {}, {}: {}, {}: {} diff --git a/apps/tickets/migrations/0007_auto_20201224_1821.py b/apps/tickets/migrations/0007_auto_20201224_1821.py index cb55b2fd1..a0ffcd3a0 100644 --- a/apps/tickets/migrations/0007_auto_20201224_1821.py +++ b/apps/tickets/migrations/0007_auto_20201224_1821.py @@ -101,7 +101,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='ticket', name='applicant_display', - field=models.CharField(default='No', max_length=256, verbose_name='Applicant display'), + field=models.CharField(default='', max_length=256, verbose_name='Applicant display'), ), migrations.AddField( model_name='ticket', @@ -111,12 +111,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='ticket', name='processor_display', - field=models.CharField(blank=True, default='No', max_length=256, null=True, verbose_name='Processor display'), + field=models.CharField(blank=True, default='', max_length=256, null=True, verbose_name='Processor display'), ), migrations.AddField( model_name='ticket', name='assignees_display_new', - field=models.JSONField(default=list, encoder=tickets.models.ticket.model.ModelJSONFieldEncoder, verbose_name='Assignees display'), + field=models.JSONField(default=list, encoder=tickets.models.ticket.ModelJSONFieldEncoder, verbose_name='Assignees display'), ), migrations.AlterField( model_name='ticket', @@ -126,7 +126,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='ticket', name='meta', - field=models.JSONField(default=dict, encoder=tickets.models.ticket.model.ModelJSONFieldEncoder, verbose_name='Meta'), + field=models.JSONField(default=dict, encoder=tickets.models.ticket.ModelJSONFieldEncoder, verbose_name='Meta'), ), migrations.AlterField( model_name='ticket', diff --git a/apps/tickets/models/ticket/model.py b/apps/tickets/models/ticket.py similarity index 64% rename from apps/tickets/models/ticket/model.py rename to apps/tickets/models/ticket.py index 2defd41ee..97d99e4b3 100644 --- a/apps/tickets/models/ticket/model.py +++ b/apps/tickets/models/ticket.py @@ -11,8 +11,9 @@ from django.conf import settings from common.mixins.models import CommonModelMixin from orgs.mixins.models import OrgModelMixin from orgs.utils import tmp_to_root_org, tmp_to_org -from tickets import const -from .mixin import TicketModelMixin +from tickets.const import TicketTypeChoices, TicketActionChoices, TicketStatusChoices +from tickets.signals import post_change_ticket_action +from tickets.handler import get_ticket_handler __all__ = ['Ticket', 'ModelJSONFieldEncoder'] @@ -30,20 +31,20 @@ class ModelJSONFieldEncoder(json.JSONEncoder): return super().default(obj) -class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin): +class Ticket(CommonModelMixin, OrgModelMixin): title = models.CharField(max_length=256, verbose_name=_("Title")) type = models.CharField( - max_length=64, choices=const.TicketTypeChoices.choices, - default=const.TicketTypeChoices.general.value, verbose_name=_("Type") + max_length=64, choices=TicketTypeChoices.choices, + default=TicketTypeChoices.general.value, verbose_name=_("Type") ) meta = models.JSONField(encoder=ModelJSONFieldEncoder, default=dict, verbose_name=_("Meta")) action = models.CharField( - choices=const.TicketActionChoices.choices, max_length=16, - default=const.TicketActionChoices.open.value, verbose_name=_("Action") + choices=TicketActionChoices.choices, max_length=16, + default=TicketActionChoices.open.value, verbose_name=_("Action") ) status = models.CharField( - max_length=16, choices=const.TicketStatusChoices.choices, - default=const.TicketStatusChoices.open.value, verbose_name=_("Status") + max_length=16, choices=TicketStatusChoices.choices, + default=TicketStatusChoices.open.value, verbose_name=_("Status") ) # 申请人 applicant = models.ForeignKey( @@ -51,7 +52,7 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin): verbose_name=_("Applicant") ) applicant_display = models.CharField( - max_length=256, default='No', verbose_name=_("Applicant display") + max_length=256, default='', verbose_name=_("Applicant display") ) # 处理人 processor = models.ForeignKey( @@ -59,7 +60,7 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin): verbose_name=_("Processor") ) processor_display = models.CharField( - max_length=256, blank=True, null=True, default='No', verbose_name=_("Processor display") + max_length=256, blank=True, null=True, default='', verbose_name=_("Processor display") ) # 受理人列表 assignees = models.ManyToManyField( @@ -80,70 +81,71 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin): # type @property def type_apply_asset(self): - return self.type == const.TicketTypeChoices.apply_asset.value + return self.type == TicketTypeChoices.apply_asset.value @property def type_apply_application(self): - return self.type == const.TicketTypeChoices.apply_application.value + return self.type == TicketTypeChoices.apply_application.value @property def type_login_confirm(self): - return self.type == const.TicketTypeChoices.login_confirm.value + return self.type == TicketTypeChoices.login_confirm.value # status @property def status_closed(self): - return self.status == const.TicketStatusChoices.closed.value + return self.status == TicketStatusChoices.closed.value @property def status_open(self): - return self.status == const.TicketStatusChoices.open.value + return self.status == TicketStatusChoices.open.value def set_status_closed(self): - self.status = const.TicketStatusChoices.closed.value + self.status = TicketStatusChoices.closed.value # action @property def action_open(self): - return self.action == const.TicketActionChoices.open.value + return self.action == TicketActionChoices.open.value @property def action_approve(self): - return self.action == const.TicketActionChoices.approve.value + return self.action == TicketActionChoices.approve.value @property def action_reject(self): - return self.action == const.TicketActionChoices.reject.value + return self.action == TicketActionChoices.reject.value @property def action_close(self): - return self.action == const.TicketActionChoices.close.value + return self.action == TicketActionChoices.close.value - @property - def has_applied(self): - return self.action_open + # action changed + def open(self, applicant): + self.applicant = applicant + self._change_action(action=TicketActionChoices.open.value) - @property - def has_processed(self): - return self.action_approve or self.action_reject or self.action_close + def approve(self, processor): + self.processor = processor + self._change_action(action=TicketActionChoices.approve.value) - def set_action_close(self): - self.action = const.TicketActionChoices.close.value + def reject(self, processor): + self.processor = processor + self._change_action(action=TicketActionChoices.reject.value) def close(self, processor): self.processor = processor - self.set_action_close() + self._change_action(action=TicketActionChoices.close.value) + + def _change_action(self, action): + self.action = action self.save() + post_change_ticket_action.send(sender=self.__class__, ticket=self, action=action) # ticket def has_assignee(self, assignee): return self.assignees.filter(id=assignee.id).exists() - @classmethod - def all(cls): - with tmp_to_root_org(): - return Ticket.objects.all() - @classmethod def get_user_related_tickets(cls, user): queries = None @@ -176,7 +178,22 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin): tickets = tickets.filter(queries) return tickets.distinct() + @classmethod + def all(cls): + with tmp_to_root_org(): + return Ticket.objects.all() + def save(self, *args, **kwargs): + """ 确保保存的org_id的是自身的值 """ with tmp_to_org(self.org_id): - # 确保保存的org_id的是自身的值 return super().save(*args, **kwargs) + + @property + def handler(self): + return get_ticket_handler(ticket=self) + + # body + @property + def body(self): + _body = self.handler.get_body() + return _body diff --git a/apps/tickets/models/ticket/__init__.py b/apps/tickets/models/ticket/__init__.py deleted file mode 100644 index 87cb7367b..000000000 --- a/apps/tickets/models/ticket/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .model import * diff --git a/apps/tickets/models/ticket/mixin/__init__.py b/apps/tickets/models/ticket/mixin/__init__.py deleted file mode 100644 index d3a9538d1..000000000 --- a/apps/tickets/models/ticket/mixin/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .ticket import TicketModelMixin diff --git a/apps/tickets/models/ticket/mixin/base.py b/apps/tickets/models/ticket/mixin/base.py deleted file mode 100644 index 5a00d018c..000000000 --- a/apps/tickets/models/ticket/mixin/base.py +++ /dev/null @@ -1,134 +0,0 @@ -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: - self.assignees_display = [str(assignee) for assignee in self.assignees.all()] - - 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): - construct_method = getattr(self, f'construct_{self.type}_applied_body', lambda: 'No') - applied_body = construct_method() - body = ''' - {}: - {} - '''.format( - __('Ticket applied info'), - applied_body - ) - return body - - # approved body - def construct_approved_body(self): - construct_method = getattr(self, f'construct_{self.type}_approved_body', lambda: 'No') - approved_body = construct_method() - body = ''' - {}: - {} - '''.format( - __('Ticket approved info'), - approved_body - ) - return body - - # meta body - def construct_meta_body(self): - applied_body = self.construct_applied_body() - if not self.action_approve: - return applied_body - approved_body = self.construct_approved_body() - return applied_body + approved_body - - # basic body - def construct_basic_body(self): - basic_body = ''' - {}: - {}: {}, - {}: {}, - {}: {}, - {}: {}, - {}: {}, - {}: {}, - {}: {} - '''.format( - __("Ticket basic info"), - __('Ticket title'), self.title, - __('Ticket type'), self.get_type_display(), - __('Ticket applicant'), self.applicant_display, - __('Ticket assignees'), self.assignees_display, - __('Ticket processor'), self.processor_display, - __('Ticket action'), self.get_action_display(), - __('Ticket status'), self.get_status_display() - ) - return basic_body - - @property - def body(self): - old_body = self.meta.get('body') - if old_body: - # 之前版本的body - return old_body - basic_body = self.construct_basic_body() - meta_body = self.construct_meta_body() - return basic_body + meta_body - - -class CreatePermissionMixin: - # create permission - def create_permission(self): - create_method = getattr(self, f'create_{self.type}_permission', lambda: None) - permission = create_method() - return permission - - -class CreateCommentMixin: - def create_comment(self, comment_body): - # 页面展示需要取消缩进 - comment_body = textwrap.dedent(comment_body) - comment_data = { - 'body': comment_body, - 'user': self.processor, - 'user_display': self.processor_display - } - return self.comments.create(**comment_data) - - def create_applied_comment(self): - comment_body = self.construct_applied_body() - self.create_comment(comment_body) - - def create_approved_comment(self): - comment_body = self.construct_approved_body() - self.create_comment(comment_body) - - def create_action_comment(self): - if self.has_applied: - user_display = self.applicant_display - if self.has_processed: - user_display = self.processor_display - comment_body = __( - 'User {} {} the ticket'.format(user_display, self.get_action_display()) - ) - self.create_comment(comment_body) diff --git a/apps/tickets/models/ticket/mixin/meta/__init__.py b/apps/tickets/models/ticket/mixin/meta/__init__.py deleted file mode 100644 index 9b5ed21c9..000000000 --- a/apps/tickets/models/ticket/mixin/meta/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .base import * diff --git a/apps/tickets/models/ticket/mixin/meta/base.py b/apps/tickets/models/ticket/mixin/meta/base.py deleted file mode 100644 index 5dddb950a..000000000 --- a/apps/tickets/models/ticket/mixin/meta/base.py +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100644 index 055a80809..000000000 --- a/apps/tickets/models/ticket/mixin/ticket.py +++ /dev/null @@ -1,30 +0,0 @@ -from . import base, meta - -__all__ = ['TicketModelMixin'] - - -class TicketSetDisplayFieldMixin(meta.ConstructDisplayFieldMixin, base.SetDisplayFieldMixin): - """ 设置 ticket display 字段(只设置,不保存)""" - pass - - -class TicketConstructBodyMixin(meta.ConstructBodyMixin, base.ConstructBodyMixin): - """ 构造 ticket body 信息 """ - pass - - -class TicketCreatePermissionMixin(meta.CreatePermissionMixin, base.CreatePermissionMixin): - """ 创建 ticket 相关授权规则""" - pass - - -class TicketCreateCommentMixin(base.CreateCommentMixin): - """ 创建 ticket 备注""" - pass - - -class TicketModelMixin( - TicketSetDisplayFieldMixin, TicketConstructBodyMixin, TicketCreatePermissionMixin, - TicketCreateCommentMixin -): - pass diff --git a/apps/tickets/serializers/ticket/meta/ticket_type/apply_asset.py b/apps/tickets/serializers/ticket/meta/ticket_type/apply_asset.py index 4d2104ff4..32f880e0c 100644 --- a/apps/tickets/serializers/ticket/meta/ticket_type/apply_asset.py +++ b/apps/tickets/serializers/ticket/meta/ticket_type/apply_asset.py @@ -103,7 +103,7 @@ class ApproveSerializer(serializers.Serializer): return system_users_id raise serializers.ValidationError(_( - 'No `Asset` are found under Organization `{}`'.format(self.root.instance.org_name) + 'No `SystemUser` are found under Organization `{}`'.format(self.root.instance.org_name) )) diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index 521d8dc3e..b6ffc5140 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -2,19 +2,16 @@ # from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.drf.fields import ReadableHiddenField from common.drf.serializers import MethodSerializer from orgs.utils import get_org_by_id from orgs.mixins.serializers import OrgResourceModelSerializerMixin from users.models import User -from tickets import const from tickets.models import Ticket from .meta import type_serializer_classes_mapping + __all__ = [ - 'TicketSerializer', 'TicketDisplaySerializer', - 'TicketApplySerializer', 'TicketApproveSerializer', - 'TicketRejectSerializer', 'TicketCloseSerializer', + 'TicketDisplaySerializer', 'TicketApplySerializer', 'TicketApproveSerializer', ] @@ -22,9 +19,6 @@ class TicketSerializer(OrgResourceModelSerializerMixin): type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type')) action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status')) - action = ReadableHiddenField(default=const.TicketActionChoices.open.value) - applicant = ReadableHiddenField(default=serializers.CurrentUserDefault()) - processor = ReadableHiddenField(default=serializers.CurrentUserDefault()) meta = MethodSerializer() class Meta: @@ -72,24 +66,23 @@ class TicketDisplaySerializer(TicketSerializer): class Meta: model = Ticket fields = TicketSerializer.Meta.fields - read_only_fields = TicketSerializer.Meta.fields + read_only_fields = fields class TicketApplySerializer(TicketSerializer): org_id = serializers.CharField( - max_length=36, allow_blank=True, required=True, label=_("Organization") + required=True, max_length=36, allow_blank=True, label=_("Organization") ) class Meta: model = Ticket fields = TicketSerializer.Meta.fields - required_fields = [ - 'id', 'title', 'type', 'applicant', 'action', 'meta', 'assignees', 'comment', 'org_id' + writeable_fields = [ + 'id', 'title', 'type', 'meta', 'assignees', 'comment', 'org_id' ] - read_only_fields = list(set(fields) - set(required_fields)) + read_only_fields = list(set(fields) - set(writeable_fields)) extra_kwargs = { 'type': {'required': True}, - 'org_id': {'required': True}, } def validate_type(self, tp): @@ -121,49 +114,16 @@ class TicketApplySerializer(TicketSerializer): raise serializers.ValidationError(error) return valid_assignees - def validate_action(self, action): - return const.TicketActionChoices.open.value - class TicketApproveSerializer(TicketSerializer): class Meta: model = Ticket fields = TicketSerializer.Meta.fields - required_fields = ['processor', 'action', 'meta'] - read_only_fields = list(set(fields) - set(required_fields)) + writeable_fields = ['meta'] + read_only_fields = list(set(fields) - set(writeable_fields)) def validate_meta(self, meta): _meta = self.instance.meta if self.instance else {} _meta.update(meta) return _meta - - @staticmethod - def validate_action(action): - return const.TicketActionChoices.approve.value - - -class TicketRejectSerializer(TicketSerializer): - meta = MethodSerializer(read_only=True) - - class Meta: - model = Ticket - fields = TicketSerializer.Meta.fields - read_only_fields = fields - - def validate_action(self, action): - return const.TicketActionChoices.reject.value - - -class TicketCloseSerializer(TicketSerializer): - meta = MethodSerializer(read_only=True) - - class Meta: - model = Ticket - fields = TicketSerializer.Meta.fields - read_only_fields = fields - - def validate_action(self, action): - return const.TicketActionChoices.close.value - - diff --git a/apps/tickets/signals.py b/apps/tickets/signals.py new file mode 100644 index 000000000..10951716d --- /dev/null +++ b/apps/tickets/signals.py @@ -0,0 +1,4 @@ +from django.dispatch import Signal + + +post_change_ticket_action = Signal() diff --git a/apps/tickets/signals_handler.py b/apps/tickets/signals_handler.py deleted file mode 100644 index 0afd2a587..000000000 --- a/apps/tickets/signals_handler.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.dispatch import receiver -from django.db.models.signals import m2m_changed, post_save, pre_save - -from common.utils import get_logger -from .models import Ticket, Comment -from .utils import ( - send_ticket_applied_mail_to_assignees, - send_ticket_processed_mail_to_applicant -) -from . import const - - -logger = get_logger(__name__) - - -@receiver(pre_save, sender=Ticket) -def on_ticket_pre_save(sender, instance=None, **kwargs): - instance.set_display_fields() - - if instance.has_processed: - instance.set_status_closed() - - -@receiver(post_save, sender=Ticket) -def on_ticket_post_save(sender, instance=None, created=False, **kwargs): - - if created and instance.action_open: - instance.create_action_comment() - instance.create_applied_comment() - - if not created and instance.has_processed: - instance.create_action_comment() - msg = 'Ticket () has processed, send mail to applicant: {}' - logger.debug(msg.format(instance.title, instance.applicant_display)) - send_ticket_processed_mail_to_applicant(instance) - if instance.action_approve: - instance.create_permission() - instance.create_approved_comment() - - -@receiver(m2m_changed, sender=Ticket.assignees.through) -def on_ticket_assignees_changed(sender, instance=None, action=None, reverse=False, model=None, pk_set=None, **kwargs): - if reverse: - return - if action != 'post_add': - return - ticket = instance - logger.debug('Receives ticket and assignees changed signal, ticket: {}'.format(ticket.title)) - ticket.set_assignees_display() - ticket.save() - 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.set_display_fields() diff --git a/apps/tickets/signals_handler/__init__.py b/apps/tickets/signals_handler/__init__.py new file mode 100644 index 000000000..ec0439b54 --- /dev/null +++ b/apps/tickets/signals_handler/__init__.py @@ -0,0 +1,2 @@ +from .ticket import * +from .comment import * diff --git a/apps/tickets/signals_handler/comment.py b/apps/tickets/signals_handler/comment.py new file mode 100644 index 000000000..a308ae973 --- /dev/null +++ b/apps/tickets/signals_handler/comment.py @@ -0,0 +1,9 @@ +from django.db.models.signals import pre_save +from django.dispatch import receiver + +from ..models import Comment + + +@receiver(pre_save, sender=Comment) +def on_comment_pre_save(sender, instance, **kwargs): + instance.set_display_fields() diff --git a/apps/tickets/signals_handler/ticket.py b/apps/tickets/signals_handler/ticket.py new file mode 100644 index 000000000..7e8bc8d5d --- /dev/null +++ b/apps/tickets/signals_handler/ticket.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +from django.dispatch import receiver +from django.db.models.signals import m2m_changed + +from common.utils import get_logger +from tickets.models import Ticket +from tickets.utils import send_ticket_applied_mail_to_assignees +from ..signals import post_change_ticket_action + + +logger = get_logger(__name__) + + +@receiver(post_change_ticket_action, sender=Ticket) +def on_post_change_ticket_action(sender, ticket, action, **kwargs): + ticket.handler.dispatch(action) + + +@receiver(m2m_changed, sender=Ticket.assignees.through) +def on_ticket_assignees_changed(sender, instance, action, reverse, model, pk_set, **kwargs): + if reverse: + return + if action != 'post_add': + return + logger.debug('Receives ticket and assignees changed signal, ticket: {}'.format(instance.title)) + instance.assignees_display = [str(assignee) for assignee in instance.assignees.all()] + instance.save() + 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(instance, assignees) + diff --git a/apps/tickets/utils.py b/apps/tickets/utils.py index a3070404d..9c626ef5a 100644 --- a/apps/tickets/utils.py +++ b/apps/tickets/utils.py @@ -20,7 +20,7 @@ def get_model_field_verbose_name(model, field_name): return field_verbose_name -def convert_model_instance_data_field_name_to_verbose_name(model, data): +def convert_model_data_field_name_to_verbose_name(model, data): if isinstance(data, dict): data = [data] converted_data = [