feat(ticket): 工单关闭生成 Comment

pull/4454/head
xinwen 2020-08-04 16:13:16 +08:00 committed by 老广
parent c3c5801d2e
commit f6a4253936
12 changed files with 180 additions and 125 deletions

View File

@ -139,7 +139,7 @@ class AuthMixin:
def get_ticket_or_create(self, confirm_setting):
ticket = self.get_ticket()
if not ticket or ticket.status == ticket.STATUS_CLOSED:
if not ticket or ticket.status == ticket.STATUS.CLOSED:
ticket = confirm_setting.create_confirm_ticket(self.request)
self.request.session['auth_ticket_id'] = str(ticket.id)
return ticket
@ -148,12 +148,12 @@ class AuthMixin:
ticket = self.get_ticket()
if not ticket:
raise errors.LoginConfirmOtherError('', "Not found")
if ticket.status == ticket.STATUS_OPEN:
if ticket.status == ticket.STATUS.OPEN:
raise errors.LoginConfirmWaitError(ticket.id)
elif ticket.action == ticket.ACTION_APPROVE:
elif ticket.action == ticket.ACTION.APPROVE:
self.request.session["auth_confirm"] = "1"
return
elif ticket.action == ticket.ACTION_REJECT:
elif ticket.action == ticket.ACTION.REJECT:
raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_action_display()
)

View File

@ -71,7 +71,7 @@ class LoginConfirmSetting(CommonModelMixin):
reviewer = self.reviewers.all()
ticket = Ticket.objects.create(
user=self.user, title=title, body=body,
type=Ticket.TYPE_LOGIN_CONFIRM,
type=Ticket.TYPE.LOGIN_CONFIRM,
)
ticket.assignees.set(reviewer)
return ticket

View File

