mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-09-25 06:42:49 +00:00
reactor&feat: 重构工单模块 & 支持申请应用工单 (#5352)
* reactor: 修改工单Model,添加工单迁移文件 * reactor: 修改工单Model,添加工单迁移文件 * reactor: 重构工单模块 * reactor: 重构工单模块2 * reactor: 重构工单模块3 * reactor: 重构工单模块4 * reactor: 重构工单模块5 * reactor: 重构工单模块6 * reactor: 重构工单模块7 * reactor: 重构工单模块8 * reactor: 重构工单模块9 * reactor: 重构工单模块10 * reactor: 重构工单模块11 * reactor: 重构工单模块12 * reactor: 重构工单模块13 * reactor: 重构工单模块14 * reactor: 重构工单模块15 * reactor: 重构工单模块16 * reactor: 重构工单模块17 * reactor: 重构工单模块18 * reactor: 重构工单模块19 * reactor: 重构工单模块20 * reactor: 重构工单模块21 * reactor: 重构工单模块22 * reactor: 重构工单模块23 * reactor: 重构工单模块24 * reactor: 重构工单模块25 * reactor: 重构工单模块26 * reactor: 重构工单模块27 * reactor: 重构工单模块28 * reactor: 重构工单模块29 * reactor: 重构工单模块30 * reactor: 重构工单模块31 * reactor: 重构工单模块32 * reactor: 重构工单模块33 * reactor: 重构工单模块34 * reactor: 重构工单模块35 * reactor: 重构工单模块36 * reactor: 重构工单模块37 * reactor: 重构工单模块38 * reactor: 重构工单模块39
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .ticket import *
|
||||
from .comment import *
|
||||
|
23
apps/tickets/models/comment.py
Normal file
23
apps/tickets/models/comment.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.mixins.models import CommonModelMixin
|
||||
|
||||
__all__ = ['Comment']
|
||||
|
||||
|
||||
class Comment(CommonModelMixin):
|
||||
ticket = models.ForeignKey(
|
||||
'tickets.Ticket', on_delete=models.CASCADE, related_name='comments'
|
||||
)
|
||||
user = models.ForeignKey(
|
||||
'users.User', on_delete=models.SET_NULL, null=True, related_name='comments',
|
||||
verbose_name=_("User")
|
||||
)
|
||||
user_display = models.CharField(max_length=256, verbose_name=_("User display name"))
|
||||
body = models.TextField(verbose_name=_("Body"))
|
||||
|
||||
class Meta:
|
||||
ordering = ('date_created', )
|
@@ -1,141 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.db.models import ChoiceSet
|
||||
from common.mixins.models import CommonModelMixin
|
||||
from common.fields.model import JsonDictTextField
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
__all__ = ['Ticket', 'Comment']
|
||||
|
||||
|
||||
class Ticket(OrgModelMixin, CommonModelMixin):
|
||||
class STATUS(ChoiceSet):
|
||||
OPEN = 'open', _("Open")
|
||||
CLOSED = 'closed', _("Closed")
|
||||
|
||||
class TYPE(ChoiceSet):
|
||||
GENERAL = 'general', _("General")
|
||||
LOGIN_CONFIRM = 'login_confirm', _("Login confirm")
|
||||
REQUEST_ASSET_PERM = 'request_asset', _('Request asset permission')
|
||||
|
||||
class ACTION(ChoiceSet):
|
||||
APPROVE = 'approve', _('Approve')
|
||||
REJECT = 'reject', _('Reject')
|
||||
|
||||
user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='%(class)s_requested', verbose_name=_("User"))
|
||||
user_display = models.CharField(max_length=128, verbose_name=_("User display name"))
|
||||
|
||||
title = models.CharField(max_length=256, verbose_name=_("Title"))
|
||||
body = models.TextField(verbose_name=_("Body"))
|
||||
meta = JsonDictTextField(verbose_name=_("Meta"), default='{}')
|
||||
assignee = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='%(class)s_handled', verbose_name=_("Assignee"))
|
||||
assignee_display = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Assignee display name"), default='')
|
||||
assignees = models.ManyToManyField('users.User', related_name='%(class)s_assigned', verbose_name=_("Assignees"))
|
||||
assignees_display = models.CharField(max_length=128, verbose_name=_("Assignees display name"), blank=True)
|
||||
type = models.CharField(max_length=16, choices=TYPE.choices, default=TYPE.GENERAL, verbose_name=_("Type"))
|
||||
status = models.CharField(choices=STATUS.choices, max_length=16, default='open')
|
||||
action = models.CharField(choices=ACTION.choices, max_length=16, default='', blank=True)
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
origin_objects = models.Manager()
|
||||
|
||||
def __str__(self):
|
||||
return '{}: {}'.format(self.user_display, self.title)
|
||||
|
||||
@property
|
||||
def body_as_html(self):
|
||||
return self.body.replace('\n', '<br/>')
|
||||
|
||||
@property
|
||||
def status_display(self):
|
||||
return self.get_status_display()
|
||||
|
||||
@property
|
||||
def type_display(self):
|
||||
return self.get_type_display()
|
||||
|
||||
@property
|
||||
def action_display(self):
|
||||
return self.get_action_display()
|
||||
|
||||
def create_status_comment(self, status, user):
|
||||
if status == self.STATUS.CLOSED:
|
||||
action = _("Close")
|
||||
else:
|
||||
action = _("Open")
|
||||
body = _('{} {} this ticket').format(self.user, action)
|
||||
self.comments.create(user=user, body=body)
|
||||
|
||||
def perform_status(self, status, user, extra_comment=None):
|
||||
self.create_comment(
|
||||
self.STATUS.get(status),
|
||||
user,
|
||||
extra_comment
|
||||
)
|
||||
self.status = status
|
||||
self.assignee = user
|
||||
self.save()
|
||||
|
||||
def create_comment(self, action_display, user, extra_comment=None):
|
||||
body = '{} {} {}'.format(user, action_display, _("this ticket"))
|
||||
if extra_comment is not None:
|
||||
body += extra_comment
|
||||
self.comments.create(body=body, user=user, user_display=str(user))
|
||||
|
||||
def perform_action(self, action, user, extra_comment=None):
|
||||
self.create_comment(
|
||||
self.ACTION.get(action),
|
||||
user,
|
||||
extra_comment
|
||||
)
|
||||
self.action = action
|
||||
self.status = self.STATUS.CLOSED
|
||||
self.assignee = user
|
||||
self.save()
|
||||
|
||||
def is_assignee(self, user):
|
||||
return self.assignees.filter(id=user.id).exists()
|
||||
|
||||
def is_user(self, user):
|
||||
return self.user == user
|
||||
|
||||
@classmethod
|
||||
def get_related_tickets(cls, user, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = cls.objects.all()
|
||||
queryset = queryset.filter(
|
||||
Q(assignees=user) | Q(user=user)
|
||||
).distinct()
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def get_assigned_tickets(cls, user, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = cls.objects.all()
|
||||
queryset = queryset.filter(assignees=user)
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def get_my_tickets(cls, user, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = cls.objects.all()
|
||||
queryset = queryset.filter(user=user)
|
||||
return queryset
|
||||
|
||||
class Meta:
|
||||
ordering = ('-date_created',)
|
||||
|
||||
|
||||
class Comment(CommonModelMixin):
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name='comments')
|
||||
user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_("User"), related_name='comments')
|
||||
user_display = models.CharField(max_length=128, verbose_name=_("User display name"))
|
||||
body = models.TextField(verbose_name=_("Body"))
|
||||
|
||||
class Meta:
|
||||
ordering = ('date_created', )
|
1
apps/tickets/models/ticket/__init__.py
Normal file
1
apps/tickets/models/ticket/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .ticket import *
|
1
apps/tickets/models/ticket/mixin/__init__.py
Normal file
1
apps/tickets/models/ticket/mixin/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .ticket import TicketModelMixin
|
100
apps/tickets/models/ticket/mixin/apply_application.py
Normal file
100
apps/tickets/models/ticket/mixin/apply_application.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from django.utils.translation import ugettext as __
|
||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
from applications.models import Application, Category
|
||||
from assets.models import SystemUser
|
||||
from perms.models import ApplicationPermission
|
||||
|
||||
|
||||
class ConstructBodyMixin:
|
||||
|
||||
def construct_apply_application_applied_body(self):
|
||||
apply_category = self.meta['apply_category']
|
||||
apply_category_display = dict(Category.choices)[apply_category]
|
||||
apply_type = self.meta['apply_type']
|
||||
apply_type_display = dict(Category.get_type_choices(apply_category))[apply_type]
|
||||
apply_application_group = self.meta['apply_application_group']
|
||||
apply_system_user_group = self.meta['apply_system_user_group']
|
||||
apply_date_start = self.meta['apply_date_start']
|
||||
apply_date_expired = self.meta['apply_date_expired']
|
||||
applied_body = '''{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
'''.format(
|
||||
__('Applied category'), apply_category_display,
|
||||
__('Applied type'), apply_type_display,
|
||||
__('Applied application group'), apply_application_group,
|
||||
__('Applied system user group'), apply_system_user_group,
|
||||
__('Applied date start'), apply_date_start,
|
||||
__('Applied date expired'), apply_date_expired,
|
||||
)
|
||||
return applied_body
|
||||
|
||||
def construct_apply_application_approved_body(self):
|
||||
# 审批信息
|
||||
approve_applications_id = self.meta['approve_applications']
|
||||
approve_system_users_id = self.meta['approve_system_users']
|
||||
with tmp_to_org(self.org_id):
|
||||
approve_applications = Application.objects.filter(id__in=approve_applications_id)
|
||||
approve_system_users = SystemUser.objects.filter(id__in=approve_system_users_id)
|
||||
approve_applications_display = [str(application) for application in approve_applications]
|
||||
approve_system_users_display = [str(system_user) for system_user in approve_system_users]
|
||||
approve_date_start = self.meta['approve_date_start']
|
||||
approve_date_expired = self.meta['approve_date_expired']
|
||||
approved_body = '''{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
'''.format(
|
||||
__('Approved applications'), ', '.join(approve_applications_display),
|
||||
__('Approved system users'), ', '.join(approve_system_users_display),
|
||||
__('Approved date start'), approve_date_start,
|
||||
__('Approved date expired'), approve_date_expired
|
||||
)
|
||||
return approved_body
|
||||
|
||||
|
||||
class CreatePermissionMixin:
|
||||
|
||||
def create_apply_application_permission(self):
|
||||
with tmp_to_root_org():
|
||||
application_permission = ApplicationPermission.objects.filter(id=self.id).first()
|
||||
if application_permission:
|
||||
return application_permission
|
||||
|
||||
apply_category = self.meta['apply_category']
|
||||
apply_type = self.meta['apply_type']
|
||||
approved_applications_id = self.meta['approve_applications']
|
||||
approve_system_users_id = self.meta['approve_system_users']
|
||||
approve_date_start = self.meta['approve_date_start']
|
||||
approve_date_expired = self.meta['approve_date_expired']
|
||||
permission_name = '{}({})'.format(
|
||||
__('Created by ticket ({})'.format(self.title)), str(self.id)[:4]
|
||||
)
|
||||
permission_comment = __(
|
||||
'Created by the ticket, '
|
||||
'ticket title: {}, '
|
||||
'ticket applicant: {}, '
|
||||
'ticket processor: {}, '
|
||||
'ticket ID: {}'
|
||||
''.format(self.title, self.applicant_display, self.processor_display, str(self.id))
|
||||
)
|
||||
permissions_data = {
|
||||
'id': self.id,
|
||||
'name': permission_name,
|
||||
'category': apply_category,
|
||||
'type': apply_type,
|
||||
'comment': permission_comment,
|
||||
'created_by': self.processor_display,
|
||||
'date_start': approve_date_start,
|
||||
'date_expired': approve_date_expired,
|
||||
}
|
||||
with tmp_to_org(self.org_id):
|
||||
application_permission = ApplicationPermission.objects.create(**permissions_data)
|
||||
application_permission.users.add(self.applicant)
|
||||
application_permission.applications.set(approved_applications_id)
|
||||
application_permission.system_users.set(approve_system_users_id)
|
||||
|
||||
return application_permission
|
99
apps/tickets/models/ticket/mixin/apply_asset.py
Normal file
99
apps/tickets/models/ticket/mixin/apply_asset.py
Normal file
@@ -0,0 +1,99 @@
|
||||
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
|
||||
|
||||
|
||||
class ConstructBodyMixin:
|
||||
def construct_apply_asset_applied_body(self):
|
||||
apply_ip_group = self.meta['apply_ip_group']
|
||||
apply_hostname_group = self.meta['apply_hostname_group']
|
||||
apply_system_user_group = self.meta['apply_system_user_group']
|
||||
apply_actions = self.meta['apply_actions']
|
||||
apply_actions_display = Action.value_to_choices_display(apply_actions)
|
||||
apply_actions_display = [str(action_display) for action_display in apply_actions_display]
|
||||
apply_date_start = self.meta['apply_date_start']
|
||||
apply_date_expired = self.meta['apply_date_expired']
|
||||
applied_body = '''{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
{}: {}
|
||||
'''.format(
|
||||
__('Applied IP group'), apply_ip_group,
|
||||
__("Applied hostname group"), apply_hostname_group,
|
||||
__("Applied system user group"), apply_system_user_group,
|
||||
__("Applied actions"), apply_actions_display,
|
||||
__('Applied date start'), apply_date_start,
|
||||
__('Applied date expired'), apply_date_expired,
|
||||
)
|
||||
return applied_body
|
||||
|
||||
def construct_apply_asset_approved_body(self):
|
||||
approve_assets_id = self.meta['approve_assets']
|
||||
approve_system_users_id = self.meta['approve_system_users']
|
||||
with tmp_to_org(self.org_id):
|
||||
approve_assets = Asset.objects.filter(id__in=approve_assets_id)
|
||||
approve_system_users = SystemUser.objects.filter(id__in=approve_system_users_id)
|
||||
approve_assets_display = [str(asset) for asset in approve_assets]
|
||||
approve_system_users_display = [str(system_user) for system_user in approve_system_users]
|
||||
approve_actions = self.meta['approve_actions']
|
||||
approve_actions_display = Action.value_to_choices_display(approve_actions)
|
||||
approve_actions_display = [str(action_display) for action_display in approve_actions_display]
|
||||
approve_date_start = self.meta['approve_date_start']
|
||||
approve_date_expired = self.meta['approve_date_expired']
|
||||
approved_body = '''{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
{}: {},
|
||||
{}: {}
|
||||
'''.format(
|
||||
__('Approved assets'), ', '.join(approve_assets_display),
|
||||
__('Approved system users'), ', '.join(approve_system_users_display),
|
||||
__('Approved actions'), ', '.join(approve_actions_display),
|
||||
__('Approved date start'), approve_date_start,
|
||||
__('Approved date expired'), approve_date_expired,
|
||||
)
|
||||
return approved_body
|
||||
|
||||
|
||||
class CreatePermissionMixin:
|
||||
def create_apply_asset_permission(self):
|
||||
with tmp_to_root_org():
|
||||
asset_permission = AssetPermission.objects.filter(id=self.id).first()
|
||||
if asset_permission:
|
||||
return asset_permission
|
||||
|
||||
approve_assets_id = self.meta['approve_assets']
|
||||
approve_system_users_id = self.meta['approve_system_users']
|
||||
approve_actions = self.meta['approve_actions']
|
||||
approve_date_start = self.meta['approve_date_start']
|
||||
approve_date_expired = self.meta['approve_date_expired']
|
||||
permission_name = '{}({})'.format(
|
||||
__('Created by ticket ({})'.format(self.title)), str(self.id)[:4]
|
||||
)
|
||||
permission_comment = __(
|
||||
'Created by the ticket, '
|
||||
'ticket title: {}, '
|
||||
'ticket applicant: {}, '
|
||||
'ticket processor: {}, '
|
||||
'ticket ID: {}'
|
||||
''.format(self.title, self.applicant_display, self.processor_display, str(self.id))
|
||||
)
|
||||
permission_data = {
|
||||
'id': self.id,
|
||||
'name': permission_name,
|
||||
'comment': permission_comment,
|
||||
'created_by': self.processor_display,
|
||||
'actions': approve_actions,
|
||||
'date_start': approve_date_start,
|
||||
'date_expired': approve_date_expired,
|
||||
}
|
||||
with tmp_to_org(self.org_id):
|
||||
asset_permission = AssetPermission.objects.create(**permission_data)
|
||||
asset_permission.users.add(self.applicant)
|
||||
asset_permission.assets.set(approve_assets_id)
|
||||
asset_permission.system_users.set(approve_system_users_id)
|
||||
|
||||
return asset_permission
|
100
apps/tickets/models/ticket/mixin/base.py
Normal file
100
apps/tickets/models/ticket/mixin/base.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import textwrap
|
||||
from django.utils.translation import ugettext as __
|
||||
|
||||
|
||||
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.is_approved:
|
||||
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)
|
||||
create_method()
|
||||
|
||||
|
||||
class CreateCommentMixin:
|
||||
def create_comment(self, comment_body):
|
||||
comment_data = {
|
||||
'body': comment_body,
|
||||
'user': self.processor,
|
||||
'user_display': self.processor_display
|
||||
}
|
||||
return self.comments.create(**comment_data)
|
||||
|
||||
def create_approved_comment(self):
|
||||
comment_body = self.construct_approved_body()
|
||||
# 页面展示需要取消缩进
|
||||
comment_body = textwrap.dedent(comment_body)
|
||||
self.create_comment(comment_body)
|
||||
|
||||
def create_action_comment(self):
|
||||
comment_body = __(
|
||||
'User {} {} the ticket'.format(self.processor_display, self.get_action_display())
|
||||
)
|
||||
self.create_comment(comment_body)
|
18
apps/tickets/models/ticket/mixin/login_confirm.py
Normal file
18
apps/tickets/models/ticket/mixin/login_confirm.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.utils.translation import ugettext as __
|
||||
|
||||
|
||||
class ConstructBodyMixin:
|
||||
|
||||
def construct_login_confirm_applied_body(self):
|
||||
apply_login_ip = self.meta['apply_login_ip']
|
||||
apply_login_city = self.meta['apply_login_city']
|
||||
apply_login_datetime = self.meta['apply_login_datetime']
|
||||
applied_body = '''{}: {},
|
||||
{}: {},
|
||||
{}: {}
|
||||
'''.format(
|
||||
__("Applied login IP"), apply_login_ip,
|
||||
__("Applied login city"), apply_login_city,
|
||||
__("Applied login datetime"), apply_login_datetime,
|
||||
)
|
||||
return applied_body
|
32
apps/tickets/models/ticket/mixin/ticket.py
Normal file
32
apps/tickets/models/ticket/mixin/ticket.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from . import base, apply_asset, apply_application, login_confirm
|
||||
|
||||
__all__ = ['TicketModelMixin']
|
||||
|
||||
|
||||
class TicketConstructBodyMixin(
|
||||
base.ConstructBodyMixin,
|
||||
apply_asset.ConstructBodyMixin,
|
||||
apply_application.ConstructBodyMixin,
|
||||
login_confirm.ConstructBodyMixin
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TicketCreatePermissionMixin(
|
||||
base.CreatePermissionMixin,
|
||||
apply_asset.CreatePermissionMixin,
|
||||
apply_application.CreatePermissionMixin
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TicketCreateCommentMixin(
|
||||
base.CreateCommentMixin
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TicketModelMixin(
|
||||
TicketConstructBodyMixin, TicketCreatePermissionMixin, TicketCreateCommentMixin
|
||||
):
|
||||
pass
|
159
apps/tickets/models/ticket/ticket.py
Normal file
159
apps/tickets/models/ticket/ticket.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
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
|
||||
|
||||
__all__ = ['Ticket']
|
||||
|
||||
|
||||
class ModelJSONFieldEncoder(json.JSONEncoder):
|
||||
""" 解决一些类型的字段不能序列化的问题 """
|
||||
def default(self, obj):
|
||||
if isinstance(obj, datetime):
|
||||
return obj.strftime(settings.DATETIME_DISPLAY_FORMAT)
|
||||
if isinstance(obj, uuid.UUID):
|
||||
return str(obj)
|
||||
else:
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
class Ticket(TicketModelMixin, 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")
|
||||
)
|
||||
meta = models.JSONField(encoder=ModelJSONFieldEncoder, verbose_name=_("Meta"))
|
||||
action = models.CharField(
|
||||
choices=const.TicketActionChoices.choices, max_length=16,
|
||||
default=const.TicketActionChoices.apply.value, verbose_name=_("Action")
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=16, choices=const.TicketStatusChoices.choices,
|
||||
default=const.TicketStatusChoices.open.value, verbose_name=_("Status")
|
||||
)
|
||||
# 申请人
|
||||
applicant = models.ForeignKey(
|
||||
'users.User', related_name='applied_tickets', on_delete=models.SET_NULL, null=True,
|
||||
verbose_name=_("Applicant")
|
||||
)
|
||||
applicant_display = models.CharField(
|
||||
max_length=256, default='No', verbose_name=_("Applicant display")
|
||||
)
|
||||
# 处理人
|
||||
processor = models.ForeignKey(
|
||||
'users.User', related_name='processed_tickets', on_delete=models.SET_NULL, null=True,
|
||||
verbose_name=_("Processor")
|
||||
)
|
||||
processor_display = models.CharField(
|
||||
max_length=256, blank=True, null=True, default='No', verbose_name=_("Processor display")
|
||||
)
|
||||
# 受理人列表
|
||||
assignees = models.ManyToManyField(
|
||||
'users.User', related_name='assigned_tickets', verbose_name=_("Assignees")
|
||||
)
|
||||
assignees_display = models.TextField(
|
||||
blank=True, default='No', verbose_name=_("Assignees display")
|
||||
)
|
||||
# 评论
|
||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
class Meta:
|
||||
ordering = ('-date_created',)
|
||||
|
||||
def __str__(self):
|
||||
return '{}({})'.format(self.title, self.applicant_display)
|
||||
|
||||
|
||||
def has_assignee(self, assignee):
|
||||
return self.assignees.filter(id=assignee.id).exists()
|
||||
|
||||
# status
|
||||
@property
|
||||
def status_closed(self):
|
||||
return self.status == const.TicketStatusChoices.closed.value
|
||||
|
||||
@property
|
||||
def status_open(self):
|
||||
return self.status == const.TicketStatusChoices.open.value
|
||||
|
||||
# action
|
||||
@property
|
||||
def is_applied(self):
|
||||
return self.action == const.TicketActionChoices.apply.value
|
||||
|
||||
@property
|
||||
def is_approved(self):
|
||||
return self.action == const.TicketActionChoices.approve.value
|
||||
|
||||
@property
|
||||
def is_rejected(self):
|
||||
return self.action == const.TicketActionChoices.reject.value
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
return self.action == const.TicketActionChoices.close.value
|
||||
|
||||
@property
|
||||
def is_processed(self):
|
||||
return self.is_approved or self.is_rejected or self.is_closed
|
||||
|
||||
# perform action
|
||||
def close(self, processor):
|
||||
self.processor = processor
|
||||
self.action = const.TicketActionChoices.close.value
|
||||
self.save()
|
||||
|
||||
# tickets
|
||||
@classmethod
|
||||
def all(cls):
|
||||
with tmp_to_root_org():
|
||||
return Ticket.objects.all()
|
||||
|
||||
@classmethod
|
||||
def get_user_related_tickets(cls, user):
|
||||
queries = None
|
||||
tickets = cls.all()
|
||||
if user.is_superuser:
|
||||
pass
|
||||
elif user.is_super_auditor:
|
||||
pass
|
||||
elif user.is_org_admin:
|
||||
admin_orgs_id = [
|
||||
str(org_id) for org_id in user.admin_orgs.values_list('id', flat=True)
|
||||
]
|
||||
assigned_tickets_id = [
|
||||
str(ticket_id) for ticket_id in user.assigned_tickets.values_list('id', flat=True)
|
||||
]
|
||||
queries = Q(applicant=user)
|
||||
queries |= Q(processor=user)
|
||||
queries |= Q(org_id__in=admin_orgs_id)
|
||||
queries |= Q(id__in=assigned_tickets_id)
|
||||
elif user.is_org_auditor:
|
||||
audit_orgs_id = [
|
||||
str(org_id) for org_id in user.audit_orgs.values_list('id', flat=True)
|
||||
]
|
||||
queries = Q(org_id__in=audit_orgs_id)
|
||||
elif user.is_common_user:
|
||||
queries = Q(applicant=user)
|
||||
else:
|
||||
tickets = cls.objects.none()
|
||||
if queries:
|
||||
tickets = tickets.filter(queries)
|
||||
return tickets.distinct()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
with tmp_to_org(self.org_id):
|
||||
# 确保保存的org_id的是自身的值
|
||||
return super().save(*args, **kwargs)
|
Reference in New Issue
Block a user