Merge pull request #5485 from jumpserver/dev

Dev
pull/5556/head
Jiangjie.Bai 2021-01-19 20:10:24 +08:00 committed by GitHub
commit ee22006683
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 590 additions and 422 deletions

View File

@ -18,22 +18,28 @@ class ApplicationSerializerMixin(serializers.Serializer):
attrs = MethodSerializer() attrs = MethodSerializer()
def get_attrs_serializer(self): def get_attrs_serializer(self):
serializer_class = None default_serializer = serializers.Serializer(read_only=True)
if isinstance(self.instance, models.Application): if isinstance(self.instance, models.Application):
instance_type = self.instance.type _type = self.instance.type
serializer_class = type_serializer_classes_mapping.get(instance_type) _category = self.instance.category
else: else:
request = self.context['request'] _type = self.context['request'].query_params.get('type')
query_type = request.query_params.get('type') _category = self.context['request'].query_params.get('category')
query_category = request.query_params.get('category')
if query_type:
serializer_class = type_serializer_classes_mapping.get(query_type)
elif query_category:
serializer_class = category_serializer_classes_mapping.get(query_category)
if serializer_class is None: if _type:
serializer_class = serializers.Serializer serializer_class = type_serializer_classes_mapping.get(_type)
elif _category:
serializer_class = category_serializer_classes_mapping.get(_category)
else:
serializer_class = default_serializer
if not serializer_class:
serializer_class = default_serializer
if isinstance(serializer_class, type):
serializer = serializer_class() serializer = serializer_class()
else:
serializer = serializer_class
return serializer return serializer

View File

@ -28,7 +28,7 @@ logger = get_logger(__name__)
class AssetUserFilterBackend(filters.BaseFilterBackend): class AssetUserFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
kwargs = {} kwargs = {}
for field in view.filter_fields: for field in view.filterset_fields:
value = request.GET.get(field) value = request.GET.get(field)
if not value: if not value:
continue continue

View File