@ -52,7 +52,7 @@ class ChoiceSetType(type):
return self._choices_dict.__getitem__(item)
def get(self, item, default=None):
return self._choices_dict.get(item, default=None)
return self._choices_dict.get(item, default)
@property
def choices(self):

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-07-31 19:20+0800\n"
"POT-Creation-Date: 2020-08-04 15:33+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -47,7 +47,7 @@ msgid "Name"
msgstr "名称"
#: applications/models/database_app.py:22 assets/models/cmd_filter.py:52
#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:46
#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:40
#: users/templates/users/user_granted_database_app.html:35
msgid "Type"
msgstr "类型"
@ -131,8 +131,8 @@ msgstr "参数"
#: applications/models/remote_app.py:39 assets/models/asset.py:224
#: assets/models/base.py:240 assets/models/cluster.py:28
#: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:60
#: assets/models/group.py:21 common/db/models.py:66 common/mixins/models.py:49
#: orgs/models.py:23 orgs/models.py:316 perms/models/base.py:54
#: assets/models/group.py:21 common/db/models.py:67 common/mixins/models.py:49
#: orgs/models.py:23 orgs/models.py:326 perms/models/base.py:54
#: users/models/user.py:530 users/serializers/group.py:35
#: users/templates/users/user_detail.html:97
#: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56
@ -145,9 +145,9 @@ msgstr "创建者"
#: applications/models/remote_app.py:42 assets/models/asset.py:225
#: assets/models/base.py:238 assets/models/cluster.py:26
#: assets/models/domain.py:23 assets/models/gathered_user.py:19
#: assets/models/group.py:22 assets/models/label.py:25 common/db/models.py:68
#: assets/models/group.py:22 assets/models/label.py:25 common/db/models.py:69
#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27
#: orgs/models.py:24 orgs/models.py:314 perms/models/base.py:55
#: orgs/models.py:24 orgs/models.py:324 perms/models/base.py:55
#: users/models/group.py:18 users/templates/users/user_group_detail.html:58
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:149
msgid "Date created"
@ -190,7 +190,7 @@ msgstr "基础"
msgid "Charset"
msgstr "编码"
#: assets/models/asset.py:148 tickets/models/ticket.py:41
#: assets/models/asset.py:148 tickets/models/ticket.py:35
msgid "Meta"
msgstr "元数据"
@ -380,8 +380,8 @@ msgid "SSH public key"
msgstr "SSH公钥"
#: assets/models/base.py:239 assets/models/gathered_user.py:20
#: common/db/models.py:69 common/mixins/models.py:51 ops/models/adhoc.py:39
#: orgs/models.py:315
#: common/db/models.py:70 common/mixins/models.py:51 ops/models/adhoc.py:39
#: orgs/models.py:325
msgid "Date updated"
msgstr "更新日期"
@ -488,7 +488,7 @@ msgstr "每行一个命令"
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/forms/asset_permission.py:20
#: tickets/serializers/request_asset_perm.py:60
#: tickets/serializers/ticket.py:26
#: tickets/serializers/ticket.py:30
#: users/templates/users/_granted_assets.html:29
#: users/templates/users/user_asset_permission.html:44
#: users/templates/users/user_asset_permission.html:79
@ -537,14 +537,14 @@ msgstr "默认资产组"
#: assets/models/label.py:15 audits/models.py:36 audits/models.py:56
#: audits/models.py:69 audits/serializers.py:77 authentication/models.py:46
#: authentication/models.py:90 orgs/models.py:16 orgs/models.py:312
#: authentication/models.py:90 orgs/models.py:16 orgs/models.py:322
#: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38
#: perms/forms/remote_app_permission.py:40 perms/models/base.py:49
#: templates/index.html:78 terminal/backends/command/models.py:18
#: terminal/backends/command/serializers.py:12 terminal/models.py:185
#: tickets/models/ticket.py:36 tickets/models/ticket.py:135
#: tickets/models/ticket.py:30 tickets/models/ticket.py:137
#: tickets/serializers/request_asset_perm.py:61
#: tickets/serializers/ticket.py:27 users/forms/group.py:15
#: tickets/serializers/ticket.py:31 users/forms/group.py:15
#: users/models/user.py:157 users/models/user.py:643
#: users/serializers/group.py:20
#: users/templates/users/user_asset_permission.html:38
@ -692,7 +692,7 @@ msgstr "协议重复: {}"
msgid "Hardware info"
msgstr "硬件信息"
#: assets/serializers/asset.py:112 orgs/mixins/serializers.py:27
#: assets/serializers/asset.py:112 orgs/mixins/serializers.py:26
msgid "Org name"
msgstr "组织名称"
@ -1014,7 +1014,7 @@ msgid "Reason"
msgstr "原因"
#: audits/models.py:106 tickets/serializers/request_asset_perm.py:59
#: tickets/serializers/ticket.py:25 xpack/plugins/cloud/models.py:211
#: tickets/serializers/ticket.py:29 xpack/plugins/cloud/models.py:211
#: xpack/plugins/cloud/models.py:269
msgid "Status"
msgstr "状态"
@ -1199,7 +1199,7 @@ msgstr "SSH密钥"
msgid "Reviewers"
msgstr "审批人"
#: authentication/models.py:56 tickets/models/ticket.py:27
#: authentication/models.py:56 tickets/models/ticket.py:23
#: users/templates/users/user_detail.html:250
msgid "Login confirm"
msgstr "登录复核"
@ -1263,7 +1263,7 @@ msgstr "删除成功"
#: authentication/templates/authentication/_access_key_modal.html:155
#: authentication/templates/authentication/_mfa_confirm_modal.html:53
#: templates/_modal.html:22 tickets/models/ticket.py:73
#: templates/_modal.html:22 tickets/models/ticket.py:67
msgid "Close"
msgstr "关闭"
@ -1393,7 +1393,7 @@ msgstr "%(name)s 创建成功"
msgid "%(name)s was updated successfully"
msgstr "%(name)s 更新成功"
#: common/db/models.py:67
#: common/db/models.py:68
msgid "Updated by"
msgstr "更新人"
@ -1649,16 +1649,16 @@ msgstr "更新任务内容: {}"
msgid "Disk used more than 80%: {} => {}"
msgstr "磁盘使用率超过 80%: {} => {}"
#: orgs/api.py:54
#: orgs/api.py:58
msgid "Organization contains undeleted resources"
msgstr ""
#: orgs/api.py:58
#: orgs/api.py:62
msgid "The current organization cannot be deleted"
msgstr ""
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:40
#: orgs/models.py:311
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:40
#: orgs/models.py:321
msgid "Organization"
msgstr "组织"
@ -1670,7 +1670,7 @@ msgstr "组织管理员"
msgid "Organization auditor"
msgstr "组织审计员"
#: orgs/models.py:313 users/forms/user.py:27 users/models/user.py:499
#: orgs/models.py:323 users/forms/user.py:27 users/models/user.py:499
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:73
#: users/templates/users/user_list.html:16
@ -2506,100 +2506,108 @@ msgstr "结束日期"
msgid "Args"
msgstr "参数"
#: tickets/api/request_asset_perm.py:42
msgid "Ticket closed"
msgstr "工单已关闭"
#: tickets/api/request_asset_perm.py:45
#: tickets/api/request_asset_perm.py:44
#, python-format
msgid "Ticket has %s"
msgstr "工单已%s"
#: tickets/api/request_asset_perm.py:90
#: tickets/api/request_asset_perm.py:89
msgid "Confirm assets first"
msgstr "请先确认资产"
#: tickets/api/request_asset_perm.py:93
#: tickets/api/request_asset_perm.py:92
msgid "Confirmed assets changed"
msgstr "确认的资产变更了"
#: tickets/api/request_asset_perm.py:97
#: tickets/api/request_asset_perm.py:96
msgid "Confirm system-user first"
msgstr "请先确认系统用户"
#: tickets/api/request_asset_perm.py:101
#: tickets/api/request_asset_perm.py:100
msgid "Confirmed system-user changed"
msgstr "确认的系统用户变更了"
#: tickets/api/request_asset_perm.py:104 xpack/plugins/cloud/models.py:202
#: tickets/api/request_asset_perm.py:103 xpack/plugins/cloud/models.py:202
msgid "Succeed"
msgstr "成功"
#: tickets/api/request_asset_perm.py:111
#: tickets/api/request_asset_perm.py:110
msgid "From request ticket: {} {}"
msgstr "来自工单申请: {} {}"
#: tickets/api/request_asset_perm.py:113
#: tickets/api/request_asset_perm.py:112
msgid "{} request assets, approved by {}"
msgstr "{} 申请资产,通过人 {}"
#: tickets/models/ticket.py:19 tickets/models/ticket.py:75
#: tickets/exceptions.py:23
msgid "Ticket closed"
msgstr "工单已关闭"
#: tickets/exceptions.py:32
msgid "Only assignee can operate ticket"
msgstr "只有审批人可以操作工单"
#: tickets/exceptions.py:37
msgid "Ticket can not be operated"
msgstr "不能操作该工单"
#: tickets/models/ticket.py:18 tickets/models/ticket.py:69
msgid "Open"
msgstr "开启"
#: tickets/models/ticket.py:20
#: tickets/models/ticket.py:19
msgid "Closed"
msgstr "关闭"
#: tickets/models/ticket.py:26
#: tickets/models/ticket.py:22
msgid "General"
msgstr "一般"
#: tickets/models/ticket.py:28
#: tickets/models/ticket.py:24
msgid "Request asset permission"
msgstr "申请资产权限"
#: tickets/models/ticket.py:33
#: tickets/models/ticket.py:27
msgid "Approve"
msgstr "同意"
#: tickets/models/ticket.py:34
#: tickets/models/ticket.py:28
msgid "Reject"
msgstr "拒绝"
#: tickets/models/ticket.py:37 tickets/models/ticket.py:136
#: tickets/models/ticket.py:31 tickets/models/ticket.py:138
msgid "User display name"
msgstr "用户显示名称"
#: tickets/models/ticket.py:39
#: tickets/models/ticket.py:33
msgid "Title"
msgstr "标题"
#: tickets/models/ticket.py:40 tickets/models/ticket.py:137
#: tickets/models/ticket.py:34 tickets/models/ticket.py:139
msgid "Body"
msgstr "内容"
#: tickets/models/ticket.py:42
#: tickets/models/ticket.py:36
msgid "Assignee"
msgstr "处理人"
#: tickets/models/ticket.py:43
#: tickets/models/ticket.py:37
msgid "Assignee display name"
msgstr "处理人名称"
#: tickets/models/ticket.py:44
#: tickets/models/ticket.py:38
msgid "Assignees"
msgstr "待处理人"
#: tickets/models/ticket.py:45
#: tickets/models/ticket.py:39
msgid "Assignees display name"
msgstr "待处理人名称"
#: tickets/models/ticket.py:76
#: tickets/models/ticket.py:70
msgid "{} {} this ticket"
msgstr "{} {} 这个工单"
#: tickets/models/ticket.py:87
#: tickets/models/ticket.py:85
msgid "this ticket"
msgstr "这个工单"

