mirror of https://github.com/jumpserver/jumpserver
feat(ticket): 调整申请资产工单
parent
b331730422
commit
f8e248f0af
|
@ -1,11 +1,21 @@
|
|||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
||||
from ..mixins.api import SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin
|
||||
from ..mixins.api import (
|
||||
SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin
|
||||
)
|
||||
|
||||
|
||||
class JmsGenericViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, GenericViewSet):
|
||||
class JmsGenericViewSet(SerializerMixin2,
|
||||
QuerySetMixin,
|
||||
ExtraFilterFieldsMixin,
|
||||
PaginatedResponseMixin,
|
||||
GenericViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class JMSModelViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, ModelViewSet):
|
||||
class JMSModelViewSet(SerializerMixin2,
|
||||
QuerySetMixin,
|
||||
ExtraFilterFieldsMixin,
|
||||
PaginatedResponseMixin,
|
||||
ModelViewSet):
|
||||
pass
|
||||
|
|
|
@ -67,6 +67,17 @@ class ExtraFilterFieldsMixin:
|
|||
return queryset
|
||||
|
||||
|
||||
class PaginatedResponseMixin:
|
||||
def get_paginated_response_with_query_set(self, queryset):
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin):
|
||||
pass
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import time
|
|||
import ipaddress
|
||||
import psutil
|
||||
|
||||
from .timezone import dt_formater
|
||||
|
||||
|
||||
UUID_PATTERN = re.compile(r'\w{8}(-\w{4}){3}-\w{12}')
|
||||
ipip_db = None
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import datetime
|
||||
|
||||
import pytz
|
||||
from django.utils import timezone as dj_timezone
|
||||
from rest_framework.fields import DateTimeField
|
||||
|
||||
max = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
|
||||
def astimezone(dt: datetime.datetime, tzinfo: pytz.tzinfo.DstTzInfo):
|
||||
assert dj_timezone.is_aware(dt)
|
||||
return tzinfo.normalize(dt.astimezone(tzinfo))
|
||||
|
||||
|
||||
def as_china_cst(dt: datetime.datetime):
|
||||
return astimezone(dt, pytz.timezone('Asia/Shanghai'))
|
||||
|
||||
|
||||
def as_current_tz(dt: datetime.datetime):
|
||||
return astimezone(dt, dj_timezone.get_current_timezone())
|
||||
|
||||
|
||||
def utcnow():
|
||||
return dj_timezone.now()
|
||||
|
||||
|
||||
def now():
|
||||
return as_current_tz(utcnow())
|
||||
|
||||
|
||||
_rest_dt_field = DateTimeField()
|
||||
dt_parser = _rest_dt_field.to_internal_value
|
||||
dt_formater = _rest_dt_field.to_representation
|
|
@ -96,3 +96,5 @@ XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID
|
|||
LOGO_URLS = DYNAMIC.LOGO_URLS
|
||||
|
||||
CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED
|
||||
|
||||
DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-07-27 19:01+0800\n"
|
||||
"POT-Creation-Date: 2020-07-28 11:25+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"
|
||||
|
@ -28,7 +28,7 @@ msgstr "自定义"
|
|||
#: assets/models/label.py:18 ops/mixin.py:24 orgs/models.py:22
|
||||
#: perms/models/base.py:48 settings/models.py:27 terminal/models.py:26
|
||||
#: terminal/models.py:342 terminal/models.py:374 terminal/models.py:411
|
||||
#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:473
|
||||
#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:489
|
||||
#: users/templates/users/_select_user_modal.html:13
|
||||
#: users/templates/users/user_asset_permission.html:37
|
||||
#: users/templates/users/user_asset_permission.html:154
|
||||
|
@ -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:45
|
||||
#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:46
|
||||
#: users/templates/users/user_granted_database_app.html:35
|
||||
msgid "Type"
|
||||
msgstr "类型"
|
||||
|
@ -77,7 +77,7 @@ msgstr "数据库"
|
|||
#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37
|
||||
#: orgs/models.py:25 perms/models/base.py:56 settings/models.py:32
|
||||
#: terminal/models.py:36 terminal/models.py:381 terminal/models.py:418
|
||||
#: users/models/group.py:16 users/models/user.py:506
|
||||
#: users/models/group.py:16 users/models/user.py:522
|
||||
#: users/templates/users/user_detail.html:115
|
||||
#: users/templates/users/user_granted_database_app.html:38
|
||||
#: users/templates/users/user_granted_remote_app.html:37
|
||||
|
@ -132,7 +132,7 @@ msgstr "参数"
|
|||
#: 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/mixins/models.py:49 orgs/models.py:23
|
||||
#: orgs/models.py:316 perms/models/base.py:54 users/models/user.py:514
|
||||
#: orgs/models.py:316 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
|
||||
#: xpack/plugins/cloud/models.py:146 xpack/plugins/gathered_user/models.py:30
|
||||
|
@ -189,7 +189,7 @@ msgstr "基础"
|
|||
msgid "Charset"
|
||||
msgstr "编码"
|
||||
|
||||
#: assets/models/asset.py:148 tickets/models/ticket.py:40
|
||||
#: assets/models/asset.py:148 tickets/models/ticket.py:41
|
||||
msgid "Meta"
|
||||
msgstr "元数据"
|
||||
|
||||
|
@ -211,7 +211,7 @@ msgstr "IP"
|
|||
|
||||
#: assets/models/asset.py:187 assets/serializers/asset_user.py:45
|
||||
#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:51
|
||||
#: tickets/serializers/request_asset_perm.py:14
|
||||
#: tickets/serializers/request_asset_perm.py:21
|
||||
#: users/templates/users/_granted_assets.html:25
|
||||
#: users/templates/users/user_asset_permission.html:157
|
||||
msgid "Hostname"
|
||||
|
@ -339,7 +339,7 @@ msgstr ""
|
|||
#: audits/models.py:99 authentication/forms.py:11
|
||||
#: authentication/templates/authentication/login.html:21
|
||||
#: authentication/templates/authentication/xpack_login.html:101
|
||||
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:471
|
||||
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:487
|
||||
#: users/templates/users/_select_user_modal.html:14
|
||||
#: users/templates/users/user_detail.html:53
|
||||
#: users/templates/users/user_list.html:15
|
||||
|
@ -391,7 +391,7 @@ msgstr "带宽"
|
|||
msgid "Contact"
|
||||
msgstr "联系人"
|
||||
|
||||
#: assets/models/cluster.py:22 users/models/user.py:492
|
||||
#: assets/models/cluster.py:22 users/models/user.py:508
|
||||
#: users/templates/users/user_detail.html:62
|
||||
msgid "Phone"
|
||||
msgstr "手机"
|
||||
|
@ -417,7 +417,7 @@ msgid "Default"
|
|||
msgstr "默认"
|
||||
|
||||
#: assets/models/cluster.py:36 assets/models/label.py:14
|
||||
#: users/models/user.py:635
|
||||
#: users/models/user.py:655
|
||||
msgid "System"
|
||||
msgstr "系统"
|
||||
|
||||
|
@ -485,7 +485,7 @@ msgstr "每行一个命令"
|
|||
#: assets/models/cmd_filter.py:56 audits/models.py:57
|
||||
#: authentication/templates/authentication/_access_key_modal.html:34
|
||||
#: perms/forms/asset_permission.py:20
|
||||
#: tickets/serializers/request_asset_perm.py:54
|
||||
#: tickets/serializers/request_asset_perm.py:60
|
||||
#: tickets/serializers/ticket.py:26
|
||||
#: users/templates/users/_granted_assets.html:29
|
||||
#: users/templates/users/user_asset_permission.html:44
|
||||
|
@ -540,10 +540,10 @@ msgstr "默认资产组"
|
|||
#: 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:35 tickets/models/ticket.py:130
|
||||
#: tickets/serializers/request_asset_perm.py:55
|
||||
#: tickets/models/ticket.py:36 tickets/models/ticket.py:135
|
||||
#: tickets/serializers/request_asset_perm.py:61
|
||||
#: tickets/serializers/ticket.py:27 users/forms/group.py:15
|
||||
#: users/models/user.py:157 users/models/user.py:623
|
||||
#: users/models/user.py:157 users/models/user.py:643
|
||||
#: users/serializers/group.py:20
|
||||
#: users/templates/users/user_asset_permission.html:38
|
||||
#: users/templates/users/user_asset_permission.html:64
|
||||
|
@ -649,7 +649,7 @@ msgstr "SFTP根路径"
|
|||
#: perms/models/remote_app_permission.py:16 templates/_nav.html:45
|
||||
#: terminal/backends/command/models.py:20
|
||||
#: terminal/backends/command/serializers.py:14 terminal/models.py:189
|
||||
#: tickets/serializers/request_asset_perm.py:16
|
||||
#: tickets/serializers/request_asset_perm.py:23
|
||||
#: users/templates/users/_granted_assets.html:27
|
||||
#: users/templates/users/user_asset_permission.html:42
|
||||
#: users/templates/users/user_asset_permission.html:76
|
||||
|
@ -708,14 +708,14 @@ msgid "Backend"
|
|||
msgstr "后端"
|
||||
|
||||
#: assets/serializers/asset_user.py:75 users/forms/profile.py:148
|
||||
#: users/models/user.py:503 users/templates/users/user_password_update.html:48
|
||||
#: users/models/user.py:519 users/templates/users/user_password_update.html:48
|
||||
#: users/templates/users/user_profile.html:69
|
||||
#: users/templates/users/user_profile_update.html:46
|
||||
#: users/templates/users/user_pubkey_update.html:46
|
||||
msgid "Public key"
|
||||
msgstr "SSH公钥"
|
||||
|
||||
#: assets/serializers/asset_user.py:79 users/models/user.py:500
|
||||
#: assets/serializers/asset_user.py:79 users/models/user.py:516
|
||||
msgid "Private key"
|
||||
msgstr "ssh私钥"
|
||||
|
||||
|
@ -875,7 +875,7 @@ msgstr "没有匹配到资产,结束任务"
|
|||
#: users/templates/users/user_list.html:98
|
||||
#: users/templates/users/user_remote_app_permission.html:111
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
msgstr "删除文件"
|
||||
|
||||
#: audits/models.py:27
|
||||
msgid "Upload"
|
||||
|
@ -920,7 +920,7 @@ msgid "Success"
|
|||
msgstr "成功"
|
||||
|
||||
#: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52
|
||||
#: terminal/models.py:199 tickets/serializers/request_asset_perm.py:18
|
||||
#: terminal/models.py:199 tickets/serializers/request_asset_perm.py:25
|
||||
#: xpack/plugins/change_auth_plan/models.py:177
|
||||
#: xpack/plugins/change_auth_plan/models.py:308
|
||||
#: xpack/plugins/gathered_user/models.py:76
|
||||
|
@ -1000,8 +1000,8 @@ msgstr "Agent"
|
|||
#: audits/models.py:104
|
||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
||||
#: authentication/templates/authentication/login_otp.html:6
|
||||
#: users/forms/profile.py:52 users/models/user.py:495
|
||||
#: users/serializers/user.py:224 users/templates/users/user_detail.html:77
|
||||
#: users/forms/profile.py:52 users/models/user.py:511
|
||||
#: users/serializers/user.py:234 users/templates/users/user_detail.html:77
|
||||
#: users/templates/users/user_profile.html:87
|
||||
msgid "MFA"
|
||||
msgstr "多因子认证"
|
||||
|
@ -1011,7 +1011,7 @@ msgstr "多因子认证"
|
|||
msgid "Reason"
|
||||
msgstr "原因"
|
||||
|
||||
#: audits/models.py:106 tickets/serializers/request_asset_perm.py:53
|
||||
#: audits/models.py:106 tickets/serializers/request_asset_perm.py:59
|
||||
#: tickets/serializers/ticket.py:25 xpack/plugins/cloud/models.py:211
|
||||
#: xpack/plugins/cloud/models.py:269
|
||||
msgid "Status"
|
||||
|
@ -1193,7 +1193,7 @@ msgstr "SSH密钥"
|
|||
msgid "Reviewers"
|
||||
msgstr "审批人"
|
||||
|
||||
#: authentication/models.py:53 tickets/models/ticket.py:26
|
||||
#: authentication/models.py:53 tickets/models/ticket.py:27
|
||||
#: users/templates/users/user_detail.html:250
|
||||
msgid "Login confirm"
|
||||
msgstr "登录复核"
|
||||
|
@ -1228,7 +1228,7 @@ msgid "Show"
|
|||
msgstr "显示"
|
||||
|
||||
#: authentication/templates/authentication/_access_key_modal.html:66
|
||||
#: users/models/user.py:393 users/serializers/user.py:221
|
||||
#: users/models/user.py:409 users/serializers/user.py:231
|
||||
#: users/templates/users/user_profile.html:94
|
||||
#: users/templates/users/user_profile.html:163
|
||||
#: users/templates/users/user_profile.html:166
|
||||
|
@ -1237,7 +1237,7 @@ msgid "Disable"
|
|||
msgstr "禁用"
|
||||
|
||||
#: authentication/templates/authentication/_access_key_modal.html:67
|
||||
#: users/models/user.py:394 users/serializers/user.py:222
|
||||
#: users/models/user.py:410 users/serializers/user.py:232
|
||||
#: users/templates/users/user_profile.html:92
|
||||
#: users/templates/users/user_profile.html:170
|
||||
msgid "Enable"
|
||||
|
@ -1249,7 +1249,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:70
|
||||
#: templates/_modal.html:22 tickets/models/ticket.py:73
|
||||
msgid "Close"
|
||||
msgstr "关闭"
|
||||
|
||||
|
@ -1628,11 +1628,11 @@ msgstr "磁盘使用率超过 80%: {} => {}"
|
|||
|
||||
#: orgs/api.py:54
|
||||
msgid "Organization contains undeleted resources"
|
||||
msgstr "组织内包含未删除的资源"
|
||||
msgstr ""
|
||||
|
||||
#: orgs/api.py:58
|
||||
msgid "The current organization cannot be deleted"
|
||||
msgstr "当能删除当前所在组织"
|
||||
msgstr ""
|
||||
|
||||
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:40
|
||||
#: orgs/models.py:311
|
||||
|
@ -1647,7 +1647,7 @@ msgstr "组织管理员"
|
|||
msgid "Organization auditor"
|
||||
msgstr "组织审计员"
|
||||
|
||||
#: orgs/models.py:313 users/forms/user.py:27 users/models/user.py:483
|
||||
#: orgs/models.py:313 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
|
||||
|
@ -1672,7 +1672,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
|
|||
#: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41
|
||||
#: perms/forms/remote_app_permission.py:43 perms/models/base.py:50
|
||||
#: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31
|
||||
#: users/models/user.py:479 users/serializers/user.py:43
|
||||
#: users/models/user.py:495 users/serializers/user.py:48
|
||||
#: users/templates/users/_select_user_modal.html:16
|
||||
#: users/templates/users/user_asset_permission.html:39
|
||||
#: users/templates/users/user_asset_permission.html:67
|
||||
|
@ -1719,15 +1719,15 @@ msgstr "上传下载"
|
|||
|
||||
#: perms/models/asset_permission.py:40
|
||||
msgid "Clipboard copy"
|
||||
msgstr "剪切板复制"
|
||||
msgstr ""
|
||||
|
||||
#: perms/models/asset_permission.py:41
|
||||
msgid "Clipboard paste"
|
||||
msgstr "剪切板粘贴"
|
||||
msgstr ""
|
||||
|
||||
#: perms/models/asset_permission.py:42
|
||||
msgid "Clipboard copy paste"
|
||||
msgstr "剪切板复制粘贴"
|
||||
msgstr ""
|
||||
|
||||
#: perms/models/asset_permission.py:93
|
||||
msgid "Actions"
|
||||
|
@ -1738,8 +1738,8 @@ msgstr "动作"
|
|||
msgid "Asset permission"
|
||||
msgstr "资产授权"
|
||||
|
||||
#: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:20
|
||||
#: users/models/user.py:511 users/templates/users/user_detail.html:93
|
||||
#: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:27
|
||||
#: users/models/user.py:527 users/templates/users/user_detail.html:93
|
||||
#: users/templates/users/user_profile.html:120
|
||||
msgid "Date expired"
|
||||
msgstr "失效日期"
|
||||
|
@ -2479,124 +2479,151 @@ msgstr "结束日期"
|
|||
msgid "Args"
|
||||
msgstr "参数"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:41
|
||||
#: tickets/api/request_asset_perm.py:43
|
||||
msgid "Ticket closed"
|
||||
msgstr "工单已关闭"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:44
|
||||
#: tickets/api/request_asset_perm.py:46
|
||||
#, python-format
|
||||
msgid "Ticket has %s"
|
||||
msgstr "工单已%s"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:69
|
||||
msgid "Superuser"
|
||||
msgstr "超级管理员"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:99
|
||||
#: tickets/api/request_asset_perm.py:93
|
||||
msgid "Confirm assets first"
|
||||
msgstr "请先确认资产"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:102
|
||||
#: tickets/api/request_asset_perm.py:96
|
||||
msgid "Confirmed assets changed"
|
||||
msgstr "确认的资产变更了"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:106
|
||||
#: tickets/api/request_asset_perm.py:100
|
||||
msgid "Confirm system-user first"
|
||||
msgstr "请先确认系统用户"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:110
|
||||
#: tickets/api/request_asset_perm.py:104
|
||||
msgid "Confirmed system-user changed"
|
||||
msgstr "确认的系统用户变更了"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:113 xpack/plugins/cloud/models.py:202
|
||||
#: tickets/api/request_asset_perm.py:107 xpack/plugins/cloud/models.py:202
|
||||
msgid "Succeed"
|
||||
msgstr "成功"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:121
|
||||
#: tickets/api/request_asset_perm.py:114
|
||||
msgid "From request ticket: {} {}"
|
||||
msgstr "来自工单申请: {} {}"
|
||||
|
||||
#: tickets/api/request_asset_perm.py:116
|
||||
msgid "{} request assets, approved by {}"
|
||||
msgstr "{} 申请资产,通过人 {}"
|
||||
|
||||
#: tickets/models/ticket.py:18 tickets/models/ticket.py:72
|
||||
#: tickets/models/ticket.py:19 tickets/models/ticket.py:75
|
||||
msgid "Open"
|
||||
msgstr "开启"
|
||||
|
||||
#: tickets/models/ticket.py:19
|
||||
#: tickets/models/ticket.py:20
|
||||
msgid "Closed"
|
||||
msgstr "关闭"
|
||||
|
||||
#: tickets/models/ticket.py:25
|
||||
#: tickets/models/ticket.py:26
|
||||
msgid "General"
|
||||
msgstr "一般"
|
||||
|
||||
#: tickets/models/ticket.py:27
|
||||
#: tickets/models/ticket.py:28
|
||||
msgid "Request asset permission"
|
||||
msgstr "申请资产权限"
|
||||
|
||||
#: tickets/models/ticket.py:32
|
||||
#: tickets/models/ticket.py:33
|
||||
msgid "Approve"
|
||||
msgstr "同意"
|
||||
|
||||
#: tickets/models/ticket.py:33
|
||||
#: tickets/models/ticket.py:34
|
||||
msgid "Reject"
|
||||
msgstr "拒绝"
|
||||
|
||||
#: tickets/models/ticket.py:36 tickets/models/ticket.py:131
|
||||
#: tickets/models/ticket.py:37 tickets/models/ticket.py:136
|
||||
msgid "User display name"
|
||||
msgstr "用户显示名称"
|
||||
|
||||
#: tickets/models/ticket.py:38
|
||||
#: tickets/models/ticket.py:39
|
||||
msgid "Title"
|
||||
msgstr "标题"
|
||||
|
||||
#: tickets/models/ticket.py:39 tickets/models/ticket.py:132
|
||||
#: tickets/models/ticket.py:40 tickets/models/ticket.py:137
|
||||
msgid "Body"
|
||||
msgstr "内容"
|
||||
|
||||
#: tickets/models/ticket.py:41
|
||||
#: tickets/models/ticket.py:42
|
||||
msgid "Assignee"
|
||||
msgstr "处理人"
|
||||
|
||||
#: tickets/models/ticket.py:42
|
||||
#: tickets/models/ticket.py:43
|
||||
msgid "Assignee display name"
|
||||
msgstr "处理人名称"
|
||||
|
||||
#: tickets/models/ticket.py:43
|
||||
#: tickets/models/ticket.py:44
|
||||
msgid "Assignees"
|
||||
msgstr "待处理人"
|
||||
|
||||
#: tickets/models/ticket.py:44
|
||||
#: tickets/models/ticket.py:45
|
||||
msgid "Assignees display name"
|
||||
msgstr "待处理人名称"
|
||||
|
||||
#: tickets/models/ticket.py:73
|
||||
#: tickets/models/ticket.py:76
|
||||
msgid "{} {} this ticket"
|
||||
msgstr "{} {} 这个工单"
|
||||
|
||||
#: tickets/models/ticket.py:84
|
||||
#: tickets/models/ticket.py:87
|
||||
msgid "this ticket"
|
||||
msgstr "这个工单"
|
||||
|
||||
#: tickets/serializers/request_asset_perm.py:12
|
||||
#: tickets/serializers/request_asset_perm.py:19
|
||||
msgid "IP group"
|
||||
msgstr "IP组"
|
||||
|
||||
#: tickets/serializers/request_asset_perm.py:24
|
||||
#: tickets/serializers/request_asset_perm.py:31
|
||||
msgid "Confirmed assets"
|
||||
msgstr "确认的资产"
|
||||
|
||||
#: tickets/serializers/request_asset_perm.py:28
|
||||
#: tickets/serializers/request_asset_perm.py:34
|
||||
msgid "Confirmed system user"
|
||||
msgstr "确认的系统用户"
|
||||
|
||||
#: tickets/serializers/request_asset_perm.py:65
|
||||
msgid "Must be organization admin or superuser"
|
||||
msgstr "必须是组织管理员或者超级管理员"
|
||||
#: tickets/serializers/request_asset_perm.py:83
|
||||
msgid "Invalid `org_id`"
|
||||
msgstr "无效的 `org_id`"
|
||||
|
||||
#: tickets/utils.py:18
|
||||
#: tickets/serializers/request_asset_perm.py:93
|
||||
msgid "Field `assignees` must be organization admin or superuser"
|
||||
msgstr "字段 assignees 必须是组织管理员或者超级管理员"
|
||||
|
||||
#: tickets/serializers/request_asset_perm.py:143
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Type: {type}<br>\n"
|
||||
" User: {username}<br>\n"
|
||||
" Ip group: {ips}<br>\n"
|
||||
" Hostname: {hostname}<br>\n"
|
||||
" System user: {system_user}<br>\n"
|
||||
" Date start: {date_start}<br>\n"
|
||||
" Date expired: {date_expired}<br>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 类型: {type}<br>\n"
|
||||
" 用户: {username}<br>\n"
|
||||
" IP 组: {ips}<br>\n"
|
||||
" 主机名: {hostname}<br>\n"
|
||||
" 系统用户: {system_user}<br>\n"
|
||||
" 开始时间: {date_start}<br>\n"
|
||||
" 过期时间: {date_expired}<br>\n"
|
||||
" "
|
||||
|
||||
#: tickets/utils.py:20
|
||||
msgid "New ticket"
|
||||
msgstr "新工单"
|
||||
|
||||
#: tickets/utils.py:21
|
||||
#: tickets/utils.py:28
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -2621,11 +2648,11 @@ msgstr ""
|
|||
" </div>\n"
|
||||
" "
|
||||
|
||||
#: tickets/utils.py:40
|
||||
#: tickets/utils.py:47
|
||||
msgid "Ticket has been reply"
|
||||
msgstr "工单已被回复"
|
||||
|
||||
#: tickets/utils.py:41
|
||||
#: tickets/utils.py:48
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -2656,7 +2683,7 @@ msgstr ""
|
|||
" </div>\n"
|
||||
" "
|
||||
|
||||
#: users/api/user.py:126
|
||||
#: users/api/user.py:147
|
||||
msgid "Could not reset self otp, use profile reset instead"
|
||||
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
|
||||
|
||||
|
@ -2702,7 +2729,7 @@ msgstr "确认密码"
|
|||
msgid "Password does not match"
|
||||
msgstr "密码不一致"
|
||||
|
||||
#: users/forms/profile.py:89 users/models/user.py:475
|
||||
#: users/forms/profile.py:89 users/models/user.py:491
|
||||
#: users/templates/users/user_detail.html:57
|
||||
#: users/templates/users/user_profile.html:59
|
||||
msgid "Email"
|
||||
|
@ -2738,12 +2765,12 @@ msgid "Public key should not be the same as your old one."
|
|||
msgstr "不能和原来的密钥相同"
|
||||
|
||||
#: users/forms/profile.py:137 users/forms/user.py:90
|
||||
#: users/serializers/user.py:185 users/serializers/user.py:266
|
||||
#: users/serializers/user.py:324
|
||||
#: users/serializers/user.py:194 users/serializers/user.py:276
|
||||
#: users/serializers/user.py:334
|
||||
msgid "Not a valid ssh public key"
|
||||
msgstr "SSH密钥不合法"
|
||||
|
||||
#: users/forms/user.py:31 users/models/user.py:518
|
||||
#: users/forms/user.py:31 users/models/user.py:534
|
||||
#: users/templates/users/user_detail.html:89
|
||||
#: users/templates/users/user_list.html:18
|
||||
#: users/templates/users/user_profile.html:102
|
||||
|
@ -2763,15 +2790,15 @@ msgstr "添加到用户组"
|
|||
msgid "* Your password does not meet the requirements"
|
||||
msgstr "* 您的密码不符合要求"
|
||||
|
||||
#: users/forms/user.py:124 users/serializers/user.py:31
|
||||
#: users/forms/user.py:124 users/serializers/user.py:36
|
||||
msgid "Reset link will be generated and sent to the user"
|
||||
msgstr "生成重置密码链接,通过邮件发送给用户"
|
||||
|
||||
#: users/forms/user.py:125 users/serializers/user.py:32
|
||||
#: users/forms/user.py:125 users/serializers/user.py:37
|
||||
msgid "Set password"
|
||||
msgstr "设置密码"
|
||||
|
||||
#: users/forms/user.py:132 users/serializers/user.py:39
|
||||
#: users/forms/user.py:132 users/serializers/user.py:44
|
||||
#: xpack/plugins/change_auth_plan/models.py:61
|
||||
#: xpack/plugins/change_auth_plan/serializers.py:30
|
||||
msgid "Password strategy"
|
||||
|
@ -2789,79 +2816,79 @@ msgstr "超级审计员"
|
|||
msgid "Application"
|
||||
msgstr "应用程序"
|
||||
|
||||
#: users/models/user.py:395 users/templates/users/user_profile.html:90
|
||||
#: users/models/user.py:411 users/templates/users/user_profile.html:90
|
||||
msgid "Force enable"
|
||||
msgstr "强制启用"
|
||||
|
||||
#: users/models/user.py:462
|
||||
#: users/models/user.py:478
|
||||
msgid "Local"
|
||||
msgstr "数据库"
|
||||
|
||||
#: users/models/user.py:486
|
||||
#: users/models/user.py:502
|
||||
msgid "Avatar"
|
||||
msgstr "头像"
|
||||
|
||||
#: users/models/user.py:489 users/templates/users/user_detail.html:68
|
||||
#: users/models/user.py:505 users/templates/users/user_detail.html:68
|
||||
msgid "Wechat"
|
||||
msgstr "微信"
|
||||
|
||||
#: users/models/user.py:522
|
||||
#: users/models/user.py:538
|
||||
msgid "Date password last updated"
|
||||
msgstr "最后更新密码日期"
|
||||
|
||||
#: users/models/user.py:631
|
||||
#: users/models/user.py:651
|
||||
msgid "Administrator"
|
||||
msgstr "管理员"
|
||||
|
||||
#: users/models/user.py:634
|
||||
#: users/models/user.py:654
|
||||
msgid "Administrator is the super user of system"
|
||||
msgstr "Administrator是初始的超级管理员"
|
||||
|
||||
#: users/serializers/user.py:72 users/serializers/user.py:237
|
||||
msgid "Is first login"
|
||||
msgstr "首次登录"
|
||||
|
||||
#: users/serializers/user.py:73
|
||||
msgid "Is valid"
|
||||
msgstr "账户是否有效"
|
||||
|
||||
#: users/serializers/user.py:74
|
||||
msgid "Is expired"
|
||||
msgstr " 是否过期"
|
||||
|
||||
#: users/serializers/user.py:75
|
||||
msgid "Avatar url"
|
||||
msgstr "头像路径"
|
||||
|
||||
#: users/serializers/user.py:79
|
||||
msgid "Groups name"
|
||||
msgstr "用户组名"
|
||||
|
||||
#: users/serializers/user.py:80
|
||||
msgid "Source name"
|
||||
msgstr "用户来源名"
|
||||
|
||||
#: users/serializers/user.py:81
|
||||
#: users/serializers/user.py:54 users/serializers/user.py:90
|
||||
msgid "Organization role name"
|
||||
msgstr "组织角色名称"
|
||||
|
||||
#: users/serializers/user.py:81 users/serializers/user.py:247
|
||||
msgid "Is first login"
|
||||
msgstr "首次登录"
|
||||
|
||||
#: users/serializers/user.py:82
|
||||
msgid "Is valid"
|
||||
msgstr "账户是否有效"
|
||||
|
||||
#: users/serializers/user.py:83
|
||||
msgid "Is expired"
|
||||
msgstr " 是否过期"
|
||||
|
||||
#: users/serializers/user.py:84
|
||||
msgid "Avatar url"
|
||||
msgstr "头像路径"
|
||||
|
||||
#: users/serializers/user.py:88
|
||||
msgid "Groups name"
|
||||
msgstr "用户组名"
|
||||
|
||||
#: users/serializers/user.py:89
|
||||
msgid "Source name"
|
||||
msgstr "用户来源名"
|
||||
|
||||
#: users/serializers/user.py:91
|
||||
msgid "Super role name"
|
||||
msgstr "超级角色名称"
|
||||
|
||||
#: users/serializers/user.py:105
|
||||
#: users/serializers/user.py:114
|
||||
msgid "Role limit to {}"
|
||||
msgstr "角色只能为 {}"
|
||||
|
||||
#: users/serializers/user.py:117 users/serializers/user.py:290
|
||||
#: users/serializers/user.py:126 users/serializers/user.py:300
|
||||
msgid "Password does not match security rules"
|
||||
msgstr "密码不满足安全规则"
|
||||
|
||||
#: users/serializers/user.py:282
|
||||
#: users/serializers/user.py:292
|
||||
msgid "The old password is incorrect"
|
||||
msgstr "旧密码错误"
|
||||
|
||||
#: users/serializers/user.py:296
|
||||
#: users/serializers/user.py:306
|
||||
msgid "The newly set password is inconsistent"
|
||||
msgstr "两次密码不一致"
|
||||
|
||||
|
@ -3992,18 +4019,15 @@ msgstr "旗舰版"
|
|||
#~ msgid "Role name"
|
||||
#~ msgstr "角色名"
|
||||
|
||||
#~ msgid "GUI copy"
|
||||
#~ msgstr "GUI 复制"
|
||||
|
||||
#~ msgid "GUI paste"
|
||||
#~ msgstr "GUI 粘贴"
|
||||
|
||||
#~ msgid "Covered always"
|
||||
#~ msgstr "总是被覆盖"
|
||||
|
||||
#~ msgid "Account name"
|
||||
#~ msgstr "账户名称"
|
||||
|
||||
#~ msgid "Superuser"
|
||||
#~ msgstr "超级管理员"
|
||||
|
||||
#~ msgid "Auditors cannot be join in the user group"
|
||||
#~ msgstr "审计员不能被加入到用户组"
|
||||
|
||||
|
|
|
@ -62,7 +62,6 @@ class OrgModelMixin(models.Model):
|
|||
org = get_current_org()
|
||||
if org is None:
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
if org.is_real() or org.is_system():
|
||||
self.org_id = org.id
|
||||
elif org.is_default():
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from django.db.transaction import atomic
|
||||
from django.db.models import F
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.request import Request
|
||||
|
||||
from orgs.models import Organization, ROLE as ORG_ROLE
|
||||
from users.models.user import User
|
||||
from common.const.http import POST, GET
|
||||
from common.drf.api import JMSModelViewSet
|
||||
from common.permissions import IsValidUser
|
||||
from common.utils.django import get_object_or_none
|
||||
from common.utils.timezone import dt_parser
|
||||
from common.drf.serializers import EmptySerializer
|
||||
from perms.models.asset_permission import AssetPermission, Asset
|
||||
from assets.models.user import SystemUser
|
||||
from ..exceptions import (
|
||||
ConfirmedAssetsChanged, ConfirmedSystemUserChanged,
|
||||
TicketClosed, TicketActionYet, NotHaveConfirmedAssets,
|
||||
TicketClosed, TicketActionAlready, NotHaveConfirmedAssets,
|
||||
NotHaveConfirmedSystemUser
|
||||
)
|
||||
from .. import serializers
|
||||
|
@ -25,15 +26,15 @@ from ..permissions import IsAssignee
|
|||
|
||||
|
||||
class RequestAssetPermTicketViewSet(JMSModelViewSet):
|
||||
queryset = Ticket.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,
|
||||
'reject': EmptySerializer,
|
||||
'assignees': serializers.OrgAssigneeSerializer,
|
||||
'assignees': serializers.AssigneeSerializer,
|
||||
}
|
||||
permission_classes = (IsValidUser,)
|
||||
filter_fields = ['status', 'title', 'action', 'user_display']
|
||||
filter_fields = ['status', 'title', 'action', 'user_display', 'org_id']
|
||||
search_fields = ['user_display', 'title']
|
||||
|
||||
def _check_can_set_action(self, instance, action):
|
||||
|
@ -41,49 +42,39 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
|
|||
raise TicketClosed(detail=_('Ticket closed'))
|
||||
if instance.action == action:
|
||||
action_display = dict(instance.ACTION_CHOICES).get(action)
|
||||
raise TicketActionYet(detail=_('Ticket has %s') % action_display)
|
||||
raise TicketActionAlready(detail=_('Ticket has %s') % action_display)
|
||||
|
||||
@action(detail=False, methods=[GET], permission_classes=[IsValidUser])
|
||||
def assignees(self, request, *args, **kwargs):
|
||||
org_mapper = {}
|
||||
UserTuple = namedtuple('UserTuple', ('id', 'name', 'username'))
|
||||
def assignees(self, request: Request, *args, **kwargs):
|
||||
user = request.user
|
||||
superusers = User.objects.filter(role=User.ROLE.ADMIN)
|
||||
org_id = request.query_params.get('org_id', Organization.DEFAULT_ID)
|
||||
|
||||
admins_with_org = User.objects.filter(related_admin_orgs__users=user).annotate(
|
||||
org_id=F('related_admin_orgs__id'), org_name=F('related_admin_orgs__name')
|
||||
)
|
||||
q = Q(role=User.ROLE.ADMIN)
|
||||
if org_id != Organization.DEFAULT_ID:
|
||||
q |= Q(m2m_org_members__role=ORG_ROLE.ADMIN, orgs__id=org_id, orgs__members=user)
|
||||
org_admins = User.objects.filter(q).distinct()
|
||||
|
||||
for user in admins_with_org:
|
||||
org_id = user.org_id
|
||||
return self.get_paginated_response_with_query_set(org_admins)
|
||||
|
||||
if org_id not in org_mapper:
|
||||
org_mapper[org_id] = {
|
||||
'org_name': user.org_name,
|
||||
'org_admins': set() # 去重
|
||||
}
|
||||
org_mapper[org_id]['org_admins'].add(UserTuple(user.id, user.name, user.username))
|
||||
def _get_extra_comment(self, instance):
|
||||
meta = instance.meta
|
||||
ips = ', '.join(meta.get('ips', []))
|
||||
confirmed_assets = ', '.join(meta.get('confirmed_assets', []))
|
||||
|
||||
result = [
|
||||
{
|
||||
'org_name': _('Superuser'),
|
||||
'org_admins': set(UserTuple(user.id, user.name, user.username)
|
||||
for user in superusers)
|
||||
}
|
||||
]
|
||||
|
||||
for org in org_mapper.values():
|
||||
result.append(org)
|
||||
serializer_class = self.get_serializer_class()
|
||||
serilizer = serializer_class(instance=result, many=True)
|
||||
return Response(data=serilizer.data)
|
||||
return f'''
|
||||
{_('IP group')}: {ips}
|
||||
{_('Hostname')}: {meta.get('hostname', '')}
|
||||
{_('System user')}: {meta.get('system_user', '')}
|
||||
{_('Confirmed assets')}: {confirmed_assets}
|
||||
{_('Confirmed system user')}: {meta.get('confirmed_system_user', '')}
|
||||
'''
|
||||
|
||||
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
|
||||
def reject(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
action = instance.ACTION_REJECT
|
||||
self._check_can_set_action(instance, action)
|
||||
instance.perform_action(action, request.user)
|
||||
instance.perform_action(action, request.user, self._get_extra_comment(instance))
|
||||
return Response()
|
||||
|
||||
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
|
||||
|
@ -109,29 +100,33 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
|
|||
if system_user is None:
|
||||
raise ConfirmedSystemUserChanged(detail=_('Confirmed system-user changed'))
|
||||
|
||||
self._create_asset_permission(instance, assets, system_user)
|
||||
self._create_asset_permission(instance, assets, system_user, request.user)
|
||||
return Response({'detail': _('Succeed')})
|
||||
|
||||
def _create_asset_permission(self, instance: Ticket, assets, system_user):
|
||||
def _create_asset_permission(self, instance: Ticket, assets, system_user, user):
|
||||
meta = instance.meta
|
||||
request = self.request
|
||||
|
||||
ap_kwargs = {
|
||||
'name': meta.get('name', ''),
|
||||
'name': _('From request ticket: {} {}').format(instance.user_display, instance.id),
|
||||
'created_by': self.request.user.username,
|
||||
'comment': _('{} request assets, approved by {}').format(instance.user_display,
|
||||
instance.assignee_display)
|
||||
instance.assignees_display)
|
||||
}
|
||||
date_start = meta.get('date_start')
|
||||
date_expired = meta.get('date_expired')
|
||||
date_start = dt_parser(meta.get('date_start'))
|
||||
date_expired = dt_parser(meta.get('date_expired'))
|
||||
if date_start:
|
||||
ap_kwargs['date_start'] = date_start
|
||||
if date_expired:
|
||||
ap_kwargs['date_expired'] = date_expired
|
||||
|
||||
with atomic():
|
||||
instance.perform_action(instance.ACTION_APPROVE, request.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(user)
|
||||
|
||||
return ap
|
||||
|
|
|
@ -11,7 +11,7 @@ from .. import serializers, models, mixins
|
|||
|
||||
class TicketViewSet(mixins.TicketMixin, viewsets.ModelViewSet):
|
||||
serializer_class = serializers.TicketSerializer
|
||||
queryset = models.Ticket.objects.all()
|
||||
queryset = models.Ticket.origin_objects.all()
|
||||
permission_classes = (IsValidUser,)
|
||||
filter_fields = ['status', 'title', 'action', 'user_display']
|
||||
search_fields = ['user_display', 'title']
|
||||
|
|
|
@ -21,5 +21,9 @@ class TicketClosed(JMSException):
|
|||
pass
|
||||
|
||||
|
||||
class TicketActionYet(JMSException):
|
||||
class TicketActionAlready(JMSException):
|
||||
pass
|
||||
|
||||
|
||||
class OrgIdRequiredException(JMSException):
|
||||
pass
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 2.2.10 on 2020-07-23 04:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ticket',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm'), ('request_asset', 'Request asset permission')], default='general', max_length=16, verbose_name='Type'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 2.2.10 on 2020-07-28 03:46
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.manager
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelManagers(
|
||||
name='ticket',
|
||||
managers=[
|
||||
('origin_objects', django.db.models.manager.Manager()),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ticket',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm'), ('request_asset', 'Request asset permission')], default='general', max_length=16, verbose_name='Type'),
|
||||
),
|
||||
]
|
|
@ -6,11 +6,12 @@ from .models import Ticket
|
|||
|
||||
class TicketMixin:
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
assign = self.request.GET.get('assign', None)
|
||||
if assign is None:
|
||||
queryset = Ticket.get_related_tickets(self.request.user)
|
||||
queryset = Ticket.get_related_tickets(self.request.user, queryset)
|
||||
elif assign in ['1']:
|
||||
queryset = Ticket.get_assigned_tickets(self.request.user)
|
||||
queryset = Ticket.get_assigned_tickets(self.request.user, queryset)
|
||||
else:
|
||||
queryset = Ticket.get_my_tickets(self.request.user)
|
||||
queryset = Ticket.get_my_tickets(self.request.user, queryset)
|
||||
return queryset
|
||||
|
|
|
@ -7,11 +7,12 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from common.mixins.models import CommonModelMixin
|
||||
from common.fields.model import JsonDictTextField
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
__all__ = ['Ticket', 'Comment']
|
||||
|
||||
|
||||
class Ticket(CommonModelMixin):
|
||||
class Ticket(OrgModelMixin, CommonModelMixin):
|
||||
STATUS_OPEN = 'open'
|
||||
STATUS_CLOSED = 'closed'
|
||||
STATUS_CHOICES = (
|
||||
|
@ -46,6 +47,8 @@ class Ticket(CommonModelMixin):
|
|||
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()
|
||||
|
||||
def __str__(self):
|
||||
return '{}: {}'.format(self.user_display, self.title)
|
||||
|
||||
|
@ -79,13 +82,15 @@ class Ticket(CommonModelMixin):
|
|||
self.status = status
|
||||
self.save()
|
||||
|
||||
def create_action_comment(self, action, user):
|
||||
def create_action_comment(self, action, user, extra_comment=None):
|
||||
action_display = dict(self.ACTION_CHOICES).get(action)
|
||||
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):
|
||||
self.create_action_comment(action, user)
|
||||
def perform_action(self, action, user, extra_comment=None):
|
||||
self.create_action_comment(action, user, extra_comment)
|
||||
self.action = action
|
||||
self.status = self.STATUS_CLOSED
|
||||
self.assignee = user
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
from itertools import chain
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse
|
||||
from django.db.models import Q
|
||||
|
||||
from common.utils.timezone import dt_parser, dt_formater
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from orgs.models import Organization, ROLE as ORG_ROLE
|
||||
from assets.models.asset import Asset
|
||||
from users.models.user import User
|
||||
from ..models import Ticket
|
||||
|
||||
|
@ -22,9 +29,8 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
|||
source='meta.confirmed_assets',
|
||||
default=list, required=False,
|
||||
label=_('Confirmed assets'))
|
||||
confirmed_system_user = serializers.ListField(child=serializers.UUIDField(),
|
||||
source='meta.confirmed_system_user',
|
||||
default=list, required=False,
|
||||
confirmed_system_user = serializers.UUIDField(source='meta.confirmed_system_user',
|
||||
default='', required=False,
|
||||
label=_('Confirmed system user'))
|
||||
assets_waitlist_url = serializers.SerializerMethodField()
|
||||
system_user_waitlist_url = serializers.SerializerMethodField()
|
||||
|
@ -36,7 +42,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
|||
'status', 'action', 'date_created', 'date_updated', 'system_user_waitlist_url',
|
||||
'type', 'type_display', 'action_display', 'ips', 'confirmed_assets',
|
||||
'date_start', 'date_expired', 'confirmed_system_user', 'hostname',
|
||||
'assets_waitlist_url', 'system_user'
|
||||
'assets_waitlist_url', 'system_user', 'org_id'
|
||||
]
|
||||
m2m_fields = [
|
||||
'user', 'user_display', 'assignees', 'assignees_display',
|
||||
|
@ -52,26 +58,44 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
|||
extra_kwargs = {
|
||||
'status': {'label': _('Status')},
|
||||
'action': {'label': _('Action')},
|
||||
'user_display': {'label': _('User')}
|
||||
'user_display': {'label': _('User')},
|
||||
'org_id': {'required': True}
|
||||
}
|
||||
|
||||
def validate_assignees(self, assignees):
|
||||
def validate(self, attrs):
|
||||
org_id = attrs.get('org_id')
|
||||
assignees = attrs.get('assignees')
|
||||
|
||||
instance = self.instance
|
||||
if instance is not None:
|
||||
if org_id and not assignees:
|
||||
assignees = list(instance.assignees.all())
|
||||
elif assignees and not org_id:
|
||||
org_id = instance.org_id
|
||||
elif assignees and org_id:
|
||||
pass
|
||||
else:
|
||||
return attrs
|
||||
|
||||
user = self.context['request'].user
|
||||
org = Organization.get_instance(org_id)
|
||||
if org is None:
|
||||
raise serializers.ValidationError(_('Invalid `org_id`'))
|
||||
|
||||
count = User.objects.filter(Q(related_admin_orgs__users=user) | Q(role=User.ROLE.ADMIN)).filter(
|
||||
id__in=[assignee.id for assignee in assignees]).distinct().count()
|
||||
q = Q(role=User.ROLE.ADMIN)
|
||||
if not org.is_default():
|
||||
q |= Q(m2m_org_members__role=ORG_ROLE.ADMIN, orgs__id=org_id, orgs__members=user)
|
||||
|
||||
q &= Q(id__in=[assignee.id for assignee in assignees])
|
||||
count = User.objects.filter(q).distinct().count()
|
||||
if count != len(assignees):
|
||||
raise serializers.ValidationError(_('Must be organization admin or superuser'))
|
||||
return assignees
|
||||
raise serializers.ValidationError(_('Field `assignees` must be organization admin or superuser'))
|
||||
return attrs
|
||||
|
||||
def get_system_user_waitlist_url(self, instance: Ticket):
|
||||
if not self._is_assignee(instance):
|
||||
return None
|
||||
meta = instance.meta
|
||||
url = reverse('api-assets:system-user-list')
|
||||
query = meta.get('system_user', '')
|
||||
return '{}?search={}'.format(url, query)
|
||||
return reverse('api-assets:system-user-list')
|
||||
|
||||
def get_assets_waitlist_url(self, instance: Ticket):
|
||||
if not self._is_assignee(instance):
|
||||
|
@ -81,37 +105,106 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
|||
query = ''
|
||||
|
||||
meta = instance.meta
|
||||
ips = meta.get('ips', [])
|
||||
hostname = meta.get('hostname')
|
||||
|
||||
if ips:
|
||||
query = '?ips=%s' % ','.join(ips)
|
||||
elif hostname:
|
||||
if hostname:
|
||||
query = '?search=%s' % hostname
|
||||
|
||||
return asset_api + query
|
||||
|
||||
def _recommend_assets(self, data, instance):
|
||||
confirmed_assets = data.get('confirmed_assets')
|
||||
if not confirmed_assets and self._is_assignee(instance):
|
||||
ips = data.get('ips')
|
||||
hostname = data.get('hostname')
|
||||
limit = 5
|
||||
|
||||
q = Q(id=None)
|
||||
if ips:
|
||||
limit = len(ips) + 2
|
||||
q |= Q(ip__in=ips)
|
||||
if hostname:
|
||||
q |= Q(hostname__icontains=hostname)
|
||||
|
||||
data['confirmed_assets'] = list(
|
||||
map(lambda x: str(x), chain(*Asset.objects.filter(q)[0: limit].values_list('id'))))
|
||||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
self._recommend_assets(data, instance)
|
||||
return data
|
||||
|
||||
def _create_body(self, validated_data):
|
||||
meta = validated_data['meta']
|
||||
type = dict(Ticket.TYPE_CHOICES).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)
|
||||
|
||||
validated_data['body'] = _('''
|
||||
Type: {type}<br>
|
||||
User: {username}<br>
|
||||
Ip group: {ips}<br>
|
||||
Hostname: {hostname}<br>
|
||||
System user: {system_user}<br>
|
||||
Date start: {date_start}<br>
|
||||
Date expired: {date_expired}<br>
|
||||
''').format(
|
||||
type=type,
|
||||
username=validated_data.get('user', ''),
|
||||
ips=', '.join(meta.get('ips', [])),
|
||||
hostname=meta.get('hostname', ''),
|
||||
system_user=meta.get('system_user', ''),
|
||||
date_start=date_start,
|
||||
date_expired=date_expired
|
||||
)
|
||||
|
||||
def create(self, validated_data):
|
||||
# `type` 与 `user` 用户不可提交,
|
||||
validated_data['type'] = self.Meta.model.TYPE_REQUEST_ASSET_PERM
|
||||
validated_data['user'] = self.context['request'].user
|
||||
# `confirmed` 相关字段只能审批人修改,所以创建时直接清理掉
|
||||
self._pop_confirmed_fields()
|
||||
self._create_body(validated_data)
|
||||
return super().create(validated_data)
|
||||
|
||||
def save(self, **kwargs):
|
||||
"""
|
||||
做了一些数据转换
|
||||
"""
|
||||
meta = self.validated_data.get('meta', {})
|
||||
|
||||
org_id = self.validated_data.get('org_id')
|
||||
if org_id is not None and org_id == Organization.DEFAULT_ID:
|
||||
self.validated_data['org_id'] = ''
|
||||
|
||||
# 时间的转换,好烦😭,可能有更好的办法吧
|
||||
date_start = meta.get('date_start')
|
||||
if date_start:
|
||||
meta['date_start'] = date_start.strftime('%Y-%m-%d %H:%M:%S%z')
|
||||
meta['date_start'] = dt_formater(date_start)
|
||||
|
||||
date_expired = meta.get('date_expired')
|
||||
if date_expired:
|
||||
meta['date_expired'] = date_expired.strftime('%Y-%m-%d %H:%M:%S%z')
|
||||
return super().save(**kwargs)
|
||||
meta['date_expired'] = dt_formater(date_expired)
|
||||
|
||||
# UUID 的转换
|
||||
confirmed_system_user = meta.get('confirmed_system_user')
|
||||
if confirmed_system_user:
|
||||
meta['confirmed_system_user'] = str(confirmed_system_user)
|
||||
|
||||
confirmed_assets = meta.get('confirmed_assets')
|
||||
if confirmed_assets:
|
||||
new_confirmed_assets = []
|
||||
for asset in confirmed_assets:
|
||||
new_confirmed_assets.append(str(asset))
|
||||
meta['confirmed_assets'] = new_confirmed_assets
|
||||
with tmp_to_root_org():
|
||||
return super().save(**kwargs)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
new_meta = validated_data['meta']
|
||||
if not self._is_assignee(instance):
|
||||
self._pop_confirmed_fields()
|
||||
|
||||
# Json 字段保存的坑😭
|
||||
old_meta = instance.meta
|
||||
meta = {}
|
||||
meta.update(old_meta)
|
||||
|
@ -134,8 +227,3 @@ class AssigneeSerializer(serializers.Serializer):
|
|||
id = serializers.UUIDField()
|
||||
name = serializers.CharField()
|
||||
username = serializers.CharField()
|
||||
|
||||
|
||||
class OrgAssigneeSerializer(serializers.Serializer):
|
||||
org_name = serializers.CharField()
|
||||
org_admins = AssigneeSerializer(many=True)
|
||||
|
|
|
@ -1,3 +1,89 @@
|
|||
from django.test import TestCase
|
||||
import datetime
|
||||
|
||||
# Create your tests here.
|
||||
from common.utils.timezone import now
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
|
||||
from orgs.models import Organization, OrganizationMember, ROLE as ORG_ROLE
|
||||
from orgs.utils import set_current_org
|
||||
from users.models.user import User
|
||||
from assets.models import Asset, AdminUser, SystemUser
|
||||
|
||||
|
||||
class TicketTest(APITestCase):
|
||||
def setUp(self):
|
||||
Organization.objects.bulk_create([
|
||||
Organization(name='org-01'),
|
||||
Organization(name='org-02'),
|
||||
Organization(name='org-03'),
|
||||
])
|
||||
org_01, org_02, org_03 = Organization.objects.all()
|
||||
self.org_01, self.org_02, self.org_03 = org_01, org_02, org_03
|
||||
|
||||
set_current_org(org_01)
|
||||
|
||||
AdminUser.objects.bulk_create([
|
||||
AdminUser(name='au-01', username='au-01'),
|
||||
AdminUser(name='au-02', username='au-02'),
|
||||
AdminUser(name='au-03', username='au-03'),
|
||||
])
|
||||
|
||||
SystemUser.objects.bulk_create([
|
||||
SystemUser(name='su-01', username='su-01'),
|
||||
SystemUser(name='su-02', username='su-02'),
|
||||
SystemUser(name='su-03', username='su-03'),
|
||||
])
|
||||
|
||||
admin_users = AdminUser.objects.all()
|
||||
Asset.objects.bulk_create([
|
||||
Asset(hostname='asset-01', ip='192.168.1.1', public_ip='192.168.1.1', admin_user=admin_users[0]),
|
||||
Asset(hostname='asset-02', ip='192.168.1.2', public_ip='192.168.1.2', admin_user=admin_users[0]),
|
||||
Asset(hostname='asset-03', ip='192.168.1.3', public_ip='192.168.1.3', admin_user=admin_users[0]),
|
||||
])
|
||||
|
||||
new_user = User.objects.create
|
||||
new_org_memeber = OrganizationMember.objects.create
|
||||
|
||||
u = new_user(name='user-01', username='user-01', email='user-01@jms.com')
|
||||
new_org_memeber(org=org_01, user=u, role=ORG_ROLE.USER)
|
||||
new_org_memeber(org=org_02, user=u, role=ORG_ROLE.USER)
|
||||
self.user_01 = u
|
||||
|
||||
u = new_user(name='org-admin-01', username='org-admin-01', email='org-admin-01@jms.com')
|
||||
new_org_memeber(org=org_01, user=u, role=ORG_ROLE.ADMIN)
|
||||
self.org_admin_01 = u
|
||||
|
||||
u = new_user(name='org-admin-02', username='org-admin-02', email='org-admin-02@jms.com')
|
||||
new_org_memeber(org=org_02, user=u, role=ORG_ROLE.ADMIN)
|
||||
self.org_admin_02 = u
|
||||
|
||||
def test_create_request_asset_perm(self):
|
||||
url = reverse('api-tickets:ticket-request-asset-perm')
|
||||
ticket_url = reverse('api-tickets:ticket')
|
||||
|
||||
self.client.force_login(self.user_01)
|
||||
|
||||
date_start = now()
|
||||
date_expired = date_start + datetime.timedelta(days=7)
|
||||
|
||||
data = {
|
||||
"title": "request-01",
|
||||
"ips": [
|
||||
"192.168.1.1"
|
||||
],
|
||||
"date_start": date_start,
|
||||
"date_expired": date_expired,
|
||||
"hostname": "",
|
||||
"system_user": "",
|
||||
"org_id": self.org_01.id,
|
||||
"assignees": [
|
||||
str(self.org_admin_01.id),
|
||||
str(self.org_admin_02.id),
|
||||
]
|
||||
}
|
||||
|
||||
self.client.post(data)
|
||||
|
||||
self.client.force_login(self.org_admin_01)
|
||||
res = self.client.get(ticket_url, params={'assgin': 1})
|
||||
|
|
|
@ -7,7 +7,7 @@ from .. import api
|
|||
app_name = 'tickets'
|
||||
router = BulkRouter()
|
||||
|
||||
# router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
|
||||
router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
|
||||
router.register('tickets', api.TicketViewSet, 'ticket')
|
||||
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
|
||||
|
||||
|
|
|
@ -1,23 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from urllib.parse import urljoin
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_logger, reverse
|
||||
from common.utils import get_logger
|
||||
from common.tasks import send_mail_async
|
||||
|
||||
logger = get_logger(__name__)
|
||||
from tickets.models import Ticket
|
||||
|
||||
|
||||
def send_new_ticket_mail_to_assignees(ticket, assignees):
|
||||
def send_new_ticket_mail_to_assignees(ticket: Ticket, assignees):
|
||||
recipient_list = [user.email for user in assignees]
|
||||
user = ticket.user
|
||||
if not recipient_list:
|
||||
logger.error("Ticket not has assignees: {}".format(ticket.id))
|
||||
return
|
||||
subject = '{}: {}'.format(_("New ticket"), ticket.title)
|
||||
detail_url = reverse('tickets:ticket-detail',
|
||||
kwargs={'pk': ticket.id}, external=True)
|
||||
|
||||
# 这里要设置前端地址,因为要直接跳转到页面
|
||||
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}')
|
||||
|
||||
message = _("""
|
||||
<div>
|
||||
<p>Your has a new ticket</p>
|
||||
|
|
|
@ -233,6 +233,11 @@ class RoleMixin:
|
|||
def is_app(self):
|
||||
return self.role == self.ROLE.APP
|
||||
|
||||
@lazyproperty
|
||||
def user_all_orgs(self):
|
||||
from orgs.models import Organization
|
||||
return Organization.get_user_all_orgs(self)
|
||||
|
||||
@lazyproperty
|
||||
def user_orgs(self):
|
||||
from orgs.models import Organization
|
||||
|
|
|
@ -27,6 +27,11 @@ class UserOrgSerializer(serializers.Serializer):
|
|||
name = serializers.CharField()
|
||||
|
||||
|
||||
class UserOrgLabelSerializer(serializers.Serializer):
|
||||
value = serializers.CharField(source='id')
|
||||
label = serializers.CharField(source='name')
|
||||
|
||||
|
||||
class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
||||
EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user')
|
||||
CUSTOM_PASSWORD = _('Set password')
|
||||
|
@ -214,6 +219,7 @@ class UserRoleSerializer(serializers.Serializer):
|
|||
|
||||
class UserProfileSerializer(UserSerializer):
|
||||
admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True)
|
||||
user_all_orgs = UserOrgLabelSerializer(many=True, read_only=True)
|
||||
current_org_roles = serializers.ListField(read_only=True)
|
||||
public_key_comment = serializers.CharField(
|
||||
source='get_public_key_comment', required=False, read_only=True, max_length=128
|
||||
|
@ -231,7 +237,7 @@ class UserProfileSerializer(UserSerializer):
|
|||
class Meta(UserSerializer.Meta):
|
||||
fields = UserSerializer.Meta.fields + [
|
||||
'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles',
|
||||
'guide_url'
|
||||
'guide_url', 'user_all_orgs'
|
||||
]
|
||||
extra_kwargs = dict(UserSerializer.Meta.extra_kwargs)
|
||||
extra_kwargs.update({
|
||||
|
|
Loading…
Reference in New Issue