@ -2,7 +2,7 @@
# #
from rest_framework import serializers from rest_framework import serializers
from django.db.models import F from django.db.models import F
from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
@ -177,6 +177,14 @@ class AssetDisplaySerializer(AssetSerializer):
class PlatformSerializer(serializers.ModelSerializer): class PlatformSerializer(serializers.ModelSerializer):
meta = serializers.DictField(required=False, allow_null=True) meta = serializers.DictField(required=False, allow_null=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# TODO 修复 drf SlugField RegexValidator bug之后记得删除
validators = self.fields['name'].validators
if isinstance(validators[-1], RegexValidator):
validators.pop()
class Meta: class Meta:
model = Platform model = Platform
fields = [ fields = [

View File

@ -86,8 +86,7 @@ class CommandExecutionSerializer(serializers.ModelSerializer):
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
queryset = queryset.annotate(user_display=F('user__name'))\ queryset = queryset.prefetch_related('user', 'run_as', 'hosts')
.annotate(run_as_display=F('run_as__name'))
return queryset return queryset

View File

@ -4,7 +4,6 @@ import uuid
from django.core.cache import cache from django.core.cache import cache
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView

View File

@ -73,9 +73,8 @@ class SimpleMetadataWithFilters(SimpleMetadata):
elif getattr(field, 'fields', None): elif getattr(field, 'fields', None):
field_info['children'] = self.get_serializer_info(field) field_info['children'] = self.get_serializer_info(field)
if (not field_info.get('read_only') and if not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) \
not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and and hasattr(field, 'choices'):
hasattr(field, 'choices')):
field_info['choices'] = [ field_info['choices'] = [
{ {
'value': choice_value, 'value': choice_value,

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-17 16:12+0800\n" "POT-Creation-Date: 2021-01-19 20:03+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -58,23 +58,22 @@ msgid "Name"
msgstr "名称" msgstr "名称"
#: applications/models/application.py:12 #: applications/models/application.py:12
#: applications/serializers/application.py:41 assets/models/label.py:21 #: applications/serializers/application.py:47 assets/models/label.py:21
#: perms/models/application_permission.py:20 #: perms/models/application_permission.py:20
#: perms/serializers/application/permission.py:16 #: perms/serializers/application/permission.py:16
#: perms/serializers/application/user_permission.py:33 #: perms/serializers/application/user_permission.py:33
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:20
msgid "Category" msgid "Category"
msgstr "种类" msgstr "种类"
#: applications/models/application.py:15 #: applications/models/application.py:15
#: applications/serializers/application.py:42 assets/models/cmd_filter.py:52 #: applications/serializers/application.py:48 assets/models/cmd_filter.py:52
#: perms/models/application_permission.py:23 #: perms/models/application_permission.py:23
#: perms/serializers/application/permission.py:17 #: perms/serializers/application/permission.py:17
#: perms/serializers/application/user_permission.py:34 #: perms/serializers/application/user_permission.py:34
#: terminal/models/storage.py:18 terminal/models/storage.py:58 #: terminal/models/storage.py:18 terminal/models/storage.py:58
#: tickets/models/ticket.py:38 #: tickets/models/ticket.py:38
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:25 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:27
#: tickets/serializers/ticket/ticket.py:19
#: users/templates/users/user_granted_database_app.html:35 #: users/templates/users/user_granted_database_app.html:35
msgid "Type" msgid "Type"
msgstr "类型" msgstr "类型"
@ -269,7 +268,7 @@ msgstr "主机名"
#: assets/models/asset.py:194 assets/models/domain.py:54 #: assets/models/asset.py:194 assets/models/domain.py:54
#: assets/models/user.py:120 terminal/serializers/session.py:29 #: assets/models/user.py:120 terminal/serializers/session.py:29
#: terminal/serializers/storage.py:59 #: terminal/serializers/storage.py:68
msgid "Protocol" msgid "Protocol"
msgstr "协议" msgstr "协议"
@ -293,7 +292,7 @@ msgstr "激活"
#: assets/models/asset.py:203 assets/models/cluster.py:19 #: assets/models/asset.py:203 assets/models/cluster.py:19
#: assets/models/user.py:66 templates/_nav.html:44 #: assets/models/user.py:66 templates/_nav.html:44
#: xpack/plugins/cloud/models.py:143 xpack/plugins/cloud/serializers.py:126 #: xpack/plugins/cloud/models.py:143 xpack/plugins/cloud/serializers.py:137
msgid "Admin user" msgid "Admin user"
msgstr "管理用户" msgstr "管理用户"
@ -508,8 +507,7 @@ msgstr "每行一个命令"
#: assets/models/cmd_filter.py:56 audits/models.py:57 #: assets/models/cmd_filter.py:56 audits/models.py:57
#: authentication/templates/authentication/_access_key_modal.html:34 #: authentication/templates/authentication/_access_key_modal.html:34
#: tickets/models/ticket.py:43 tickets/serializers/ticket/ticket.py:20 #: tickets/models/ticket.py:43 users/templates/users/_granted_assets.html:29
#: users/templates/users/_granted_assets.html:29
#: users/templates/users/user_asset_permission.html:44 #: users/templates/users/user_asset_permission.html:44
#: users/templates/users/user_asset_permission.html:79 #: users/templates/users/user_asset_permission.html:79
#: users/templates/users/user_database_app_permission.html:42 #: users/templates/users/user_database_app_permission.html:42
@ -604,7 +602,7 @@ msgstr "ssh私钥"
#: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:73
#: users/templates/users/user_asset_permission.html:158 #: users/templates/users/user_asset_permission.html:158
#: xpack/plugins/cloud/models.py:139 xpack/plugins/cloud/serializers.py:127 #: xpack/plugins/cloud/models.py:139 xpack/plugins/cloud/serializers.py:138
msgid "Node" msgid "Node"
msgstr "节点" msgstr "节点"
@ -996,10 +994,10 @@ msgstr "成功"
#: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52 #: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52
#: terminal/models/session.py:51 #: terminal/models/session.py:51
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:41 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:43
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:69 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:74
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:38 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:40
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:73 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:78
#: xpack/plugins/change_auth_plan/models.py:177 #: xpack/plugins/change_auth_plan/models.py:177
#: xpack/plugins/change_auth_plan/models.py:307 #: xpack/plugins/change_auth_plan/models.py:307
#: xpack/plugins/gathered_user/models.py:76 #: xpack/plugins/gathered_user/models.py:76
@ -1093,8 +1091,7 @@ msgid "Reason"
msgstr "原因" msgstr "原因"
#: audits/models.py:106 tickets/models/ticket.py:47 #: audits/models.py:106 tickets/models/ticket.py:47
#: tickets/serializers/ticket/ticket.py:21 xpack/plugins/cloud/models.py:224 #: xpack/plugins/cloud/models.py:224 xpack/plugins/cloud/models.py:282
#: xpack/plugins/cloud/models.py:282
msgid "Status" msgid "Status"
msgstr "状态" msgstr "状态"
@ -1132,7 +1129,7 @@ msgstr "是否成功"
msgid "Result" msgid "Result"
msgstr "结果" msgstr "结果"
#: audits/serializers.py:79 terminal/serializers/storage.py:157 #: audits/serializers.py:79 terminal/serializers/storage.py:177
msgid "Hosts" msgid "Hosts"
msgstr "主机" msgstr "主机"
@ -1786,11 +1783,11 @@ msgstr "定期清除Celery日志"
msgid "Task log" msgid "Task log"
msgstr "任务列表" msgstr "任务列表"
#: ops/utils.py:62 #: ops/utils.py:64
msgid "Update task content: {}" msgid "Update task content: {}"
msgstr "更新任务内容: {}" msgstr "更新任务内容: {}"
#: ops/utils.py:72 #: ops/utils.py:74
msgid "Disk used more than 80%: {} => {}" msgid "Disk used more than 80%: {} => {}"
msgstr "磁盘使用率超过 80%: {} => {}" msgstr "磁盘使用率超过 80%: {} => {}"
@ -1804,7 +1801,7 @@ msgstr "当前组织不能被删除"
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:41 #: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:41
#: orgs/models.py:422 orgs/serializers.py:100 #: orgs/models.py:422 orgs/serializers.py:100
#: tickets/serializers/ticket/ticket.py:74 #: tickets/serializers/ticket/ticket.py:81
msgid "Organization" msgid "Organization"
msgstr "组织" msgstr "组织"
@ -1904,10 +1901,10 @@ msgid "User group"
msgstr "用户组" msgstr "用户组"
#: perms/models/base.py:53 #: perms/models/base.py:53
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:44 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:46
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:72 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:77
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:41 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:43
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:76 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:81
#: users/models/user.py:556 users/templates/users/user_detail.html:93 #: users/models/user.py:556 users/templates/users/user_detail.html:93
#: users/templates/users/user_profile.html:120 #: users/templates/users/user_profile.html:120
msgid "Date expired" msgid "Date expired"
@ -2717,60 +2714,64 @@ msgstr "是否可重放"
msgid "Can join" msgid "Can join"
msgstr "是否可加入" msgstr "是否可加入"
#: terminal/serializers/storage.py:19 #: terminal/serializers/storage.py:20
msgid "Endpoint invalid: remove path `{}`"
msgstr "端点无效: 移除路径 `{}`"
#: terminal/serializers/storage.py:26
msgid "Bucket" msgid "Bucket"
msgstr "桶名称" msgstr "桶名称"
#: terminal/serializers/storage.py:22 #: terminal/serializers/storage.py:29
msgid "Access key" msgid "Access key"
msgstr "" msgstr ""
#: terminal/serializers/storage.py:26 #: terminal/serializers/storage.py:33
msgid "Secret key" msgid "Secret key"
msgstr "" msgstr ""
#: terminal/serializers/storage.py:30 terminal/serializers/storage.py:41 #: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50
#: terminal/serializers/storage.py:70 #: terminal/serializers/storage.py:80
msgid "Endpoint" msgid "Endpoint"
msgstr "端点" msgstr "端点"
#: terminal/serializers/storage.py:56 xpack/plugins/cloud/models.py:276 #: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:276
msgid "Region" msgid "Region"
msgstr "地域" msgstr "地域"
#: terminal/serializers/storage.py:80 #: terminal/serializers/storage.py:90
msgid "Container name" msgid "Container name"
msgstr "容器名称" msgstr "容器名称"
#: terminal/serializers/storage.py:82 #: terminal/serializers/storage.py:92
msgid "Account name" msgid "Account name"
msgstr "账户名称" msgstr "账户名称"
#: terminal/serializers/storage.py:83 #: terminal/serializers/storage.py:93
msgid "Account key" msgid "Account key"
msgstr "账户密钥" msgstr "账户密钥"
#: terminal/serializers/storage.py:86 #: terminal/serializers/storage.py:96
msgid "Endpoint suffix" msgid "Endpoint suffix"
msgstr "端点后缀" msgstr "端点后缀"
#: terminal/serializers/storage.py:135 #: terminal/serializers/storage.py:154
msgid "The address format is incorrect" msgid "The address format is incorrect"
msgstr "地址格式不正确" msgstr "地址格式不正确"
#: terminal/serializers/storage.py:142 #: terminal/serializers/storage.py:161
msgid "Host invalid" msgid "Host invalid"
msgstr "主机无效" msgstr "主机无效"
#: terminal/serializers/storage.py:145 #: terminal/serializers/storage.py:164
msgid "Port invalid" msgid "Port invalid"
msgstr "端口无效" msgstr "端口无效"
#: terminal/serializers/storage.py:161 #: terminal/serializers/storage.py:180
msgid "Index" msgid "Index"
msgstr "索引" msgstr "索引"
#: terminal/serializers/storage.py:164 #: terminal/serializers/storage.py:183
msgid "Doc type" msgid "Doc type"
msgstr "文档类型" msgstr "文档类型"
@ -2778,14 +2779,14 @@ msgstr "文档类型"
msgid "Not found" msgid "Not found"
msgstr "没有发现" msgstr "没有发现"
#: terminal/utils.py:74 #: terminal/utils.py:79
#, python-format #, python-format
msgid "" msgid ""
"Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $" "Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $"
"%(command)s" "%(command)s"
msgstr "危险命令告警: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s" msgstr "危险命令告警: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s"
#: terminal/utils.py:81 #: terminal/utils.py:87
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -2842,6 +2843,129 @@ msgstr "拒绝"
msgid "Closed" msgid "Closed"
msgstr "关闭" msgstr "关闭"
#: tickets/handler/apply_application.py:55
msgid "Applied category"
msgstr "申请的种类"
#: tickets/handler/apply_application.py:56
msgid "Applied type"
msgstr "申请的类型"
#: tickets/handler/apply_application.py:57
msgid "Applied application group"
msgstr "申请的应用组"
#: tickets/handler/apply_application.py:58 tickets/handler/apply_asset.py:59
msgid "Applied system user group"
msgstr "申请的系统用户组"
#: tickets/handler/apply_application.py:59 tickets/handler/apply_asset.py:61
msgid "Applied date start"
msgstr "申请的开始日期"
#: tickets/handler/apply_application.py:60 tickets/handler/apply_asset.py:62
msgid "Applied date expired"
msgstr "申请的失效日期"
#: tickets/handler/apply_application.py:75
msgid "Approved applications"
msgstr "批准的应用"
#: tickets/handler/apply_application.py:76 tickets/handler/apply_asset.py:79
msgid "Approved system users"
msgstr "批准的系统用户"
#: tickets/handler/apply_application.py:77 tickets/handler/apply_asset.py:81
msgid "Approved date start"
msgstr "批准的开始日期"
#: tickets/handler/apply_application.py:78 tickets/handler/apply_asset.py:82
msgid "Approved date expired"
msgstr "批准的失效日期"
#: tickets/handler/apply_application.py:100 tickets/handler/apply_asset.py:103
msgid ""
"Created by the ticket, ticket title: {}, ticket applicant: {}, ticket "
"processor: {}, ticket ID: {}"
msgstr ""
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
#: tickets/handler/apply_asset.py:57
msgid "Applied IP group"
msgstr "申请的IP组"
#: tickets/handler/apply_asset.py:58
msgid "Applied hostname group"
msgstr "申请的主机名组"
#: tickets/handler/apply_asset.py:60
msgid "Applied actions"
msgstr "申请的动作"
#: tickets/handler/apply_asset.py:78
msgid "Approved assets"
msgstr "批准的资产"
#: tickets/handler/apply_asset.py:80
msgid "Approved actions"
msgstr "批准的动作"
#: tickets/handler/base.py:62
msgid "User {} {} the ticket"
msgstr "用户 {} {} 这个工单"
#: tickets/handler/base.py:91
msgid "Ticket title"
msgstr "工单标题"
#: tickets/handler/base.py:92
msgid "Ticket type"
msgstr "工单类型"
#: tickets/handler/base.py:93
msgid "Ticket status"
msgstr "工单状态"
#: tickets/handler/base.py:94
msgid "Ticket action"
msgstr "工单动作"
#: tickets/handler/base.py:95
msgid "Ticket applicant"
msgstr "工单申请人"
#: tickets/handler/base.py:96
msgid "Ticket assignees"
msgstr "工单受理人"
#: tickets/handler/base.py:99
msgid "Ticket processor"
msgstr "工单处理人"
#: tickets/handler/base.py:100
msgid "Ticket basic info"
msgstr "工单基本信息"
#: tickets/handler/base.py:114
msgid "Ticket applied info"
msgstr "工单申请信息"
#: tickets/handler/base.py:119
msgid "Ticket approved info"
msgstr "工单批准信息"
#: tickets/handler/login_confirm.py:16
msgid "Applied login IP"
msgstr "申请的登录IP"
#: tickets/handler/login_confirm.py:17
msgid "Applied login city"
msgstr "申请的登录城市"
#: tickets/handler/login_confirm.py:18
msgid "Applied login datetime"
msgstr "申请的登录日期"
#: tickets/models/comment.py:19 #: tickets/models/comment.py:19
msgid "User display name" msgid "User display name"
msgstr "用户显示名称" msgstr "用户显示名称"
@ -2864,7 +2988,7 @@ msgstr "申请人"
#: tickets/models/ticket.py:55 #: tickets/models/ticket.py:55
msgid "Applicant display" msgid "Applicant display"
msgstr "申请人" msgstr "申请人 (显示名称)"
#: tickets/models/ticket.py:60 #: tickets/models/ticket.py:60
msgid "Processor" msgid "Processor"
@ -2872,7 +2996,7 @@ msgstr "处理人"
#: tickets/models/ticket.py:63 #: tickets/models/ticket.py:63
msgid "Processor display" msgid "Processor display"
msgstr "处理人" msgstr "处理人 (显示名称)"
#: tickets/models/ticket.py:67 #: tickets/models/ticket.py:67
msgid "Assignees" msgid "Assignees"
@ -2880,153 +3004,134 @@ msgstr "受理人"
#: tickets/models/ticket.py:70 #: tickets/models/ticket.py:70
msgid "Assignees display" msgid "Assignees display"
msgstr "受理人" msgstr "受理人 (显示名称)"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:24
msgid "Category display" msgid "Category display"
msgstr "种类" msgstr "种类 (显示名称)"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:31
#: tickets/serializers/ticket/ticket.py:19
msgid "Type display" msgid "Type display"
msgstr "类型" msgstr "类型 (显示名称)"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:35
msgid "Application group" msgid "Application group"
msgstr "应用组" msgstr "应用组"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:37 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:39
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:26 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:28
msgid "System user group" msgid "System user group"
msgstr "系统用户组" msgstr "系统用户组"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:51 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:53
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50
msgid "Permission name"
msgstr "授权名称"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:56
msgid "Approve applications" msgid "Approve applications"
msgstr "批准的应用" msgstr "批准的应用"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:56 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:61
msgid "Approve applications display" msgid "Approve applications display"
msgstr "批准的应用" msgstr "批准的应用 (显示名称)"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:60 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:65
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:62
msgid "Approve system users" msgid "Approve system users"
msgstr "批准的系统用户" msgstr "批准的系统用户"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:65 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:70
msgid "Approve system user display" msgid "Approve system user display"
msgstr "批准的系统用户" msgstr "批准的系统用户 (显示名称)"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:89 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:90
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:94
msgid "Permission named `{}` already exists"
msgstr "授权名称 `{}` 已存在"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:107
msgid "No `Application` are found under Organization `{}`" msgid "No `Application` are found under Organization `{}`"
msgstr "在组织 `{}` 下没有发现 `应用`" msgstr "在组织 `{}` 下没有发现 `应用`"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:107 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:125
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:106 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:124
msgid "No `SystemUser` are found under Organization `{}`" msgid "No `SystemUser` are found under Organization `{}`"
msgstr "在组织 `{}` 下没有发现 `系统用户`" msgstr "在组织 `{}` 下没有发现 `系统用户`"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:20
msgid "IP group" msgid "IP group"
msgstr "IP组" msgstr "IP组"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:22 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:24
msgid "Hostname group" msgid "Hostname group"
msgstr "主机名组" msgstr "主机名组"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:34 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:36
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:52 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:61 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:66
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:69 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:74
msgid "Approve assets display" msgid "Approve assets display"
msgstr "批准的资产" msgstr "批准的资产 (显示名称)"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:48 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:53
msgid "Approve assets" msgid "Approve assets"
msgstr "批准的资产" msgstr "批准的资产"
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:90 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:108
msgid "No `Asset` are found under Organization `{}`" msgid "No `Asset` are found under Organization `{}`"
msgstr "在组织 `{}` 下没有发现 `资产`" msgstr "在组织 `{}` 下没有发现 `资产`"
#: tickets/serializers/ticket/meta/ticket_type/common.py:11
msgid "Created by ticket ({}-{})"
msgstr "通过工单创建 ({}-{})"
#: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:20 #: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:20
msgid "Login datetime" msgid "Login datetime"
msgstr "登录日期" msgstr "登录日期"
#: tickets/serializers/ticket/ticket.py:92 #: tickets/serializers/ticket/ticket.py:21
msgid "Action display"
msgstr "动作 (显示名称)"
#: tickets/serializers/ticket/ticket.py:24
msgid "Status display"
msgstr "状态(显示名称)"
#: tickets/serializers/ticket/ticket.py:99
msgid "" msgid ""
"The `type` in the submission data (`{}`) is different from the type in the " "The `type` in the submission data (`{}`) is different from the type in the "
"request url (`{}`)" "request url (`{}`)"
msgstr "提交数据中的类型 (`{}`) 与请求URL地址中的类型 (`{}`) 不一致" msgstr "提交数据中的类型 (`{}`) 与请求URL地址中的类型 (`{}`) 不一致"
#: tickets/serializers/ticket/ticket.py:102 #: tickets/serializers/ticket/ticket.py:109
msgid "The organization `{}` does not exist" msgid "The organization `{}` does not exist"
msgstr "组织 `{}` 不存在" msgstr "组织 `{}` 不存在"
#: tickets/serializers/ticket/ticket.py:113 #: tickets/serializers/ticket/ticket.py:120
msgid "None of the assignees belong to Organization `{}` admins" msgid "None of the assignees belong to Organization `{}` admins"
msgstr "所有受理人都不属于组织 `{}` 下的管理员" msgstr "所有受理人都不属于组织 `{}` 下的管理员"
#: tickets/utils.py:21 #: tickets/utils.py:36
msgid "New Ticket: {} ({})" msgid "New Ticket - {} ({})"
msgstr "新建工单: {} ({})" msgstr "新工单 - {} ({})"
#: tickets/utils.py:26 #: tickets/utils.py:38
#, python-brace-format msgid "Your has a new ticket, applicant - {}"
msgid "" msgstr "你有一个新的工单, 申请人 - {}"
"<div>\n"
" <p>Your has a new ticket</p>\n"
" <div>\n"
" <b>Ticket:</b> \n"
" <br/>\n"
" {body}\n"
" <br/>\n"
" <a href={ticket_detail_url}>click here to review</a> \n"
" </div>\n"
" </div>\n"
" "
msgstr ""
"<div>\n"
" <p>你有一个新工单</p>\n"
" <div>\n"
" <b>工单:</b> \n"
" <br/>\n"
" {body}\n"
" <br/>\n"
" <a href={ticket_detail_url}>点击查看</a> \n"
" </div>\n"
" </div>\n"
" "
#: tickets/utils.py:51 #: tickets/utils.py:40 tickets/utils.py:59
msgid "Ticket has processed: {} ({})" msgid "click here to review"
msgstr "工单已处理: {} ({})" msgstr "点击查看"
#: tickets/utils.py:53 #: tickets/utils.py:55
#, python-brace-format msgid "Ticket has processed - {} ({})"
msgid "" msgstr "工单已处理 - {} ({})"
"\n"
" <div>\n" #: tickets/utils.py:57
" <p>Your ticket has been processed</p>\n" msgid "Your ticket has been processed, processor - {}"
" <div>\n" msgstr "你的工单已被处理, 处理人 - {}"
" <b>Ticket:</b> \n"
" <br/>\n"
" {body}\n"
" <br/>\n"
" </div>\n"
" </div>\n"
" "
msgstr ""
"\n"
" <div>\n"
" <p>你的工单已被处理</p>\n"
" <div>\n"
" <b>工单:</b> \n"
" <br/>\n"
" {body}\n"
" <br/>\n"
" </div>\n"
" </div>\n"
" "
#: users/api/user.py:199 #: users/api/user.py:199
msgid "Could not reset self otp, use profile reset instead" msgid "Could not reset self otp, use profile reset instead"
@ -3263,7 +3368,7 @@ msgstr "安全令牌验证"
#: users/templates/users/_base_otp.html:14 users/templates/users/_user.html:13 #: users/templates/users/_base_otp.html:14 users/templates/users/_user.html:13
#: users/templates/users/user_profile_update.html:55 #: users/templates/users/user_profile_update.html:55
#: xpack/plugins/cloud/models.py:125 xpack/plugins/cloud/serializers.py:125 #: xpack/plugins/cloud/models.py:125 xpack/plugins/cloud/serializers.py:136
msgid "Account" msgid "Account"
msgstr "账户" msgstr "账户"
@ -4079,7 +4184,7 @@ msgstr "邮箱地址错误,重新输入"
#: users/views/profile/reset.py:51 #: users/views/profile/reset.py:51
msgid "" msgid ""
"The user is from A, please go to the corresponding system to change the " "The user is from {}, please go to the corresponding system to change the "
"password" "password"
msgstr "用户来自 {} 请去相应系统修改密码" msgstr "用户来自 {} 请去相应系统修改密码"
@ -4227,7 +4332,7 @@ msgstr "实例名称"
msgid "Instance name and Partial IP" msgid "Instance name and Partial IP"
msgstr "实例名称和部分IP" msgstr "实例名称和部分IP"
#: xpack/plugins/cloud/models.py:128 xpack/plugins/cloud/serializers.py:101 #: xpack/plugins/cloud/models.py:128 xpack/plugins/cloud/serializers.py:112
msgid "Regions" msgid "Regions"
msgstr "地域" msgstr "地域"
@ -4239,7 +4344,7 @@ msgstr "实例"
msgid "Hostname strategy" msgid "Hostname strategy"
msgstr "主机名策略" msgstr "主机名策略"
#: xpack/plugins/cloud/models.py:147 xpack/plugins/cloud/serializers.py:129 #: xpack/plugins/cloud/models.py:147 xpack/plugins/cloud/serializers.py:140
msgid "Always update" msgid "Always update"
msgstr "总是更新" msgstr "总是更新"
@ -4371,15 +4476,15 @@ msgstr "租户ID"
msgid "Subscription ID" msgid "Subscription ID"
msgstr "订阅ID" msgstr "订阅ID"
#: xpack/plugins/cloud/serializers.py:99 #: xpack/plugins/cloud/serializers.py:110
msgid "History count" msgid "History count"
msgstr "执行次数" msgstr "执行次数"
#: xpack/plugins/cloud/serializers.py:100 #: xpack/plugins/cloud/serializers.py:111
msgid "Instance count" msgid "Instance count"
msgstr "实例个数" msgstr "实例个数"
#: xpack/plugins/cloud/serializers.py:128 #: xpack/plugins/cloud/serializers.py:139
#: xpack/plugins/gathered_user/serializers.py:20 #: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display" msgid "Periodic display"
msgstr "定时执行" msgstr "定时执行"
@ -4471,3 +4576,6 @@ msgstr "旗舰版"
#: xpack/plugins/license/models.py:77 #: xpack/plugins/license/models.py:77
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
#~ msgid "No"
#~ msgstr "无"

View File

@ -1,11 +1,13 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
import os import os
import uuid
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.tasks import send_mail_async from common.tasks import send_mail_async
from orgs.utils import org_aware_func from orgs.utils import org_aware_func
from jumpserver.const import PROJECT_DIR
from .models import Task, AdHoc from .models import Task, AdHoc
@ -79,8 +81,12 @@ def send_server_performance_mail(path, usage, usages):
def get_task_log_path(base_path, task_id, level=2): def get_task_log_path(base_path, task_id, level=2):
task_id = str(task_id) task_id = str(task_id)
try:
uuid.UUID(task_id)
except:
return os.path.join(PROJECT_DIR, 'data', 'caution.txt')
rel_path = os.path.join(*task_id[:level], task_id + '.log') rel_path = os.path.join(*task_id[:level], task_id + '.log')
path = os.path.join(base_path, rel_path) path = os.path.join(base_path, rel_path)
os.makedirs(os.path.dirname(path), exist_ok=True) os.makedirs(os.path.dirname(path), exist_ok=True)
return path return path

View File

@ -22,7 +22,7 @@ class TaskLogWebsocket(JsonWebsocketConsumer):
def connect(self): def connect(self):
user = self.scope["user"] user = self.scope["user"]
if user.is_authenticated and user.is_org_admin: if user.is_authenticated:
self.accept() self.accept()
else: else:
self.close() self.close()

View File

@ -122,18 +122,18 @@ def refresh_user_amount_on_user_create_or_delete(user_id):
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def on_user_create(sender, instance, created, **kwargs): def on_user_create_refresh_cache(sender, instance, created, **kwargs):
if created: if created:
refresh_user_amount_on_user_create_or_delete(instance.id) refresh_user_amount_on_user_create_or_delete(instance.id)
@receiver(pre_delete, sender=User) @receiver(pre_delete, sender=User)
def on_user_delete(sender, instance, **kwargs): def on_user_delete_refresh_cache(sender, instance, **kwargs):
refresh_user_amount_on_user_create_or_delete(instance.id) refresh_user_amount_on_user_create_or_delete(instance.id)
@receiver(m2m_changed, sender=OrganizationMember) @receiver(m2m_changed, sender=OrganizationMember)
def on_org_user_changed(sender, action, instance, reverse, pk_set, **kwargs): def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set, **kwargs):
if not action.startswith(POST_PREFIX): if not action.startswith(POST_PREFIX):
return return

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from collections import OrderedDict
import logging import logging
import uuid import uuid
@ -8,7 +7,6 @@ from django.core.cache import cache
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from rest_framework.permissions import AllowAny
from common.drf.api import JMSBulkModelViewSet from common.drf.api import JMSBulkModelViewSet
from common.utils import get_object_or_none from common.utils import get_object_or_none
@ -18,7 +16,7 @@ from .. import serializers
from .. import exceptions from .. import exceptions
__all__ = [ __all__ = [
'TerminalViewSet', 'TerminalTokenApi', 'StatusViewSet', 'TerminalConfig', 'TerminalViewSet', 'StatusViewSet', 'TerminalConfig',
] ]
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
@ -69,41 +67,6 @@ class TerminalViewSet(JMSBulkModelViewSet):
queryset = queryset.filter(id__in=filtered_queryset_id) queryset = queryset.filter(id__in=filtered_queryset_id)
return queryset return queryset
def get_permissions(self):
if self.action == "create":
self.permission_classes = (AllowAny,)
return super().get_permissions()
class TerminalTokenApi(APIView):
permission_classes = (AllowAny,)
queryset = Terminal.objects.filter(is_deleted=False)
def get(self, request, *args, **kwargs):
try:
terminal = self.queryset.get(id=kwargs.get('terminal'))
except Terminal.DoesNotExist:
terminal = None
token = request.query_params.get("token")
if terminal is None:
return Response('May be reject by administrator', status=401)
if token is None or cache.get(token, "") != str(terminal.id):
return Response('Token is not valid', status=401)
if not terminal.is_accepted:
return Response("Terminal was not accepted yet", status=400)
if not terminal.user or not terminal.user.access_key:
return Response("No access key generate", status=401)
access_key = terminal.user.access_key()
data = OrderedDict()
data['access_key'] = {'id': access_key.id, 'secret': access_key.secret}
return Response(data, status=200)
class StatusViewSet(viewsets.ModelViewSet): class StatusViewSet(viewsets.ModelViewSet):
queryset = Status.objects.all() queryset = Status.objects.all()

View File

@ -14,6 +14,13 @@ from .. import const
# -------------------------- # --------------------------
def replay_storage_endpoint_format_validator(endpoint):
h = urlparse(endpoint)
if h.path:
raise serializers.ValidationError(_('Endpoint invalid: remove path `{}`').format(h.path))
return endpoint
class ReplayStorageTypeBaseSerializer(serializers.Serializer): class ReplayStorageTypeBaseSerializer(serializers.Serializer):
BUCKET = serializers.CharField( BUCKET = serializers.CharField(
required=True, max_length=1024, label=_('Bucket'), allow_null=True required=True, max_length=1024, label=_('Bucket'), allow_null=True
@ -27,6 +34,7 @@ class ReplayStorageTypeBaseSerializer(serializers.Serializer):
allow_null=True, allow_null=True,
) )
ENDPOINT = serializers.CharField( ENDPOINT = serializers.CharField(
validators=[replay_storage_endpoint_format_validator],
required=True, max_length=1024, label=_('Endpoint'), allow_null=True, required=True, max_length=1024, label=_('Endpoint'), allow_null=True,
) )
@ -38,6 +46,7 @@ class ReplayStorageTypeS3Serializer(ReplayStorageTypeBaseSerializer):
Such as: http://s3.cn-north-1.amazonaws.com.cn Such as: http://s3.cn-north-1.amazonaws.com.cn
''' '''
ENDPOINT = serializers.CharField( ENDPOINT = serializers.CharField(
validators=[replay_storage_endpoint_format_validator],
required=True, max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text), required=True, max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text),
allow_null=True, allow_null=True,
) )
@ -67,6 +76,7 @@ class ReplayStorageTypeOSSSerializer(ReplayStorageTypeBaseSerializer):
Such as: http://oss-cn-hangzhou.aliyuncs.com Such as: http://oss-cn-hangzhou.aliyuncs.com
''' '''
ENDPOINT = serializers.CharField( ENDPOINT = serializers.CharField(
validators=[replay_storage_endpoint_format_validator],
max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text), allow_null=True, max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text), allow_null=True,
) )
@ -113,16 +123,25 @@ class ReplayStorageSerializer(serializers.ModelSerializer):
return _meta return _meta
def get_meta_serializer(self): def get_meta_serializer(self):
serializer_class = None default_serializer = serializers.Serializer(read_only=True)
query_type = self.context['request'].query_params.get('type')
if query_type:
serializer_class = replay_storage_type_serializer_classes_mapping.get(query_type)
if isinstance(self.instance, ReplayStorage): if isinstance(self.instance, ReplayStorage):
instance_type = self.instance.type _type = self.instance.type
serializer_class = replay_storage_type_serializer_classes_mapping.get(instance_type) else:
if serializer_class is None: _type = self.context['request'].query_params.get('type')
serializer_class = serializers.Serializer
if _type:
serializer_class = replay_storage_type_serializer_classes_mapping.get(_type)
else:
serializer_class = default_serializer
if not serializer_class:
serializer_class = default_serializer
if isinstance(serializer_class, type):
serializer = serializer_class() serializer = serializer_class()
else:
serializer = serializer_class
return serializer return serializer
@ -130,7 +149,7 @@ class ReplayStorageSerializer(serializers.ModelSerializer):
# --------------------------- # ---------------------------
def es_host_format_validator(host): def command_storage_es_host_format_validator(host):
h = urlparse(host) h = urlparse(host)
default_error_msg = _('The address format is incorrect') default_error_msg = _('The address format is incorrect')
if h.scheme not in ['http', 'https']: if h.scheme not in ['http', 'https']:
@ -154,8 +173,8 @@ class CommandStorageTypeESSerializer(serializers.Serializer):
(eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com) (eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com)
''' '''
HOSTS = serializers.ListField( HOSTS = serializers.ListField(
child=serializers.CharField(validators=[es_host_format_validator]), label=_('Hosts'), child=serializers.CharField(validators=[command_storage_es_host_format_validator]),
help_text=_(hosts_help_text), allow_null=True label=_('Hosts'), help_text=_(hosts_help_text), allow_null=True
) )
INDEX = serializers.CharField( INDEX = serializers.CharField(
max_length=1024, default='jumpserver', label=_('Index'), allow_null=True max_length=1024, default='jumpserver', label=_('Index'), allow_null=True
@ -187,14 +206,23 @@ class CommandStorageSerializer(serializers.ModelSerializer):
return _meta return _meta
def get_meta_serializer(self): def get_meta_serializer(self):
serializer_class = None default_serializer = serializers.Serializer(read_only=True)
query_type = self.context['request'].query_params.get('type')
if query_type:
serializer_class = command_storage_type_serializer_classes_mapping.get(query_type)
if isinstance(self.instance, CommandStorage): if isinstance(self.instance, CommandStorage):
instance_type = self.instance.type _type = self.instance.type
serializer_class = command_storage_type_serializer_classes_mapping.get(instance_type) else:
if serializer_class is None: _type = self.context['request'].query_params.get('type')
serializer_class = serializers.Serializer
if _type:
serializer_class = command_storage_type_serializer_classes_mapping.get(_type)
else:
serializer_class = default_serializer
if not serializer_class:
serializer_class = default_serializer
if isinstance(serializer_class, type):
serializer = serializer_class() serializer = serializer_class()
else:
serializer = serializer_class
return serializer return serializer

View File

@ -27,8 +27,6 @@ urlpatterns = [
api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}), api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
name='session-replay'), name='session-replay'),
path('tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'), path('tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'),
path('terminals/<uuid:terminal>/access-key/', api.TerminalTokenApi.as_view(),
name='terminal-access-key'),
path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'), path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'),
path('commands/export/', api.CommandExportApi.as_view(), name="command-export"), path('commands/export/', api.CommandExportApi.as_view(), name="command-export"),
path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"), path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"),

