mirror of https://github.com/jumpserver/jumpserver
perf: 重构工单处理流程 (#5408)
* perf: 重构工单处理流程 * perf: 重构工单处理流程 (1) * perf: 重构工单处理流程 (2) * perf: 重构工单处理流程 (3) * perf: 重构工单处理流程 (4) * perf: 重构工单处理流程 (5) * perf: 重构工单处理流程 (6) Co-authored-by: Bai <bugatti_it@163.com>pull/5415/head
parent
528e251f31
commit
a7468a243d
|
@ -207,12 +207,13 @@ class SystemUser(BaseUser):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_protocol_by_application_type(cls, app_type):
|
def get_protocol_by_application_type(cls, app_type):
|
||||||
from applications.const import ApplicationTypeChoices
|
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
|
protocol = cls.PROTOCOL_RDP
|
||||||
else:
|
else:
|
||||||
protocol = app_type
|
protocol = None
|
||||||
if protocol in cls.APPLICATION_CATEGORY_PROTOCOLS:
|
return protocol
|
||||||
return protocol
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
|
@ -254,152 +254,3 @@ def get_disk_usage():
|
||||||
mount_points = [p.mountpoint for p in partitions]
|
mount_points = [p.mountpoint for p in partitions]
|
||||||
usages = {p: psutil.disk_usage(p) for p in mount_points}
|
usages = {p: psutil.disk_usage(p) for p in mount_points}
|
||||||
return usages
|
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -18,7 +18,7 @@ class CommandStorage(CommonModelMixin):
|
||||||
default=const.CommandStorageTypeChoices.server.value, verbose_name=_('Type'),
|
default=const.CommandStorageTypeChoices.server.value, verbose_name=_('Type'),
|
||||||
)
|
)
|
||||||
meta = EncryptJsonDictTextField(default={})
|
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):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -58,7 +58,7 @@ class ReplayStorage(CommonModelMixin):
|
||||||
default=const.ReplayStorageTypeChoices.server.value, verbose_name=_('Type')
|
default=const.ReplayStorageTypeChoices.server.value, verbose_name=_('Type')
|
||||||
)
|
)
|
||||||
meta = EncryptJsonDictTextField(default={})
|
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):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework import viewsets, mixins
|
from rest_framework import viewsets, mixins
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from common.exceptions import JMSException
|
from common.exceptions import JMSException
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from tickets import serializers
|
from tickets import serializers
|
||||||
|
@ -22,11 +21,10 @@ class CommentViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||||
if getattr(self, 'swagger_fake_view', False):
|
if getattr(self, 'swagger_fake_view', False):
|
||||||
return None
|
return None
|
||||||
ticket_id = self.request.query_params.get('ticket_id')
|
ticket_id = self.request.query_params.get('ticket_id')
|
||||||
try:
|
ticket = Ticket.all().filter(pk=ticket_id).first()
|
||||||
ticket = get_object_or_404(Ticket, pk=ticket_id)
|
if not ticket:
|
||||||
return ticket
|
raise JMSException('Not found Ticket object about `id={}`'.format(ticket_id))
|
||||||
except Exception as e:
|
return ticket
|
||||||
raise JMSException(str(e))
|
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
context = super().get_serializer_context()
|
context = super().get_serializer_context()
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import MethodNotAllowed
|
from rest_framework.exceptions import MethodNotAllowed
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.const.http import POST, PUT
|
from common.const.http import POST, PUT
|
||||||
from common.mixins.api import CommonApiMixin
|
from common.mixins.api import CommonApiMixin
|
||||||
|
@ -21,12 +22,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = (IsValidUser,)
|
||||||
serializer_class = serializers.TicketDisplaySerializer
|
serializer_class = serializers.TicketDisplaySerializer
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.TicketDisplaySerializer,
|
|
||||||
'display': serializers.TicketDisplaySerializer,
|
|
||||||
'open': serializers.TicketApplySerializer,
|
'open': serializers.TicketApplySerializer,
|
||||||
'approve': serializers.TicketApproveSerializer,
|
'approve': serializers.TicketApproveSerializer,
|
||||||
'reject': serializers.TicketRejectSerializer,
|
|
||||||
'close': serializers.TicketCloseSerializer,
|
|
||||||
}
|
}
|
||||||
filterset_fields = [
|
filterset_fields = [
|
||||||
'id', 'title', 'type', 'action', 'status', 'applicant', 'applicant_display', 'processor',
|
'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)
|
queryset = Ticket.get_user_related_tickets(self.request.user)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
instance = serializer.save()
|
||||||
|
instance.open(applicant=self.request.user)
|
||||||
|
|
||||||
@action(detail=False, methods=[POST])
|
@action(detail=False, methods=[POST])
|
||||||
def open(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])
|
||||||
def approve(self, request, *args, **kwargs):
|
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])
|
@action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed])
|
||||||
def reject(self, request, *args, **kwargs):
|
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])
|
@action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed])
|
||||||
def close(self, request, *args, **kwargs):
|
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)
|
||||||
|
|
|
@ -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)
|
|
@ -4,25 +4,32 @@ from applications.models import Application
|
||||||
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
|
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
|
||||||
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_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:
|
class Handler(BaseHandler):
|
||||||
def construct_meta_apply_application_open_fields_display(self):
|
|
||||||
|
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']
|
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_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)
|
apply_type_display = ApplicationTypeChoices.get_label(apply_type)
|
||||||
meta_display_values = [apply_category_display, apply_type_display]
|
meta_display_values = [apply_category_display, apply_type_display]
|
||||||
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
||||||
return meta_display
|
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']
|
meta_display_fields = ['approve_applications_snapshot', 'approve_system_users_snapshot']
|
||||||
approve_applications_id = self.meta.get('approve_applications', [])
|
approve_applications_id = self.ticket.meta.get('approve_applications', [])
|
||||||
approve_system_users_id = self.meta.get('approve_system_users', [])
|
approve_system_users_id = self.ticket.meta.get('approve_system_users', [])
|
||||||
with tmp_to_org(self.org_id):
|
with tmp_to_org(self.ticket.org_id):
|
||||||
approve_applications_snapshot = list(
|
approve_applications_snapshot = list(
|
||||||
Application.objects.filter(id__in=approve_applications_id).values(
|
Application.objects.filter(id__in=approve_applications_id).values(
|
||||||
'name', 'category', 'type'
|
'name', 'category', 'type'
|
||||||
|
@ -38,16 +45,14 @@ class ConstructDisplayFieldMixin:
|
||||||
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
||||||
return meta_display
|
return meta_display
|
||||||
|
|
||||||
|
# body
|
||||||
class ConstructBodyMixin:
|
def _construct_meta_body_of_open(self):
|
||||||
|
apply_category_display = self.ticket.meta.get('apply_category_display')
|
||||||
def construct_apply_application_applied_body(self):
|
apply_type_display = self.ticket.meta.get('apply_type_display')
|
||||||
apply_category_display = self.meta.get('apply_category_display')
|
apply_application_group = self.ticket.meta.get('apply_application_group', [])
|
||||||
apply_type_display = self.meta.get('apply_type_display')
|
apply_system_user_group = self.ticket.meta.get('apply_system_user_group', [])
|
||||||
apply_application_group = self.meta.get('apply_application_group', [])
|
apply_date_start = self.ticket.meta.get('apply_date_start')
|
||||||
apply_system_user_group = self.meta.get('apply_system_user_group', [])
|
apply_date_expired = self.ticket.meta.get('apply_date_expired')
|
||||||
apply_date_start = self.meta.get('apply_date_start')
|
|
||||||
apply_date_expired = self.meta.get('apply_date_expired')
|
|
||||||
applied_body = '''{}: {},
|
applied_body = '''{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
|
@ -64,18 +69,18 @@ class ConstructBodyMixin:
|
||||||
)
|
)
|
||||||
return applied_body
|
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 = self.ticket.meta.get('approve_applications_snapshot', [])
|
||||||
approve_applications_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
|
approve_applications_snapshot_display = convert_model_data_field_name_to_verbose_name(
|
||||||
Application, approve_applications_snapshot
|
model=Application, data=approve_applications_snapshot
|
||||||
)
|
)
|
||||||
approve_system_users_snapshot = self.meta.get('approve_system_users_snapshot', [])
|
approve_system_users_snapshot = self.ticket.meta.get('approve_system_users_snapshot', [])
|
||||||
approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
|
approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name(
|
||||||
SystemUser, approve_system_users_snapshot
|
model=SystemUser, data=approve_system_users_snapshot
|
||||||
)
|
)
|
||||||
approve_date_start = self.meta.get('approve_date_start')
|
approve_date_start = self.ticket.meta.get('approve_date_start')
|
||||||
approve_date_expired = self.meta.get('approve_date_expired')
|
approve_date_expired = self.ticket.meta.get('approve_date_expired')
|
||||||
approved_body = '''{}: {},
|
approved_body = '''{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
|
@ -88,23 +93,21 @@ class ConstructBodyMixin:
|
||||||
)
|
)
|
||||||
return approved_body
|
return approved_body
|
||||||
|
|
||||||
|
# permission
|
||||||
class CreatePermissionMixin:
|
def _create_application_permission(self):
|
||||||
|
|
||||||
def create_apply_application_permission(self):
|
|
||||||
with tmp_to_root_org():
|
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:
|
if application_permission:
|
||||||
return application_permission
|
return application_permission
|
||||||
|
|
||||||
apply_category = self.meta.get('apply_category')
|
apply_category = self.ticket.meta.get('apply_category')
|
||||||
apply_type = self.meta.get('apply_type')
|
apply_type = self.ticket.meta.get('apply_type')
|
||||||
approved_applications_id = self.meta.get('approve_applications', [])
|
approved_applications_id = self.ticket.meta.get('approve_applications', [])
|
||||||
approve_system_users_id = self.meta.get('approve_system_users', [])
|
approve_system_users_id = self.ticket.meta.get('approve_system_users', [])
|
||||||
approve_date_start = self.meta.get('approve_date_start')
|
approve_date_start = self.ticket.meta.get('approve_date_start')
|
||||||
approve_date_expired = self.meta.get('approve_date_expired')
|
approve_date_expired = self.ticket.meta.get('approve_date_expired')
|
||||||
permission_name = '{}({})'.format(
|
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 = __(
|
permission_comment = __(
|
||||||
'Created by the ticket, '
|
'Created by the ticket, '
|
||||||
|
@ -112,21 +115,24 @@ class CreatePermissionMixin:
|
||||||
'ticket applicant: {}, '
|
'ticket applicant: {}, '
|
||||||
'ticket processor: {}, '
|
'ticket processor: {}, '
|
||||||
'ticket ID: {}'
|
'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 = {
|
permissions_data = {
|
||||||
'id': self.id,
|
'id': self.ticket.id,
|
||||||
'name': permission_name,
|
'name': permission_name,
|
||||||
'category': apply_category,
|
'category': apply_category,
|
||||||
'type': apply_type,
|
'type': apply_type,
|
||||||
'comment': permission_comment,
|
'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_start': approve_date_start,
|
||||||
'date_expired': approve_date_expired,
|
'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 = 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.applications.set(approved_applications_id)
|
||||||
application_permission.system_users.set(approve_system_users_id)
|
application_permission.system_users.set(approve_system_users_id)
|
||||||
|
|
|
@ -1,31 +1,36 @@
|
||||||
|
from .base import BaseHandler
|
||||||
from django.utils.translation import ugettext as __
|
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_instance_data_field_name_to_verbose_name
|
from tickets.utils import convert_model_data_field_name_to_verbose_name
|
||||||
|
|
||||||
|
|
||||||
class ConstructDisplayFieldMixin:
|
class Handler(BaseHandler):
|
||||||
def construct_meta_apply_asset_open_fields_display(self):
|
|
||||||
|
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']
|
meta_display_fields = ['apply_actions_display']
|
||||||
|
apply_actions = self.ticket.meta.get('apply_actions', Action.NONE)
|
||||||
apply_actions = self.meta.get('apply_actions', Action.NONE)
|
|
||||||
apply_actions_display = Action.value_to_choices_display(apply_actions)
|
apply_actions_display = Action.value_to_choices_display(apply_actions)
|
||||||
|
|
||||||
meta_display_values = [apply_actions_display]
|
meta_display_values = [apply_actions_display]
|
||||||
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
||||||
return meta_display
|
return meta_display
|
||||||
|
|
||||||
def construct_meta_apply_asset_approve_fields_display(self):
|
def _construct_meta_display_of_approve(self):
|
||||||
meta_display_fields = [
|
meta_display_fields = [
|
||||||
'approve_actions_display', 'approve_assets_snapshot', 'approve_system_users_snapshot'
|
'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_actions_display = Action.value_to_choices_display(approve_actions)
|
||||||
approve_assets_id = self.meta.get('approve_assets', [])
|
approve_assets_id = self.ticket.meta.get('approve_assets', [])
|
||||||
approve_system_users_id = self.meta.get('approve_system_users', [])
|
approve_system_users_id = self.ticket.meta.get('approve_system_users', [])
|
||||||
with tmp_to_org(self.org_id):
|
with tmp_to_org(self.ticket.org_id):
|
||||||
approve_assets_snapshot = list(
|
approve_assets_snapshot = list(
|
||||||
Asset.objects.filter(id__in=approve_assets_id).values(
|
Asset.objects.filter(id__in=approve_assets_id).values(
|
||||||
'hostname', 'ip', 'protocols', 'platform__name', 'public_ip'
|
'hostname', 'ip', 'protocols', 'platform__name', 'public_ip'
|
||||||
|
@ -43,15 +48,14 @@ class ConstructDisplayFieldMixin:
|
||||||
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
meta_display = dict(zip(meta_display_fields, meta_display_values))
|
||||||
return meta_display
|
return meta_display
|
||||||
|
|
||||||
|
# body
|
||||||
class ConstructBodyMixin:
|
def _construct_meta_body_of_open(self):
|
||||||
def construct_apply_asset_applied_body(self):
|
apply_ip_group = self.ticket.meta.get('apply_ip_group', [])
|
||||||
apply_ip_group = self.meta.get('apply_ip_group', [])
|
apply_hostname_group = self.ticket.meta.get('apply_hostname_group', [])
|
||||||
apply_hostname_group = self.meta.get('apply_hostname_group', [])
|
apply_system_user_group = self.ticket.meta.get('apply_system_user_group', [])
|
||||||
apply_system_user_group = self.meta.get('apply_system_user_group', [])
|
apply_actions_display = self.ticket.meta.get('apply_actions_display', [])
|
||||||
apply_actions_display = self.meta.get('apply_actions_display', [])
|
apply_date_start = self.ticket.meta.get('apply_date_start')
|
||||||
apply_date_start = self.meta.get('apply_date_start')
|
apply_date_expired = self.ticket.meta.get('apply_date_expired')
|
||||||
apply_date_expired = self.meta.get('apply_date_expired')
|
|
||||||
applied_body = '''{}: {},
|
applied_body = '''{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
|
@ -67,18 +71,18 @@ class ConstructBodyMixin:
|
||||||
)
|
)
|
||||||
return applied_body
|
return applied_body
|
||||||
|
|
||||||
def construct_apply_asset_approved_body(self):
|
def _construct_meta_body_of_approve(self):
|
||||||
approve_assets_snapshot = self.meta.get('approve_assets_snapshot', [])
|
approve_assets_snapshot = self.ticket.meta.get('approve_assets_snapshot', [])
|
||||||
approve_assets_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
|
approve_assets_snapshot_display = convert_model_data_field_name_to_verbose_name(
|
||||||
Asset, approve_assets_snapshot
|
model=Asset, data=approve_assets_snapshot
|
||||||
)
|
)
|
||||||
approve_system_users_snapshot = self.meta.get('approve_system_users_snapshot', [])
|
approve_system_users_snapshot = self.ticket.meta.get('approve_system_users_snapshot', [])
|
||||||
approve_system_users_snapshot_display = convert_model_instance_data_field_name_to_verbose_name(
|
approve_system_users_snapshot_display = convert_model_data_field_name_to_verbose_name(
|
||||||
SystemUser, approve_system_users_snapshot
|
model=SystemUser, data=approve_system_users_snapshot
|
||||||
)
|
)
|
||||||
approve_actions_display = self.meta.get('approve_actions_display', [])
|
approve_actions_display = self.ticket.meta.get('approve_actions_display', [])
|
||||||
approve_date_start = self.meta.get('approve_date_start')
|
approve_date_start = self.ticket.meta.get('approve_date_start')
|
||||||
approve_date_expired = self.meta.get('approve_date_expired')
|
approve_date_expired = self.ticket.meta.get('approve_date_expired')
|
||||||
approved_body = '''{}: {},
|
approved_body = '''{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
|
@ -93,21 +97,20 @@ class ConstructBodyMixin:
|
||||||
)
|
)
|
||||||
return approved_body
|
return approved_body
|
||||||
|
|
||||||
|
# permission
|
||||||
class CreatePermissionMixin:
|
def _create_asset_permission(self):
|
||||||
def create_apply_asset_permission(self):
|
|
||||||
with tmp_to_root_org():
|
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:
|
if asset_permission:
|
||||||
return asset_permission
|
return asset_permission
|
||||||
|
|
||||||
approve_assets_id = self.meta.get('approve_assets', [])
|
approve_assets_id = self.ticket.meta.get('approve_assets', [])
|
||||||
approve_system_users_id = self.meta.get('approve_system_users', [])
|
approve_system_users_id = self.ticket.meta.get('approve_system_users', [])
|
||||||
approve_actions = self.meta.get('approve_actions', Action.NONE)
|
approve_actions = self.ticket.meta.get('approve_actions', Action.NONE)
|
||||||
approve_date_start = self.meta.get('approve_date_start')
|
approve_date_start = self.ticket.meta.get('approve_date_start')
|
||||||
approve_date_expired = self.meta.get('approve_date_expired')
|
approve_date_expired = self.ticket.meta.get('approve_date_expired')
|
||||||
permission_name = '{}({})'.format(
|
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 = __(
|
permission_comment = __(
|
||||||
'Created by the ticket, '
|
'Created by the ticket, '
|
||||||
|
@ -115,20 +118,23 @@ class CreatePermissionMixin:
|
||||||
'ticket applicant: {}, '
|
'ticket applicant: {}, '
|
||||||
'ticket processor: {}, '
|
'ticket processor: {}, '
|
||||||
'ticket ID: {}'
|
'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 = {
|
permission_data = {
|
||||||
'id': self.id,
|
'id': self.ticket.id,
|
||||||
'name': permission_name,
|
'name': permission_name,
|
||||||
'comment': permission_comment,
|
'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,
|
'actions': approve_actions,
|
||||||
'date_start': approve_date_start,
|
'date_start': approve_date_start,
|
||||||
'date_expired': approve_date_expired,
|
'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 = 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.assets.set(approve_assets_id)
|
||||||
asset_permission.system_users.set(approve_system_users_id)
|
asset_permission.system_users.set(approve_system_users_id)
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
from .base import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHandler):
|
||||||
|
pass
|
|
@ -1,12 +1,14 @@
|
||||||
from django.utils.translation import ugettext as __
|
from django.utils.translation import ugettext as __
|
||||||
|
from .base import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
class ConstructBodyMixin:
|
class Handler(BaseHandler):
|
||||||
|
|
||||||
def construct_login_confirm_applied_body(self):
|
# body
|
||||||
apply_login_ip = self.meta.get('apply_login_ip')
|
def _construct_meta_body_of_open(self):
|
||||||
apply_login_city = self.meta.get('apply_login_city')
|
apply_login_ip = self.ticket.meta.get('apply_login_ip')
|
||||||
apply_login_datetime = self.meta.get('apply_login_datetime')
|
apply_login_city = self.ticket.meta.get('apply_login_city')
|
||||||
|
apply_login_datetime = self.ticket.meta.get('apply_login_datetime')
|
||||||
applied_body = '''{}: {},
|
applied_body = '''{}: {},
|
||||||
{}: {},
|
{}: {},
|
||||||
{}: {}
|
{}: {}
|
|
@ -101,7 +101,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='ticket',
|
model_name='ticket',
|
||||||
name='applicant_display',
|
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(
|
migrations.AddField(
|
||||||
model_name='ticket',
|
model_name='ticket',
|
||||||
|
@ -111,12 +111,12 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='ticket',
|
model_name='ticket',
|
||||||
name='processor_display',
|
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(
|
migrations.AddField(
|
||||||
model_name='ticket',
|
model_name='ticket',
|
||||||
name='assignees_display_new',
|
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(
|
migrations.AlterField(
|
||||||
model_name='ticket',
|
model_name='ticket',
|
||||||
|
@ -126,7 +126,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='ticket',
|
model_name='ticket',
|
||||||
name='meta',
|
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(
|
migrations.AlterField(
|
||||||
model_name='ticket',
|
model_name='ticket',
|
||||||
|
|
|
@ -11,8 +11,9 @@ from django.conf import settings
|
||||||
from common.mixins.models import CommonModelMixin
|
from common.mixins.models import CommonModelMixin
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from orgs.utils import tmp_to_root_org, tmp_to_org
|
from orgs.utils import tmp_to_root_org, tmp_to_org
|
||||||
from tickets import const
|
from tickets.const import TicketTypeChoices, TicketActionChoices, TicketStatusChoices
|
||||||
from .mixin import TicketModelMixin
|
from tickets.signals import post_change_ticket_action
|
||||||
|
from tickets.handler import get_ticket_handler
|
||||||
|
|
||||||
__all__ = ['Ticket', 'ModelJSONFieldEncoder']
|
__all__ = ['Ticket', 'ModelJSONFieldEncoder']
|
||||||
|
|
||||||
|
@ -30,20 +31,20 @@ class ModelJSONFieldEncoder(json.JSONEncoder):
|
||||||
return super().default(obj)
|
return super().default(obj)
|
||||||
|
|
||||||
|
|
||||||
class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
|
class Ticket(CommonModelMixin, OrgModelMixin):
|
||||||
title = models.CharField(max_length=256, verbose_name=_("Title"))
|
title = models.CharField(max_length=256, verbose_name=_("Title"))
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=64, choices=const.TicketTypeChoices.choices,
|
max_length=64, choices=TicketTypeChoices.choices,
|
||||||
default=const.TicketTypeChoices.general.value, verbose_name=_("Type")
|
default=TicketTypeChoices.general.value, verbose_name=_("Type")
|
||||||
)
|
)
|
||||||
meta = models.JSONField(encoder=ModelJSONFieldEncoder, default=dict, verbose_name=_("Meta"))
|
meta = models.JSONField(encoder=ModelJSONFieldEncoder, default=dict, verbose_name=_("Meta"))
|
||||||
action = models.CharField(
|
action = models.CharField(
|
||||||
choices=const.TicketActionChoices.choices, max_length=16,
|
choices=TicketActionChoices.choices, max_length=16,
|
||||||
default=const.TicketActionChoices.open.value, verbose_name=_("Action")
|
default=TicketActionChoices.open.value, verbose_name=_("Action")
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=16, choices=const.TicketStatusChoices.choices,
|
max_length=16, choices=TicketStatusChoices.choices,
|
||||||
default=const.TicketStatusChoices.open.value, verbose_name=_("Status")
|
default=TicketStatusChoices.open.value, verbose_name=_("Status")
|
||||||
)
|
)
|
||||||
# 申请人
|
# 申请人
|
||||||
applicant = models.ForeignKey(
|
applicant = models.ForeignKey(
|
||||||
|
@ -51,7 +52,7 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
|
||||||
verbose_name=_("Applicant")
|
verbose_name=_("Applicant")
|
||||||
)
|
)
|
||||||
applicant_display = models.CharField(
|
applicant_display = models.CharField(
|
||||||
max_length=256, default='No', verbose_name=_("Applicant display")
|
max_length=256, default='', verbose_name=_("Applicant display")
|
||||||
)
|
)
|
||||||
# 处理人
|
# 处理人
|
||||||
processor = models.ForeignKey(
|
processor = models.ForeignKey(
|
||||||
|
@ -59,7 +60,7 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
|
||||||
verbose_name=_("Processor")
|
verbose_name=_("Processor")
|
||||||
)
|
)
|
||||||
processor_display = models.CharField(
|
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(
|
assignees = models.ManyToManyField(
|
||||||
|
@ -80,70 +81,71 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
|
||||||
# type
|
# type
|
||||||
@property
|
@property
|
||||||
def type_apply_asset(self):
|
def type_apply_asset(self):
|
||||||
return self.type == const.TicketTypeChoices.apply_asset.value
|
return self.type == TicketTypeChoices.apply_asset.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_apply_application(self):
|
def type_apply_application(self):
|
||||||
return self.type == const.TicketTypeChoices.apply_application.value
|
return self.type == TicketTypeChoices.apply_application.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_login_confirm(self):
|
def type_login_confirm(self):
|
||||||
return self.type == const.TicketTypeChoices.login_confirm.value
|
return self.type == TicketTypeChoices.login_confirm.value
|
||||||
|
|
||||||
# status
|
# status
|
||||||
@property
|
@property
|
||||||
def status_closed(self):
|
def status_closed(self):
|
||||||
return self.status == const.TicketStatusChoices.closed.value
|
return self.status == TicketStatusChoices.closed.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status_open(self):
|
def status_open(self):
|
||||||
return self.status == const.TicketStatusChoices.open.value
|
return self.status == TicketStatusChoices.open.value
|
||||||
|
|
||||||
def set_status_closed(self):
|
def set_status_closed(self):
|
||||||
self.status = const.TicketStatusChoices.closed.value
|
self.status = TicketStatusChoices.closed.value
|
||||||
|
|
||||||
# action
|
# action
|
||||||
@property
|
@property
|
||||||
def action_open(self):
|
def action_open(self):
|
||||||
return self.action == const.TicketActionChoices.open.value
|
return self.action == TicketActionChoices.open.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def action_approve(self):
|
def action_approve(self):
|
||||||
return self.action == const.TicketActionChoices.approve.value
|
return self.action == TicketActionChoices.approve.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def action_reject(self):
|
def action_reject(self):
|
||||||
return self.action == const.TicketActionChoices.reject.value
|
return self.action == TicketActionChoices.reject.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def action_close(self):
|
def action_close(self):
|
||||||
return self.action == const.TicketActionChoices.close.value
|
return self.action == TicketActionChoices.close.value
|
||||||
|
|
||||||
@property
|
# action changed
|
||||||
def has_applied(self):
|
def open(self, applicant):
|
||||||
return self.action_open
|
self.applicant = applicant
|
||||||
|
self._change_action(action=TicketActionChoices.open.value)
|
||||||
|
|
||||||
@property
|
def approve(self, processor):
|
||||||
def has_processed(self):
|
self.processor = processor
|
||||||
return self.action_approve or self.action_reject or self.action_close
|
self._change_action(action=TicketActionChoices.approve.value)
|
||||||
|
|
||||||
def set_action_close(self):
|
def reject(self, processor):
|
||||||
self.action = const.TicketActionChoices.close.value
|
self.processor = processor
|
||||||
|
self._change_action(action=TicketActionChoices.reject.value)
|
||||||
|
|
||||||
def close(self, processor):
|
def close(self, processor):
|
||||||
self.processor = 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()
|
self.save()
|
||||||
|
post_change_ticket_action.send(sender=self.__class__, ticket=self, action=action)
|
||||||
|
|
||||||
# ticket
|
# ticket
|
||||||
def has_assignee(self, assignee):
|
def has_assignee(self, assignee):
|
||||||
return self.assignees.filter(id=assignee.id).exists()
|
return self.assignees.filter(id=assignee.id).exists()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def all(cls):
|
|
||||||
with tmp_to_root_org():
|
|
||||||
return Ticket.objects.all()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_related_tickets(cls, user):
|
def get_user_related_tickets(cls, user):
|
||||||
queries = None
|
queries = None
|
||||||
|
@ -176,7 +178,22 @@ class Ticket(TicketModelMixin, CommonModelMixin, OrgModelMixin):
|
||||||
tickets = tickets.filter(queries)
|
tickets = tickets.filter(queries)
|
||||||
return tickets.distinct()
|
return tickets.distinct()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all(cls):
|
||||||
|
with tmp_to_root_org():
|
||||||
|
return Ticket.objects.all()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
""" 确保保存的org_id的是自身的值 """
|
||||||
with tmp_to_org(self.org_id):
|
with tmp_to_org(self.org_id):
|
||||||
# 确保保存的org_id的是自身的值
|
|
||||||
return super().save(*args, **kwargs)
|
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
|
|
@ -1 +0,0 @@
|
||||||
from .model import *
|
|
|
@ -1 +0,0 @@
|
||||||
from .ticket import TicketModelMixin
|
|
|
@ -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)
|
|
|
@ -1 +0,0 @@
|
||||||
from .base import *
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -103,7 +103,7 @@ class ApproveSerializer(serializers.Serializer):
|
||||||
return system_users_id
|
return system_users_id
|
||||||
|
|
||||||
raise serializers.ValidationError(_(
|
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)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,16 @@
|
||||||
#
|
#
|
||||||
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 common.drf.fields import ReadableHiddenField
|
|
||||||
from common.drf.serializers import MethodSerializer
|
from common.drf.serializers import MethodSerializer
|
||||||
from orgs.utils import get_org_by_id
|
from orgs.utils import get_org_by_id
|
||||||
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from tickets import const
|
|
||||||
from tickets.models import Ticket
|
from tickets.models import Ticket
|
||||||
from .meta import type_serializer_classes_mapping
|
from .meta import type_serializer_classes_mapping
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'TicketSerializer', 'TicketDisplaySerializer',
|
'TicketDisplaySerializer', 'TicketApplySerializer', 'TicketApproveSerializer',
|
||||||
'TicketApplySerializer', 'TicketApproveSerializer',
|
|
||||||
'TicketRejectSerializer', 'TicketCloseSerializer',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,9 +19,6 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
|
||||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type'))
|
||||||
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
||||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status'))
|
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()
|
meta = MethodSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -72,24 +66,23 @@ class TicketDisplaySerializer(TicketSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = TicketSerializer.Meta.fields
|
fields = TicketSerializer.Meta.fields
|
||||||
read_only_fields = TicketSerializer.Meta.fields
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
class TicketApplySerializer(TicketSerializer):
|
class TicketApplySerializer(TicketSerializer):
|
||||||
org_id = serializers.CharField(
|
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:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = TicketSerializer.Meta.fields
|
fields = TicketSerializer.Meta.fields
|
||||||
required_fields = [
|
writeable_fields = [
|
||||||
'id', 'title', 'type', 'applicant', 'action', 'meta', 'assignees', 'comment', 'org_id'
|
'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 = {
|
extra_kwargs = {
|
||||||
'type': {'required': True},
|
'type': {'required': True},
|
||||||
'org_id': {'required': True},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_type(self, tp):
|
def validate_type(self, tp):
|
||||||
|
@ -121,49 +114,16 @@ class TicketApplySerializer(TicketSerializer):
|
||||||
raise serializers.ValidationError(error)
|
raise serializers.ValidationError(error)
|
||||||
return valid_assignees
|
return valid_assignees
|
||||||
|
|
||||||
def validate_action(self, action):
|
|
||||||
return const.TicketActionChoices.open.value
|
|
||||||
|
|
||||||
|
|
||||||
class TicketApproveSerializer(TicketSerializer):
|
class TicketApproveSerializer(TicketSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = TicketSerializer.Meta.fields
|
fields = TicketSerializer.Meta.fields
|
||||||
required_fields = ['processor', 'action', 'meta']
|
writeable_fields = ['meta']
|
||||||
read_only_fields = list(set(fields) - set(required_fields))
|
read_only_fields = list(set(fields) - set(writeable_fields))
|
||||||
|
|
||||||
def validate_meta(self, meta):
|
def validate_meta(self, meta):
|
||||||
_meta = self.instance.meta if self.instance else {}
|
_meta = self.instance.meta if self.instance else {}
|
||||||
_meta.update(meta)
|
_meta.update(meta)
|
||||||
return _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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
from django.dispatch import Signal
|
||||||
|
|
||||||
|
|
||||||
|
post_change_ticket_action = Signal()
|
|
@ -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()
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .ticket import *
|
||||||
|
from .comment import *
|
|
@ -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()
|
|
@ -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)
|
||||||
|
|
|
@ -20,7 +20,7 @@ def get_model_field_verbose_name(model, field_name):
|
||||||
return field_verbose_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):
|
if isinstance(data, dict):
|
||||||
data = [data]
|
data = [data]
|
||||||
converted_data = [
|
converted_data = [
|
||||||
|
|
Loading…
Reference in New Issue