diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index cdc7856bd..a450f610a 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -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() ) diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 27a9d7857..208d9c9fb 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -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 diff --git a/apps/common/db/models.py b/apps/common/db/models.py index b807b9dd5..502df31e9 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -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): diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index e779e4164..cb2695eec 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 92d93b15f..fb3ab714f 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: JumpServer team\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 "这个工单" diff --git a/apps/tickets/api/request_asset_perm.py b/apps/tickets/api/request_asset_perm.py index 40f908838..c12ea3070 100644 --- a/apps/tickets/api/request_asset_perm.py +++ b/apps/tickets/api/request_asset_perm.py @@ -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 diff --git a/apps/tickets/exceptions.py b/apps/tickets/exceptions.py index 3332139b5..5e5dedd21 100644 --- a/apps/tickets/exceptions.py +++ b/apps/tickets/exceptions.py @@ -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' diff --git a/apps/tickets/migrations/0003_auto_20200804_1551.py b/apps/tickets/migrations/0003_auto_20200804_1551.py new file mode 100644 index 000000000..936dbc5bb --- /dev/null +++ b/apps/tickets/migrations/0003_auto_20200804_1551.py @@ -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'), + ), + ] diff --git a/apps/tickets/models/ticket.py b/apps/tickets/models/ticket.py index 4f68aa6e0..3e979f244 100644 --- a/apps/tickets/models/ticket.py +++ b/apps/tickets/models/ticket.py @@ -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() diff --git a/apps/tickets/serializers/request_asset_perm.py b/apps/tickets/serializers/request_asset_perm.py index 3b2d72b7c..521d2582e 100644 --- a/apps/tickets/serializers/request_asset_perm.py +++ b/apps/tickets/serializers/request_asset_perm.py @@ -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() diff --git a/apps/tickets/serializers/ticket.py b/apps/tickets/serializers/ticket.py index f6c995ae0..34724be3a 100644 --- a/apps/tickets/serializers/ticket.py +++ b/apps/tickets/serializers/ticket.py @@ -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' diff --git a/apps/tickets/utils.py b/apps/tickets/utils.py index 97b5334e0..152b5182b 100644 --- a/apps/tickets/utils.py +++ b/apps/tickets/utils.py @@ -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}')