View File

@ -71,12 +71,18 @@ def get_session_replay_url(session):
def send_command_alert_mail(command): def send_command_alert_mail(command):
session_obj = Session.objects.get(id=command['session']) session_obj = Session.objects.get(id=command['session'])
input = command['input']
if isinstance(input, str):
input = input.replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ')
subject = _("Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s") % { subject = _("Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s") % {
'name': command['user'], 'name': command['user'],
'login_from': session_obj.get_login_from_display(), 'login_from': session_obj.get_login_from_display(),
'remote_addr': session_obj.remote_addr, 'remote_addr': session_obj.remote_addr,
'command': command['input'] 'command': input
} }
recipient_list = settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER.split(',') recipient_list = settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER.split(',')
message = _(""" message = _("""
Command: %(command)s Command: %(command)s

View File

@ -12,7 +12,7 @@ from common.permissions import IsValidUser, IsOrgAdmin
from tickets import serializers from tickets import serializers
from tickets.models import Ticket from tickets.models import Ticket
from tickets.permissions.ticket import IsAssignee, NotClosed from tickets.permissions.ticket import IsAssignee, IsAssigneeOrApplicant, NotClosed
__all__ = ['TicketViewSet'] __all__ = ['TicketViewSet']
@ -50,7 +50,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
instance = serializer.save() instance = serializer.save()
instance.open(applicant=self.request.user) instance.open(applicant=self.request.user)
@action(detail=False, methods=[POST]) @action(detail=False, methods=[POST], permission_classes=[IsValidUser, ])
def open(self, request, *args, **kwargs): def open(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs) return super().create(request, *args, **kwargs)
@ -68,7 +68,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
instance.reject(processor=request.user) instance.reject(processor=request.user)
return Response(serializer.data) return Response(serializer.data)
@action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed]) @action(detail=True, methods=[PUT], permission_classes=[IsAssigneeOrApplicant, NotClosed])
def close(self, request, *args, **kwargs): def close(self, request, *args, **kwargs):
instance = self.get_object() instance = self.get_object()
serializer = self.get_serializer(instance) serializer = self.get_serializer(instance)