View File

@ -1,4 +1,3 @@
from django.db.transaction import atomic
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import action
@ -26,7 +25,7 @@ from ..permissions import IsAssignee
class RequestAssetPermTicketViewSet(JMSModelViewSet):
queryset = Ticket.origin_objects.filter(type=Ticket.TYPE_REQUEST_ASSET_PERM)
queryset = Ticket.origin_objects.filter(type=Ticket.TYPE.REQUEST_ASSET_PERM)
serializer_classes = {
'default': serializers.RequestAssetPermTicketSerializer,
'approve': EmptySerializer,
@ -38,10 +37,10 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
search_fields = ['user_display', 'title']
def _check_can_set_action(self, instance, action):
if instance.status == instance.STATUS_CLOSED:
raise TicketClosed(detail=_('Ticket closed'))
if instance.status == instance.STATUS.CLOSED:
raise TicketClosed
if instance.action == action:
action_display = dict(instance.ACTION_CHOICES).get(action)
action_display = instance.ACTION.get(action)
raise TicketActionAlready(detail=_('Ticket has %s') % action_display)
@action(detail=False, methods=[GET], permission_classes=[IsValidUser])
@ -72,7 +71,7 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
def reject(self, request, *args, **kwargs):
instance = self.get_object()
action = instance.ACTION_REJECT
action = instance.ACTION.REJECT
self._check_can_set_action(instance, action)
instance.perform_action(action, request.user, self._get_extra_comment(instance))
return Response()
@ -80,7 +79,7 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
def approve(self, request, *args, **kwargs):
instance = self.get_object()
action = instance.ACTION_APPROVE
action = instance.ACTION.APPROVE
self._check_can_set_action(instance, action)
meta = instance.meta
@ -100,10 +99,10 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
if system_user is None:
raise ConfirmedSystemUserChanged(detail=_('Confirmed system-user changed'))
self._create_asset_permission(instance, assets, system_user, request.user)
self._create_asset_permission(instance, assets, system_user)
return Response({'detail': _('Succeed')})
def _create_asset_permission(self, instance: Ticket, assets, system_user, user):
def _create_asset_permission(self, instance: Ticket, assets, system_user):
meta = instance.meta
request = self.request
@ -120,13 +119,12 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
if date_expired:
ap_kwargs['date_expired'] = date_expired
with atomic():
instance.perform_action(instance.ACTION_APPROVE,
request.user,
self._get_extra_comment(instance))
ap = AssetPermission.objects.create(**ap_kwargs)
ap.system_users.add(system_user)
ap.assets.add(*assets)
ap.users.add(user)
instance.perform_action(instance.ACTION.APPROVE,
request.user,
self._get_extra_comment(instance))
ap = AssetPermission.objects.create(**ap_kwargs)
ap.system_users.add(system_user)
ap.assets.add(*assets)
ap.users.add(instance.user)
return ap

View File

@ -1,3 +1,5 @@
from django.utils.translation import gettext_lazy as _
from common.exceptions import JMSException
@ -18,12 +20,19 @@ class ConfirmedSystemUserChanged(JMSException):
class TicketClosed(JMSException):
pass
default_detail = _('Ticket closed')
default_code = 'ticket_closed'
class TicketActionAlready(JMSException):
pass
class OrgIdRequiredException(JMSException):
pass
class OnlyTicketAssigneeCanOperate(JMSException):
default_detail = _('Only assignee can operate ticket')
default_code = 'can_not_operate'
class TicketCanNotOperate(JMSException):
default_detail = _('Ticket can not be operated')
default_code = 'ticket_can_not_be_operated'

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.10 on 2020-08-04 07:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tickets', '0002_auto_20200728_1146'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='assignee_display',
field=models.CharField(blank=True, default='', max_length=128, null=True, verbose_name='Assignee display name'),
),
]

