feat(ticket): 调整申请资产工单

pull/4422/head
xinwen 2020-07-09 15:41:02 +08:00 committed by 老广
parent b331730422
commit f8e248f0af
21 changed files with 522 additions and 232 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

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-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 "审计员不能被加入到用户组"

View File

@ -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():

View File

@ -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

View File

@ -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']

View File

@ -21,5 +21,9 @@ class TicketClosed(JMSException):
pass
class TicketActionYet(JMSException):
class TicketActionAlready(JMSException):
pass
class OrgIdRequiredException(JMSException):
pass

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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})

View File

@ -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')

View File

@ -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>

View File

@ -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

View File

@ -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({