View File

@ -1,4 +1,4 @@
from django.utils.translation import ugettext as __ from django.utils.translation import ugettext as _
from orgs.utils import tmp_to_org, tmp_to_root_org from orgs.utils import tmp_to_org, tmp_to_root_org
from applications.models import Application from applications.models import Application
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
@ -52,12 +52,12 @@ class Handler(BaseHandler):
{}: {}, {}: {},
{}: {}, {}: {},
'''.format( '''.format(
__('Applied category'), apply_category_display, _('Applied category'), apply_category_display,
__('Applied type'), apply_type_display, _('Applied type'), apply_type_display,
__('Applied application group'), apply_application_group, _('Applied application group'), apply_application_group,
__('Applied system user group'), apply_system_user_group, _('Applied system user group'), apply_system_user_group,
__('Applied date start'), apply_date_start, _('Applied date start'), apply_date_start,
__('Applied date expired'), apply_date_expired, _('Applied date expired'), apply_date_expired,
) )
return applied_body return applied_body
@ -72,10 +72,10 @@ class Handler(BaseHandler):
{}: {}, {}: {},
{}: {}, {}: {},
'''.format( '''.format(
__('Approved applications'), approve_applications_display, _('Approved applications'), approve_applications_display,
__('Approved system users'), approve_system_users_display, _('Approved system users'), approve_system_users_display,
__('Approved date start'), approve_date_start, _('Approved date start'), approve_date_start,
__('Approved date expired'), approve_date_expired _('Approved date expired'), approve_date_expired
) )
return approved_body return approved_body
@ -88,31 +88,33 @@ class Handler(BaseHandler):
apply_category = self.ticket.meta.get('apply_category') apply_category = self.ticket.meta.get('apply_category')
apply_type = self.ticket.meta.get('apply_type') apply_type = self.ticket.meta.get('apply_type')
approve_permission_name = self.ticket.meta.get('approve_permission_name', '')
approved_applications_id = self.ticket.meta.get('approve_applications', []) approved_applications_id = self.ticket.meta.get('approve_applications', [])
approve_system_users_id = self.ticket.meta.get('approve_system_users', []) approve_system_users_id = self.ticket.meta.get('approve_system_users', [])
approve_date_start = self.ticket.meta.get('approve_date_start') approve_date_start = self.ticket.meta.get('approve_date_start')
approve_date_expired = self.ticket.meta.get('approve_date_expired') approve_date_expired = self.ticket.meta.get('approve_date_expired')
permission_name = '{}({})'.format( permission_created_by = '{}:{}'.format(
__('Created by ticket ({})'.format(self.ticket.title)), str(self.ticket.id)[:4] str(self.ticket.__class__.__name__), str(self.ticket.id)
) )
permission_comment = __( permission_comment = _(
'Created by the ticket, ' 'Created by the ticket, '
'ticket title: {}, ' 'ticket title: {}, '
'ticket applicant: {}, ' 'ticket applicant: {}, '
'ticket processor: {}, ' 'ticket processor: {}, '
'ticket ID: {}' 'ticket ID: {}'
''.format( ).format(
self.ticket.title, self.ticket.applicant_display, self.ticket.title,
self.ticket.processor_display, str(self.ticket.id) self.ticket.applicant_display,
) self.ticket.processor_display,
str(self.ticket.id)
) )
permissions_data = { permissions_data = {
'id': self.ticket.id, 'id': self.ticket.id,
'name': permission_name, 'name': approve_permission_name,
'category': apply_category, 'category': apply_category,
'type': apply_type, 'type': apply_type,
'comment': permission_comment, 'comment': str(permission_comment),
'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.ticket.id)), 'created_by': permission_created_by,
'date_start': approve_date_start, 'date_start': approve_date_start,
'date_expired': approve_date_expired, 'date_expired': approve_date_expired,
} }