View File

@ -5,6 +5,7 @@ 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
@ -13,26 +14,19 @@ __all__ = ['Ticket', 'Comment']
class Ticket(OrgModelMixin, CommonModelMixin):
STATUS_OPEN = 'open'
STATUS_CLOSED = 'closed'
STATUS_CHOICES = (
(STATUS_OPEN, _("Open")),
(STATUS_CLOSED, _("Closed"))
)
TYPE_GENERAL = 'general'
TYPE_LOGIN_CONFIRM = 'login_confirm'
TYPE_REQUEST_ASSET_PERM = 'request_asset'
TYPE_CHOICES = (
(TYPE_GENERAL, _("General")),
(TYPE_LOGIN_CONFIRM, _("Login confirm")),
(TYPE_REQUEST_ASSET_PERM, _('Request asset permission'))
)
ACTION_APPROVE = 'approve'
ACTION_REJECT = 'reject'
ACTION_CHOICES = (
(ACTION_APPROVE, _('Approve')),
(ACTION_REJECT, _('Reject')),
)
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"))
@ -40,12 +34,12 @@ class Ticket(OrgModelMixin, CommonModelMixin):
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"))
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)
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)
origin_objects = models.Manager()
@ -69,30 +63,38 @@ class Ticket(OrgModelMixin, CommonModelMixin):
return self.get_action_display()
def create_status_comment(self, status, user):
if status == self.STATUS_CLOSED:
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):
if self.status == status:
return
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.assignees_display = str(user)
self.save()
def create_action_comment(self, action, user, extra_comment=None):
action_display = dict(self.ACTION_CHOICES).get(action)
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_action_comment(action, user, extra_comment)
self.create_comment(
self.ACTION.get(action),
user,
extra_comment
)
self.action = action
self.status = self.STATUS_CLOSED
self.status = self.STATUS.CLOSED
self.assignee = user
self.assignees_display = str(user)
self.save()