View File

@ -1,5 +1,5 @@
from .base import BaseHandler from .base import BaseHandler
from django.utils.translation import ugettext as __ from django.utils.translation import ugettext as _
from perms.models import AssetPermission, Action from perms.models import AssetPermission, Action
from assets.models import Asset, SystemUser from assets.models import Asset, SystemUser
@ -54,12 +54,12 @@ class Handler(BaseHandler):
{}: {}, {}: {},
{}: {} {}: {}
'''.format( '''.format(
__('Applied IP group'), apply_ip_group, _('Applied IP group'), apply_ip_group,
__("Applied hostname group"), apply_hostname_group, _("Applied hostname group"), apply_hostname_group,
__("Applied system user group"), apply_system_user_group, _("Applied system user group"), apply_system_user_group,
__("Applied actions"), apply_actions_display, _("Applied actions"), apply_actions_display,
__('Applied date start'), apply_date_start, _('Applied date start'), apply_date_start,
__('Applied date expired'), apply_date_expired, _('Applied date expired'), apply_date_expired,
) )
return applied_body return applied_body
@ -75,11 +75,11 @@ class Handler(BaseHandler):
{}: {}, {}: {},
{}: {} {}: {}
'''.format( '''.format(
__('Approved assets'), approve_assets_display, _('Approved assets'), approve_assets_display,
__('Approved system users'), approve_system_users_display, _('Approved system users'), approve_system_users_display,
__('Approved actions'), ', '.join(approve_actions_display), _('Approved actions'), ', '.join(approve_actions_display),
__('Approved date start'), approve_date_start, _('Approved date start'), approve_date_start,
__('Approved date expired'), approve_date_expired, _('Approved date expired'), approve_date_expired,
) )
return approved_body return approved_body
@ -90,30 +90,33 @@ class Handler(BaseHandler):
if asset_permission: if asset_permission:
return asset_permission return asset_permission
approve_permission_name = self.ticket.meta.get('approve_permission_name', )
approve_assets_id = self.ticket.meta.get('approve_assets', []) approve_assets_id = self.ticket.meta.get('approve_assets', [])
approve_system_users_id = self.ticket.meta.get('approve_system_users', []) approve_system_users_id = self.ticket.meta.get('approve_system_users', [])
approve_actions = self.ticket.meta.get('approve_actions', Action.NONE) approve_actions = self.ticket.meta.get('approve_actions', Action.NONE)
approve_date_start = self.ticket.meta.get('approve_date_start') approve_date_start = self.ticket.meta.get('approve_date_start')
approve_date_expired = self.ticket.meta.get('approve_date_expired') approve_date_expired = self.ticket.meta.get('approve_date_expired')
permission_name = '{}({})'.format( permission_created_by = '{}:{}'.format(
__('Created by ticket ({})'.format(self.ticket.title)), str(self.ticket.id)[:4] str(self.ticket.__class__.__name__), str(self.ticket.id)
) )
permission_comment = __( permission_comment = _(
'Created by the ticket, ' 'Created by the ticket, '
'ticket title: {}, ' 'ticket title: {}, '
'ticket applicant: {}, ' 'ticket applicant: {}, '
'ticket processor: {}, ' 'ticket processor: {}, '
'ticket ID: {}' 'ticket ID: {}'
''.format( ).format(
self.ticket.title, self.ticket.applicant_display, self.ticket.processor_display, self.ticket.title,
self.ticket.applicant_display,
self.ticket.processor_display,
str(self.ticket.id) str(self.ticket.id)
) )
)
permission_data = { permission_data = {
'id': self.ticket.id, 'id': self.ticket.id,
'name': permission_name, 'name': approve_permission_name,
'comment': permission_comment, 'comment': str(permission_comment),
'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.ticket.id)), 'created_by': permission_created_by,
'actions': approve_actions, 'actions': approve_actions,
'date_start': approve_date_start, 'date_start': approve_date_start,
'date_expired': approve_date_expired, 'date_expired': approve_date_expired,

View File

@ -1,6 +1,8 @@
from django.utils.translation import ugettext as __ from django.utils.translation import ugettext as _
from common.utils import get_logger from common.utils import get_logger
from tickets.utils import send_ticket_processed_mail_to_applicant from tickets.utils import (
send_ticket_processed_mail_to_applicant, send_ticket_applied_mail_to_assignees
)
logger = get_logger(__name__) logger = get_logger(__name__)
@ -14,9 +16,11 @@ class BaseHandler(object):
# on action # on action
def _on_open(self): def _on_open(self):
self.ticket.applicant_display = str(self.ticket.applicant) self.ticket.applicant_display = str(self.ticket.applicant)
self.ticket.assignees_display = [str(assignee) for assignee in self.ticket.assignees.all()]
meta_display = getattr(self, '_construct_meta_display_of_open', lambda: {})() meta_display = getattr(self, '_construct_meta_display_of_open', lambda: {})()
self.ticket.meta.update(meta_display) self.ticket.meta.update(meta_display)
self.ticket.save() self.ticket.save()
self._send_applied_mail_to_assignees()
def _on_approve(self): def _on_approve(self):
meta_display = getattr(self, '_construct_meta_display_of_approve', lambda: {})() meta_display = getattr(self, '_construct_meta_display_of_approve', lambda: {})()
@ -41,11 +45,12 @@ class BaseHandler(object):
return method() return method()
# email # email
def _send_applied_mail_to_assignees(self):
logger.debug('Send applied email to assignees: {}'.format(self.ticket.assignees_display))
send_ticket_applied_mail_to_assignees(self.ticket)
def _send_processed_mail_to_applicant(self): def _send_processed_mail_to_applicant(self):
msg = 'Ticket ({}) has processed, send mail to applicant ({})'.format( logger.debug('Send processed mail to applicant: {}'.format(self.ticket.applicant_display))
self.ticket.title, self.ticket.applicant_display
)
logger.debug(msg)
send_ticket_processed_mail_to_applicant(self.ticket) send_ticket_processed_mail_to_applicant(self.ticket)
# comments # comments
@ -54,13 +59,18 @@ class BaseHandler(object):
user_display = str(user) user_display = str(user)
action_display = self.ticket.get_action_display() action_display = self.ticket.get_action_display()
data = { data = {
'body': __('User {} {} the ticket'.format(user_display, action_display)), 'body': _('User {} {} the ticket'.format(user_display, action_display)),
'user': user, 'user': user,
'user_display': user_display 'user_display': user_display
} }
return self.ticket.comments.create(**data) return self.ticket.comments.create(**data)
# body # body
body_html_format = '''
{}:
<div style="margin-left: 20px;">{}</div>
'''
def get_body(self): def get_body(self):
old_body = self.ticket.meta.get('body') old_body = self.ticket.meta.get('body')
if old_body: if old_body:
@ -71,25 +81,23 @@ class BaseHandler(object):
return basic_body + meta_body return basic_body + meta_body
def _construct_basic_body(self): def _construct_basic_body(self):
body = ''' basic_body = '''{}: {},
{}:
{}: {},
{}: {},
{}: {}, {}: {},
{}: {}, {}: {},
{}: {}, {}: {},
{}: {}, {}: {},
{}: {} {}: {}
'''.format( '''.format(
__("Ticket basic info"), _('Ticket title'), self.ticket.title,
__('Ticket title'), self.ticket.title, _('Ticket type'), self.ticket.get_type_display(),
__('Ticket type'), self.ticket.get_type_display(), _('Ticket status'), self.ticket.get_status_display(),
__('Ticket applicant'), self.ticket.applicant_display, _('Ticket action'), self.ticket.get_action_display(),
__('Ticket assignees'), self.ticket.assignees_display, _('Ticket applicant'), self.ticket.applicant_display,
__('Ticket processor'), self.ticket.processor_display, _('Ticket assignees'), self.ticket.assignees_display,
__('Ticket action'), self.ticket.get_action_display(),
__('Ticket status'), self.ticket.get_status_display()
) )
if self.ticket.status_closed:
basic_body += '''{}: {}'''.format(_('Ticket processor'), self.ticket.processor_display)
body = self.body_html_format.format(_("Ticket basic info"), basic_body)
return body return body
def _construct_meta_body(self): def _construct_meta_body(self):
@ -102,21 +110,11 @@ class BaseHandler(object):
return body return body
def _base_construct_meta_body_of_open(self): def _base_construct_meta_body_of_open(self):
open_body = ''' meta_body_of_open = getattr(self, '_construct_meta_body_of_open', lambda: 'No')()
{}: body = self.body_html_format.format(_('Ticket applied info'), meta_body_of_open)
{} return body
'''.format(
__('Ticket applied info'),
getattr(self, '_construct_meta_body_of_open', lambda: 'No')()
)
return open_body
def _base_construct_meta_body_of_approve(self): def _base_construct_meta_body_of_approve(self):
approve_body = ''' meta_body_of_approve = getattr(self, '_construct_meta_body_of_approve', lambda: 'No')()
{}: body = self.body_html_format.format(_('Ticket approved info'), meta_body_of_approve)
{} return body
'''.format(
__('Ticket approved info'),
getattr(self, '_construct_meta_body_of_approve', lambda: 'No')()
)
return approve_body

View File

@ -1,4 +1,4 @@
from django.utils.translation import ugettext as __ from django.utils.translation import ugettext as _
from .base import BaseHandler from .base import BaseHandler
@ -13,8 +13,8 @@ class Handler(BaseHandler):
{}: {}, {}: {},
{}: {} {}: {}
'''.format( '''.format(
__("Applied login IP"), apply_login_ip, _("Applied login IP"), apply_login_ip,
__("Applied login city"), apply_login_city, _("Applied login city"), apply_login_city,
__("Applied login datetime"), apply_login_datetime, _("Applied login datetime"), apply_login_datetime,
) )
return applied_body return applied_body

View File

@ -148,35 +148,9 @@ class Ticket(CommonModelMixin, OrgModelMixin):
@classmethod @classmethod
def get_user_related_tickets(cls, user): def get_user_related_tickets(cls, user):
queries = None queries = Q(applicant=user) | Q(assignees=user)
tickets = cls.all() tickets = cls.all().filter(queries).distinct()
if user.is_superuser: return tickets
pass
elif user.is_super_auditor:
pass
elif user.is_org_admin:
admin_orgs_id = [
str(org_id) for org_id in user.admin_orgs.values_list('id', flat=True)
]
assigned_tickets_id = [
str(ticket_id) for ticket_id in user.assigned_tickets.values_list('id', flat=True)
]
queries = Q(applicant=user)
queries |= Q(processor=user)
queries |= Q(org_id__in=admin_orgs_id)
queries |= Q(id__in=assigned_tickets_id)
elif user.is_org_auditor:
audit_orgs_id = [
str(org_id) for org_id in user.audit_orgs.values_list('id', flat=True)
]
queries = Q(org_id__in=audit_orgs_id)
elif user.is_common_user:
queries = Q(applicant=user)
else:
tickets = cls.objects.none()
if queries:
tickets = tickets.filter(queries)
return tickets.distinct()
@classmethod @classmethod
def all(cls): def all(cls):