View File

@ -17,7 +17,7 @@ from ..models import Ticket
class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
ips = serializers.ListField(child=serializers.IPAddressField(), source='meta.ips',
default=list, label=_('IP group'))
hostname = serializers.CharField(max_length=256, source='meta.hostname', default=None,
hostname = serializers.CharField(max_length=256, source='meta.hostname', default='',
allow_blank=True, label=_('Hostname'))
system_user = serializers.CharField(max_length=256, source='meta.system_user', default='',
allow_blank=True, label=_('System user'))
@ -135,7 +135,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
def _create_body(self, validated_data):
meta = validated_data['meta']
type = dict(Ticket.TYPE_CHOICES).get(validated_data.get('type', ''))
type = Ticket.TYPE.get(validated_data.get('type', ''))
date_start = dt_parser(meta.get('date_start')).strftime(settings.DATETIME_DISPLAY_FORMAT)
date_expired = dt_parser(meta.get('date_expired')).strftime(settings.DATETIME_DISPLAY_FORMAT)
@ -159,7 +159,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# `type` 与 `user` 用户不可提交,
validated_data['type'] = self.Meta.model.TYPE_REQUEST_ASSET_PERM
validated_data['type'] = self.Meta.model.TYPE.REQUEST_ASSET_PERM
validated_data['user'] = self.context['request'].user
# `confirmed` 相关字段只能审批人修改,所以创建时直接清理掉
self._pop_confirmed_fields()

View File

@ -3,14 +3,18 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from .. import models
from ..exceptions import (
TicketClosed, OnlyTicketAssigneeCanOperate,
TicketCanNotOperate
)
from ..models import Ticket, Comment
__all__ = ['TicketSerializer', 'CommentSerializer']
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = models.Ticket
model = Ticket
fields = [
'id', 'user', 'user_display', 'title', 'body',
'assignees', 'assignees_display', 'assignee', 'assignee_display',
@ -32,17 +36,33 @@ class TicketSerializer(serializers.ModelSerializer):
return super().create(validated_data)
def update(self, instance, validated_data):
action = validated_data.get("action")
user = self.context["request"].user
action = validated_data.get('action')
user = self.context['request'].user
if instance.type not in (Ticket.TYPE.GENERAL,
Ticket.TYPE.LOGIN_CONFIRM):
# 暂时的兼容操作吧,后期重构工单
raise TicketCanNotOperate
if instance.status == instance.STATUS.CLOSED:
raise TicketClosed
if action:
if user not in instance.assignees.all():
raise OnlyTicketAssigneeCanOperate
# 有 `action` 时忽略 `status`
validated_data.pop('status', None)
instance = super().update(instance, validated_data)
if not instance.status == instance.STATUS.CLOSED and action:
instance.perform_action(action, user)
else:
status = validated_data.get('status')
instance = super().update(instance, validated_data)
if status:
instance.perform_status(status, user)
if action and user not in instance.assignees.all():
error = {"action": "Only assignees can update"}
raise serializers.ValidationError(error)
if instance.status == instance.STATUS_CLOSED:
validated_data.pop('action')
instance = super().update(instance, validated_data)
if not instance.status == instance.STATUS_CLOSED and action:
instance.perform_action(action, user)
return instance
@ -65,7 +85,7 @@ class CommentSerializer(serializers.ModelSerializer):
)
class Meta:
model = models.Comment
model = Comment
fields = [
'id', 'ticket', 'body', 'user', 'user_display',
'date_created', 'date_updated'

View File

@ -20,7 +20,7 @@ def send_new_ticket_mail_to_assignees(ticket: Ticket, assignees):
subject = '{}: {}'.format(_("New ticket"), ticket.title)
# 这里要设置前端地址,因为要直接跳转到页面
if ticket.type == ticket.TYPE_REQUEST_ASSET_PERM:
if ticket.type == ticket.TYPE.REQUEST_ASSET_PERM:
detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/request-asset-perm/{ticket.id}')
else:
detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/{ticket.id}')