View File

@ -7,6 +7,12 @@ class IsAssignee(permissions.BasePermission):
return obj.has_assignee(request.user) return obj.has_assignee(request.user)
class IsAssigneeOrApplicant(IsAssignee):
def has_object_permission(self, request, view, obj):
return super().has_object_permission(request, view, obj) or obj.applicant == request.user
class NotClosed(permissions.BasePermission): class NotClosed(permissions.BasePermission):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return not obj.status_closed return not obj.status_closed

View File

@ -29,5 +29,6 @@ type_serializer_classes_mapping = {
const.TicketTypeChoices.login_confirm.value: { const.TicketTypeChoices.login_confirm.value: {
'default': login_confirm.LoginConfirmSerializer, 'default': login_confirm.LoginConfirmSerializer,
action_open: login_confirm.ApplySerializer, action_open: login_confirm.ApplySerializer,
action_approve: login_confirm.LoginConfirmSerializer(read_only=True),
} }
} }

View File

@ -1,11 +1,13 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q from django.db.models import Q
from perms.models import ApplicationPermission
from applications.models import Application from applications.models import Application
from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices
from assets.models import SystemUser from assets.models import SystemUser
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from tickets.models import Ticket from tickets.models import Ticket
from .common import DefaultPermissionName
__all__ = [ __all__ = [
'ApplyApplicationSerializer', 'ApplySerializer', 'ApproveSerializer', 'ApplyApplicationSerializer', 'ApplySerializer', 'ApproveSerializer',
@ -47,6 +49,9 @@ class ApplySerializer(serializers.Serializer):
class ApproveSerializer(serializers.Serializer): class ApproveSerializer(serializers.Serializer):
# 审批信息 # 审批信息
approve_permission_name = serializers.CharField(
max_length=128, default=DefaultPermissionName(), label=_('Permission name')
)
approve_applications = serializers.ListField( approve_applications = serializers.ListField(
required=True, child=serializers.UUIDField(), label=_('Approve applications'), required=True, child=serializers.UUIDField(), label=_('Approve applications'),
allow_null=True allow_null=True
@ -72,6 +77,19 @@ class ApproveSerializer(serializers.Serializer):
required=True, label=_('Date expired'), allow_null=True required=True, label=_('Date expired'), allow_null=True
) )
def validate_approve_permission_name(self, permission_name):
if not isinstance(self.root.instance, Ticket):
return permission_name
with tmp_to_org(self.root.instance.org_id):
already_exists = ApplicationPermission.objects.filter(name=permission_name).exists()
if not already_exists:
return permission_name
raise serializers.ValidationError(_(
'Permission named `{}` already exists'.format(permission_name)
))
def validate_approve_applications(self, approve_applications): def validate_approve_applications(self, approve_applications):
if not isinstance(self.root.instance, Ticket): if not isinstance(self.root.instance, Ticket):
return [] return []
@ -118,6 +136,9 @@ class ApplyApplicationSerializer(ApplySerializer, ApproveSerializer):
return [] return []
apply_application_group = value.get('apply_application_group', []) apply_application_group = value.get('apply_application_group', [])
if not apply_application_group:
return []
apply_type = value.get('apply_type') apply_type = value.get('apply_type')
queries = Q() queries = Q()
for application in apply_application_group: for application in apply_application_group:
@ -133,8 +154,11 @@ class ApplyApplicationSerializer(ApplySerializer, ApproveSerializer):
if not isinstance(self.root.instance, Ticket): if not isinstance(self.root.instance, Ticket):
return [] return []
apply_type = value.get('apply_type')
apply_system_user_group = value.get('apply_system_user_group', []) apply_system_user_group = value.get('apply_system_user_group', [])
if not apply_system_user_group:
return []
apply_type = value.get('apply_type')
protocol = SystemUser.get_protocol_by_application_type(apply_type) protocol = SystemUser.get_protocol_by_application_type(apply_type)
queries = Q() queries = Q()
for system_user in apply_system_user_group: for system_user in apply_system_user_group:

View File

@ -2,9 +2,11 @@ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q from django.db.models import Q
from rest_framework import serializers from rest_framework import serializers
from perms.serializers import ActionsField from perms.serializers import ActionsField
from perms.models import AssetPermission
from assets.models import Asset, SystemUser from assets.models import Asset, SystemUser
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from tickets.models import Ticket from tickets.models import Ticket
from .common import DefaultPermissionName
__all__ = [ __all__ = [
@ -44,6 +46,9 @@ class ApplySerializer(serializers.Serializer):
class ApproveSerializer(serializers.Serializer): class ApproveSerializer(serializers.Serializer):
# 审批信息 # 审批信息
approve_permission_name = serializers.CharField(
max_length=128, default=DefaultPermissionName(), label=_('Permission name')
)
approve_assets = serializers.ListField( approve_assets = serializers.ListField(
required=True, allow_null=True, child=serializers.UUIDField(), label=_('Approve assets') required=True, allow_null=True, child=serializers.UUIDField(), label=_('Approve assets')
) )
@ -76,6 +81,19 @@ class ApproveSerializer(serializers.Serializer):
required=True, label=_('Date expired'), allow_null=True required=True, label=_('Date expired'), allow_null=True
) )
def validate_approve_permission_name(self, permission_name):
if not isinstance(self.root.instance, Ticket):
return permission_name
with tmp_to_org(self.root.instance.org_id):
already_exists = AssetPermission.objects.filter(name=permission_name).exists()
if not already_exists:
return permission_name
raise serializers.ValidationError(_(
'Permission named `{}` already exists'.format(permission_name)
))
def validate_approve_assets(self, approve_assets): def validate_approve_assets(self, approve_assets):
if not isinstance(self.root.instance, Ticket): if not isinstance(self.root.instance, Ticket):
return [] return []
@ -118,10 +136,13 @@ class ApplyAssetSerializer(ApplySerializer, ApproveSerializer):
apply_ip_group = value.get('apply_ip_group', []) apply_ip_group = value.get('apply_ip_group', [])
apply_hostname_group = value.get('apply_hostname_group', []) apply_hostname_group = value.get('apply_hostname_group', [])
queries = Q(ip__in=apply_ip_group) queries = Q()
if apply_ip_group:
queries |= Q(ip__in=apply_ip_group)
for hostname in apply_hostname_group: for hostname in apply_hostname_group:
queries |= Q(hostname__icontains=hostname) queries |= Q(hostname__icontains=hostname)
if not queries:
return []
with tmp_to_org(self.root.instance.org_id): with tmp_to_org(self.root.instance.org_id):
assets_id = Asset.objects.filter(queries).values_list('id', flat=True)[:5] assets_id = Asset.objects.filter(queries).values_list('id', flat=True)[:5]
assets_id = [str(asset_id) for asset_id in assets_id] assets_id = [str(asset_id) for asset_id in assets_id]
@ -132,6 +153,9 @@ class ApplyAssetSerializer(ApplySerializer, ApproveSerializer):
return [] return []
apply_system_user_group = value.get('apply_system_user_group', []) apply_system_user_group = value.get('apply_system_user_group', [])
if not apply_system_user_group:
return []
queries = Q() queries = Q()
for system_user in apply_system_user_group: for system_user in apply_system_user_group:
queries |= Q(username__icontains=system_user) queries |= Q(username__icontains=system_user)

View File

@ -0,0 +1,30 @@
from django.utils.translation import ugettext as _
from tickets.models import Ticket
__all__ = ['DefaultPermissionName', 'get_default_permission_name']
def get_default_permission_name(ticket):
name = ''
if isinstance(ticket, Ticket):
name = _('Created by ticket ({}-{})').format(ticket.title, str(ticket.id)[:4])
return name
class DefaultPermissionName(object):
default = None
@staticmethod
def _construct_default_permission_name(serializer_field):
permission_name = ''
ticket = serializer_field.root.instance
if isinstance(ticket, Ticket):
permission_name = get_default_permission_name(ticket)
return permission_name
def set_context(self, serializer_field):
self.default = self._construct_default_permission_name(serializer_field)
def __call__(self):
return self.default

View File

@ -16,9 +16,13 @@ __all__ = [
class TicketSerializer(OrgResourceModelSerializerMixin): class TicketSerializer(OrgResourceModelSerializerMixin):
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type')) type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display'))
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) action_display = serializers.ReadOnlyField(
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status')) source='get_action_display', label=_('Action display')
)
status_display = serializers.ReadOnlyField(
source='get_status_display', label=_('Status display')
)
meta = MethodSerializer() meta = MethodSerializer()
class Meta: class Meta:
@ -34,31 +38,34 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
] ]
def get_meta_serializer(self): def get_meta_serializer(self):
request = self.context['request'] default_serializer = serializers.Serializer(read_only=True)
default_serializer_class = serializers.Serializer
if isinstance(self.instance, Ticket): if isinstance(self.instance, Ticket):
_type = self.instance.type _type = self.instance.type
else: else:
_type = request.query_params.get('type') _type = self.context['request'].query_params.get('type')
if not _type:
return default_serializer_class()
if _type:
action_serializer_classes_mapping = type_serializer_classes_mapping.get(_type) action_serializer_classes_mapping = type_serializer_classes_mapping.get(_type)
if not action_serializer_classes_mapping: if action_serializer_classes_mapping:
return default_serializer_class() query_action = self.context['request'].query_params.get('action')
action = query_action if query_action else self.context['view'].action
query_action = request.query_params.get('action') serializer_class = action_serializer_classes_mapping.get(action)
_action = query_action if query_action else self.context['view'].action if not serializer_class:
serializer_class = action_serializer_classes_mapping.get(_action)
if serializer_class:
return serializer_class()
serializer_class = action_serializer_classes_mapping.get('default') serializer_class = action_serializer_classes_mapping.get('default')
if serializer_class: else:
return serializer_class() serializer_class = default_serializer
else:
serializer_class = default_serializer
return default_serializer_class() if not serializer_class:
serializer_class = default_serializer
if isinstance(serializer_class, type):
serializer = serializer_class()
else:
serializer = serializer_class
return serializer
class TicketDisplaySerializer(TicketSerializer): class TicketDisplaySerializer(TicketSerializer):

View File

@ -1,11 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import m2m_changed
from common.utils import get_logger from common.utils import get_logger
from tickets.models import Ticket from tickets.models import Ticket
from tickets.utils import send_ticket_applied_mail_to_assignees
from ..signals import post_change_ticket_action from ..signals import post_change_ticket_action
@ -15,19 +13,3 @@ logger = get_logger(__name__)
@receiver(post_change_ticket_action, sender=Ticket) @receiver(post_change_ticket_action, sender=Ticket)
def on_post_change_ticket_action(sender, ticket, action, **kwargs): def on_post_change_ticket_action(sender, ticket, action, **kwargs):
ticket.handler.dispatch(action) ticket.handler.dispatch(action)
@receiver(m2m_changed, sender=Ticket.assignees.through)
def on_ticket_assignees_changed(sender, instance, action, reverse, model, pk_set, **kwargs):
if reverse:
return
if action != 'post_add':
return
logger.debug('Receives ticket and assignees changed signal, ticket: {}'.format(instance.title))
instance.assignees_display = [str(assignee) for assignee in instance.assignees.all()]
instance.save()
assignees = model.objects.filter(pk__in=pk_set)
assignees_display = [str(assignee) for assignee in assignees]
logger.debug('Send applied email to assignees: {}'.format(assignees_display))
send_ticket_applied_mail_to_assignees(instance, assignees)

View File

@ -10,37 +10,39 @@ from . import const
logger = get_logger(__file__) logger = get_logger(__file__)
EMAIL_TEMPLATE = '''
<div>
<p>
{title}
<a href={ticket_detail_url}>
<strong>{ticket_detail_url_description}</strong>
</a>
</p>
<div>
{body}
</div>
</div>
'''
def send_ticket_applied_mail_to_assignees(ticket, assignees):
if not assignees: def send_ticket_applied_mail_to_assignees(ticket):
if not ticket.assignees:
logger.debug("Not found assignees, ticket: {}({}), assignees: {}".format( logger.debug("Not found assignees, ticket: {}({}), assignees: {}".format(
ticket, str(ticket.id), assignees) ticket, str(ticket.id), ticket.assignees)
) )
return return
subject = _('New Ticket: {} ({})'.format(ticket.title, ticket.get_type_display())) ticket_detail_url = urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id)))
ticket_detail_url = urljoin( subject = _('New Ticket - {} ({})').format(ticket.title, ticket.get_type_display())
settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id)) message = EMAIL_TEMPLATE.format(
) title=_('Your has a new ticket, applicant - {}').format(str(ticket.applicant_display)),
message = _( ticket_detail_url=ticket_detail_url,
"""<div> ticket_detail_url_description=_('click here to review'),
<p>Your has a new ticket</p>
<div>
<b>Ticket:</b>
<br/>
{body}
<br/>
<a href={ticket_detail_url}>click here to review</a>
</div>
</div>
""".format(
body=ticket.body.replace('\n', '<br/>'), body=ticket.body.replace('\n', '<br/>'),
ticket_detail_url=ticket_detail_url
)
) )
if settings.DEBUG: if settings.DEBUG:
logger.debug(message) logger.debug(message)
recipient_list = [assignee.email for assignee in assignees] recipient_list = [assignee.email for assignee in ticket.assignees.all()]
send_mail_async.delay(subject, message, recipient_list, html_message=message) send_mail_async.delay(subject, message, recipient_list, html_message=message)
@ -48,22 +50,15 @@ def send_ticket_processed_mail_to_applicant(ticket):
if not ticket.applicant: if not ticket.applicant:
logger.error("Not found applicant: {}({})".format(ticket.title, ticket.id)) logger.error("Not found applicant: {}({})".format(ticket.title, ticket.id))
return return
subject = _('Ticket has processed: {} ({})').format(ticket.title, ticket.get_type_display())
message = _( ticket_detail_url = urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id)))
""" subject = _('Ticket has processed - {} ({})').format(ticket.title, ticket.processor_display)
<div> message = EMAIL_TEMPLATE.format(
<p>Your ticket has been processed</p> title=_('Your ticket has been processed, processor - {}').format(ticket.processor_display),
<div> ticket_detail_url=ticket_detail_url,
<b>Ticket:</b> ticket_detail_url_description=_('click here to review'),
<br/>
{body}
<br/>
</div>
</div>
""".format(
body=ticket.body.replace('\n', '<br/>'), body=ticket.body.replace('\n', '<br/>'),
) )
)
if settings.DEBUG: if settings.DEBUG:
logger.debug(message) logger.debug(message)
recipient_list = [ticket.applicant.email] recipient_list = [ticket.applicant.email]

View File

@ -48,7 +48,7 @@ class UserForgotPasswordView(FormView):
if not user.is_local: if not user.is_local:
error = _( error = _(
'The user is from A, please go to the corresponding system to change the password' 'The user is from {}, please go to the corresponding system to change the password'
''.format(user.get_source_display()) ''.format(user.get_source_display())
) )
form.add_error('email', error) form.add_error('email', error)

2
data/caution.txt Normal file
View File

@ -0,0 +1,2 @@
 你想偷看啥
 What are you trying to peek at !!!