feat: 支持企业微信、钉钉直接审批工单 (#8115)

pull/8157/head^2
jiangweidong 2022-05-05 13:07:48 +08:00 committed by GitHub
parent d23953932f
commit c56179e9e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 424 additions and 81 deletions

View File

@ -21,6 +21,7 @@ from authentication.mixins import AuthMixin
from common.sdk.im.dingtalk import DingTalk
from common.utils.common import get_request_ip
from authentication.notifications import OAuthBindMessage
from .mixins import METAMixin
logger = get_logger(__file__)
@ -200,14 +201,17 @@ class DingTalkEnableStartView(UserVerifyPasswordView):
return success_url
class DingTalkQRLoginView(DingTalkQRMixin, View):
class DingTalkQRLoginView(DingTalkQRMixin, METAMixin, View):
permission_classes = (AllowAny,)
def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url')
redirect_uri = reverse('authentication:dingtalk-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({'redirect_url': redirect_url})
redirect_uri += '?' + urlencode({
'redirect_url': redirect_url,
'next': self.get_next_url_from_meta()
})
url = self.get_qr_url(redirect_uri)
return HttpResponseRedirect(url)
@ -305,4 +309,4 @@ class DingTalkOAuthLoginCallbackView(AuthMixin, DingTalkOAuthMixin, View):
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
return self.redirect_to_guard_view()
return self.redirect_to_guard_view()

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
#
class METAMixin:
def get_next_url_from_meta(self):
request_meta = self.request.META or {}
next_url = None
referer = request_meta.get('HTTP_REFERER', '')
next_url_item = referer.rsplit('next=', 1)
if len(next_url_item) > 1:
next_url = next_url_item[-1]
return next_url

View File

@ -21,6 +21,7 @@ from common.utils.common import get_request_ip
from authentication import errors
from authentication.mixins import AuthMixin
from authentication.notifications import OAuthBindMessage
from .mixins import METAMixin
logger = get_logger(__file__)
@ -196,14 +197,17 @@ class WeComEnableStartView(UserVerifyPasswordView):
return success_url
class WeComQRLoginView(WeComQRMixin, View):
class WeComQRLoginView(WeComQRMixin, METAMixin, View):
permission_classes = (AllowAny,)
def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url')
redirect_uri = reverse('authentication:wecom-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({'redirect_url': redirect_url})
redirect_uri += '?' + urlencode({
'redirect_url': redirect_url,
'next': self.get_next_url_from_meta()
})
url = self.get_qr_url(redirect_uri)
return HttpResponseRedirect(url)
@ -301,4 +305,4 @@ class WeComOAuthLoginCallbackView(AuthMixin, WeComOAuthMixin, View):
response = self.get_failed_response(login_url, title=msg, msg=msg)
return response
return self.redirect_to_guard_view()
return self.redirect_to_guard_view()

View File

@ -31,6 +31,7 @@ api_v1 = [
app_view_patterns = [
path('auth/', include('authentication.urls.view_urls'), name='auth'),
path('ops/', include('ops.urls.view_urls'), name='ops'),
path('tickets/', include('tickets.urls.view_urls'), name='tickets'),
path('common/', include('common.urls.view_urls'), name='common'),
re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'),
path('download/', views.ResourceDownload.as_view(), name='download'),

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e70a491494af861945bde8a0b03c9b6e78dde7016446236ead362362b76b09a8
size 125713

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-29 12:49+0800\n"
"POT-Creation-Date: 2022-05-05 13:03+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -1402,6 +1402,7 @@ msgid "Filename"
msgstr "ファイル名"
#: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90
#: tickets/views/approve.py:93
#: xpack/plugins/change_auth_plan/serializers/app.py:87
#: xpack/plugins/change_auth_plan/serializers/asset.py:197
msgid "Success"
@ -1557,12 +1558,12 @@ msgid "Auth Token"
msgstr "認証トークン"
#: audits/signal_handlers.py:71 authentication/notifications.py:73
#: authentication/views/login.py:164 authentication/views/wecom.py:181
#: authentication/views/login.py:164 authentication/views/wecom.py:182
#: notifications/backends/__init__.py:11 users/models/user.py:720
msgid "WeCom"
msgstr "企業微信"
#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:182
#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:183
#: authentication/views/login.py:170 notifications/backends/__init__.py:12
#: users/models/user.py:721
msgid "DingTalk"
@ -1740,7 +1741,7 @@ msgstr "{ApplicationPermission} 追加 {SystemUser}"
msgid "{ApplicationPermission} REMOVE {SystemUser}"
msgstr "{ApplicationPermission} 削除 {SystemUser}"
#: authentication/api/connection_token.py:328
#: authentication/api/connection_token.py:326
msgid "Invalid token"
msgstr "無効なトークン"
@ -2184,6 +2185,7 @@ msgstr "コードエラー"
#: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:23
#: users/templates/users/_msg_account_expire_reminder.html:4
#: users/templates/users/_msg_password_expire_reminder.html:4
#: users/templates/users/_msg_reset_mfa.html:4
@ -2323,54 +2325,54 @@ msgstr "返品"
msgid "Copy success"
msgstr "コピー成功"
#: authentication/views/dingtalk.py:39
#: authentication/views/dingtalk.py:40
msgid "DingTalk Error, Please contact your system administrator"
msgstr "DingTalkエラー、システム管理者に連絡してください"
#: authentication/views/dingtalk.py:42
#: authentication/views/dingtalk.py:43
msgid "DingTalk Error"
msgstr "DingTalkエラー"
#: authentication/views/dingtalk.py:54 authentication/views/feishu.py:50
#: authentication/views/wecom.py:54
#: authentication/views/dingtalk.py:55 authentication/views/feishu.py:50
#: authentication/views/wecom.py:55
msgid ""
"The system configuration is incorrect. Please contact your administrator"
msgstr "システム設定が正しくありません。管理者に連絡してください"
#: authentication/views/dingtalk.py:78
#: authentication/views/dingtalk.py:79
msgid "DingTalk is already bound"
msgstr "DingTalkはすでにバインドされています"
#: authentication/views/dingtalk.py:127 authentication/views/feishu.py:99
#: authentication/views/wecom.py:127
#: authentication/views/dingtalk.py:128 authentication/views/feishu.py:99
#: authentication/views/wecom.py:128
msgid "Please verify your password first"
msgstr "最初にパスワードを確認してください"
#: authentication/views/dingtalk.py:151 authentication/views/wecom.py:151
#: authentication/views/dingtalk.py:152 authentication/views/wecom.py:152
msgid "Invalid user_id"
msgstr "無効なuser_id"
#: authentication/views/dingtalk.py:167
#: authentication/views/dingtalk.py:168
msgid "DingTalk query user failed"
msgstr "DingTalkクエリユーザーが失敗しました"
#: authentication/views/dingtalk.py:176
#: authentication/views/dingtalk.py:177
msgid "The DingTalk is already bound to another user"
msgstr "DingTalkはすでに別のユーザーにバインドされています"
#: authentication/views/dingtalk.py:183
#: authentication/views/dingtalk.py:184
msgid "Binding DingTalk successfully"
msgstr "DingTalkのバインドに成功"
#: authentication/views/dingtalk.py:235 authentication/views/dingtalk.py:289
#: authentication/views/dingtalk.py:239 authentication/views/dingtalk.py:293
msgid "Failed to get user from DingTalk"
msgstr "DingTalkからユーザーを取得できませんでした"
#: authentication/views/dingtalk.py:241 authentication/views/dingtalk.py:295
#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:299
msgid "DingTalk is not bound"
msgstr "DingTalkはバインドされていません"
#: authentication/views/dingtalk.py:242 authentication/views/dingtalk.py:296
#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300
msgid "Please login with a password and then bind the DingTalk"
msgstr "パスワードでログインし、DingTalkをバインドしてください"
@ -2443,39 +2445,39 @@ msgstr "ログアウト成功"
msgid "Logout success, return login page"
msgstr "ログアウト成功、ログインページを返す"
#: authentication/views/wecom.py:39
#: authentication/views/wecom.py:40
msgid "WeCom Error, Please contact your system administrator"
msgstr "企業微信エラー、システム管理者に連絡してください"
#: authentication/views/wecom.py:42
#: authentication/views/wecom.py:43
msgid "WeCom Error"
msgstr "企業微信エラー"
#: authentication/views/wecom.py:78
#: authentication/views/wecom.py:79
msgid "WeCom is already bound"
msgstr "企業の微信はすでにバインドされています"
#: authentication/views/wecom.py:166
#: authentication/views/wecom.py:167
msgid "WeCom query user failed"
msgstr "企業微信ユーザーの問合せに失敗しました"
#: authentication/views/wecom.py:175
#: authentication/views/wecom.py:176
msgid "The WeCom is already bound to another user"
msgstr "この企業の微信はすでに他のユーザーをバインドしている。"
#: authentication/views/wecom.py:182
#: authentication/views/wecom.py:183
msgid "Binding WeCom successfully"
msgstr "企業の微信のバインドに成功"
#: authentication/views/wecom.py:231 authentication/views/wecom.py:285
#: authentication/views/wecom.py:235 authentication/views/wecom.py:289
msgid "Failed to get user from WeCom"
msgstr "企業の微信からユーザーを取得できませんでした"
#: authentication/views/wecom.py:237 authentication/views/wecom.py:291
#: authentication/views/wecom.py:241 authentication/views/wecom.py:295
msgid "WeCom is not bound"
msgstr "企業の微信をバインドしていません"
#: authentication/views/wecom.py:238 authentication/views/wecom.py:292
#: authentication/views/wecom.py:242 authentication/views/wecom.py:296
msgid "Please login with a password and then bind the WeCom"
msgstr "パスワードでログインしてからWeComをバインドしてください"
@ -4564,7 +4566,7 @@ msgstr "フィルター"
#: terminal/api/endpoint.py:63
msgid "Not found protocol query params"
msgstr ""
msgstr "プロトコルクエリパラメータが見つかりません"
#: terminal/api/session.py:210
msgid "Session does not exist: {}"
@ -5295,19 +5297,19 @@ msgstr "もう一度お試しください"
msgid "Super ticket"
msgstr "スーパーチケット"
#: tickets/notifications.py:63
#: tickets/notifications.py:72
msgid "Your has a new ticket, applicant - {}"
msgstr "新しいチケットがあります- {}"
#: tickets/notifications.py:69
#: tickets/notifications.py:78
msgid "New Ticket - {} ({})"
msgstr "新しいチケット- {} ({})"
#: tickets/notifications.py:91
#: tickets/notifications.py:123
msgid "Your ticket has been processed, processor - {}"
msgstr "チケットが処理されました。プロセッサー- {}"
#: tickets/notifications.py:95
#: tickets/notifications.py:127
msgid "Ticket has processed - {} ({})"
msgstr "チケットが処理済み- {} ({})"
@ -5444,10 +5446,55 @@ msgid "The current organization type already exists"
msgstr "現在の組織タイプは既に存在します。"
#: tickets/templates/tickets/_base_ticket_body.html:17
#: tickets/templates/tickets/_msg_ticket.html:12
msgid "Click here to review"
msgstr "こちらをクリックしてご覧ください"
#: tickets/templates/tickets/_msg_ticket.html:12
msgid "View details"
msgstr "詳細の表示"
#: tickets/templates/tickets/_msg_ticket.html:17
msgid "Direct approval"
msgstr "直接承認"
#: tickets/templates/tickets/approve_check_password.html:11
msgid "Ticket information"
msgstr "作業指示情報"
#: tickets/templates/tickets/approve_check_password.html:19
#, fuzzy
#| msgid "Ticket flow approval rule"
msgid "Ticket approval"
msgstr "作業指示の承認"
#: tickets/templates/tickets/approve_check_password.html:35
#: tickets/views/approve.py:25
msgid "Ticket direct approval"
msgstr "作業指示の直接承認"
#: tickets/templates/tickets/approve_check_password.html:40
msgid "Go Login"
msgstr "ログイン"
#: tickets/views/approve.py:26
msgid ""
"This ticket does not exist, the process has ended, or this link has expired"
msgstr ""
"このワークシートが存在しないか、ワークシートが終了したか、このリンクが無効に"
"なっています"
#: tickets/views/approve.py:55
msgid "Click the button to approve directly"
msgstr "ボタンをクリックすると、直接承認に成功します。"
#: tickets/views/approve.py:57
msgid "After successful authentication, this ticket can be approved directly"
msgstr "認証に成功した後、作業指示書は直接承認することができる。"
#: tickets/views/approve.py:84
msgid "This user is not authorized to approve this ticket"
msgstr "このユーザーはこの作業指示を承認する権限がありません"
#: users/api/user.py:183
msgid "Could not reset self otp, use profile reset instead"
msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用"

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:95e9f6addbdb6811647fd2bb5ae64bfc2572a80702c371eab0a1bb041a1e8476
size 104032

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-29 12:49+0800\n"
"POT-Creation-Date: 2022-05-05 13:03+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -1390,6 +1390,7 @@ msgid "Filename"
msgstr "文件名"
#: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90
#: tickets/views/approve.py:93
#: xpack/plugins/change_auth_plan/serializers/app.py:87
#: xpack/plugins/change_auth_plan/serializers/asset.py:197
msgid "Success"
@ -1545,12 +1546,12 @@ msgid "Auth Token"
msgstr "认证令牌"
#: audits/signal_handlers.py:71 authentication/notifications.py:73
#: authentication/views/login.py:164 authentication/views/wecom.py:181
#: authentication/views/login.py:164 authentication/views/wecom.py:182
#: notifications/backends/__init__.py:11 users/models/user.py:720
msgid "WeCom"
msgstr "企业微信"
#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:182
#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:183
#: authentication/views/login.py:170 notifications/backends/__init__.py:12
#: users/models/user.py:721
msgid "DingTalk"
@ -1728,7 +1729,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}"
msgid "{ApplicationPermission} REMOVE {SystemUser}"
msgstr "{ApplicationPermission} 移除 {SystemUser}"
#: authentication/api/connection_token.py:328
#: authentication/api/connection_token.py:326
msgid "Invalid token"
msgstr "无效的令牌"
@ -2163,6 +2164,7 @@ msgstr "代码错误"
#: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:23
#: users/templates/users/_msg_account_expire_reminder.html:4
#: users/templates/users/_msg_password_expire_reminder.html:4
#: users/templates/users/_msg_reset_mfa.html:4
@ -2293,54 +2295,54 @@ msgstr "返回"
msgid "Copy success"
msgstr "复制成功"
#: authentication/views/dingtalk.py:39
#: authentication/views/dingtalk.py:40
msgid "DingTalk Error, Please contact your system administrator"
msgstr "钉钉错误,请联系系统管理员"
#: authentication/views/dingtalk.py:42
#: authentication/views/dingtalk.py:43
msgid "DingTalk Error"
msgstr "钉钉错误"
#: authentication/views/dingtalk.py:54 authentication/views/feishu.py:50
#: authentication/views/wecom.py:54
#: authentication/views/dingtalk.py:55 authentication/views/feishu.py:50
#: authentication/views/wecom.py:55
msgid ""
"The system configuration is incorrect. Please contact your administrator"
msgstr "企业配置错误,请联系系统管理员"
#: authentication/views/dingtalk.py:78
#: authentication/views/dingtalk.py:79
msgid "DingTalk is already bound"
msgstr "钉钉已经绑定"
#: authentication/views/dingtalk.py:127 authentication/views/feishu.py:99
#: authentication/views/wecom.py:127
#: authentication/views/dingtalk.py:128 authentication/views/feishu.py:99
#: authentication/views/wecom.py:128
msgid "Please verify your password first"
msgstr "请检查密码"
#: authentication/views/dingtalk.py:151 authentication/views/wecom.py:151
#: authentication/views/dingtalk.py:152 authentication/views/wecom.py:152
msgid "Invalid user_id"
msgstr "无效的 user_id"
#: authentication/views/dingtalk.py:167
#: authentication/views/dingtalk.py:168
msgid "DingTalk query user failed"
msgstr "钉钉查询用户失败"
#: authentication/views/dingtalk.py:176
#: authentication/views/dingtalk.py:177
msgid "The DingTalk is already bound to another user"
msgstr "该钉钉已经绑定其他用户"
#: authentication/views/dingtalk.py:183
#: authentication/views/dingtalk.py:184
msgid "Binding DingTalk successfully"
msgstr "绑定 钉钉 成功"
#: authentication/views/dingtalk.py:235 authentication/views/dingtalk.py:289
#: authentication/views/dingtalk.py:239 authentication/views/dingtalk.py:293
msgid "Failed to get user from DingTalk"
msgstr "从钉钉获取用户失败"
#: authentication/views/dingtalk.py:241 authentication/views/dingtalk.py:295
#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:299
msgid "DingTalk is not bound"
msgstr "钉钉没有绑定"
#: authentication/views/dingtalk.py:242 authentication/views/dingtalk.py:296
#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300
msgid "Please login with a password and then bind the DingTalk"
msgstr "请使用密码登录,然后绑定钉钉"
@ -2413,39 +2415,39 @@ msgstr "退出登录成功"
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: authentication/views/wecom.py:39
#: authentication/views/wecom.py:40
msgid "WeCom Error, Please contact your system administrator"
msgstr "企业微信错误,请联系系统管理员"
#: authentication/views/wecom.py:42
#: authentication/views/wecom.py:43
msgid "WeCom Error"
msgstr "企业微信错误"
#: authentication/views/wecom.py:78
#: authentication/views/wecom.py:79
msgid "WeCom is already bound"
msgstr "企业微信已经绑定"
#: authentication/views/wecom.py:166
#: authentication/views/wecom.py:167
msgid "WeCom query user failed"
msgstr "企业微信查询用户失败"
#: authentication/views/wecom.py:175
#: authentication/views/wecom.py:176
msgid "The WeCom is already bound to another user"
msgstr "该企业微信已经绑定其他用户"
#: authentication/views/wecom.py:182
#: authentication/views/wecom.py:183
msgid "Binding WeCom successfully"
msgstr "绑定 企业微信 成功"
#: authentication/views/wecom.py:231 authentication/views/wecom.py:285
#: authentication/views/wecom.py:235 authentication/views/wecom.py:289
msgid "Failed to get user from WeCom"
msgstr "从企业微信获取用户失败"
#: authentication/views/wecom.py:237 authentication/views/wecom.py:291
#: authentication/views/wecom.py:241 authentication/views/wecom.py:295
msgid "WeCom is not bound"
msgstr "没有绑定企业微信"
#: authentication/views/wecom.py:238 authentication/views/wecom.py:292
#: authentication/views/wecom.py:242 authentication/views/wecom.py:296
msgid "Please login with a password and then bind the WeCom"
msgstr "请使用密码登录,然后绑定企业微信"
@ -5221,19 +5223,19 @@ msgstr "请再次尝试"
msgid "Super ticket"
msgstr "超级工单"
#: tickets/notifications.py:63
#: tickets/notifications.py:72
msgid "Your has a new ticket, applicant - {}"
msgstr "你有一个新的工单, 申请人 - {}"
#: tickets/notifications.py:69
#: tickets/notifications.py:78
msgid "New Ticket - {} ({})"
msgstr "新工单 - {} ({})"
#: tickets/notifications.py:91
#: tickets/notifications.py:123
msgid "Your ticket has been processed, processor - {}"
msgstr "你的工单已被处理, 处理人 - {}"
#: tickets/notifications.py:95
#: tickets/notifications.py:127
msgid "Ticket has processed - {} ({})"
msgstr "你的工单已被处理, 处理人 - {} ({})"
@ -5368,10 +5370,51 @@ msgid "The current organization type already exists"
msgstr "当前组织已存在该类型"
#: tickets/templates/tickets/_base_ticket_body.html:17
#: tickets/templates/tickets/_msg_ticket.html:12
msgid "Click here to review"
msgstr "点击查看"
#: tickets/templates/tickets/_msg_ticket.html:12
msgid "View details"
msgstr "查看详情"
#: tickets/templates/tickets/_msg_ticket.html:17
msgid "Direct approval"
msgstr "直接批准"
#: tickets/templates/tickets/approve_check_password.html:11
msgid "Ticket information"
msgstr "工单信息"
#: tickets/templates/tickets/approve_check_password.html:19
msgid "Ticket approval"
msgstr "工单审批"
#: tickets/templates/tickets/approve_check_password.html:35
#: tickets/views/approve.py:25
msgid "Ticket direct approval"
msgstr "工单直接审批"
#: tickets/templates/tickets/approve_check_password.html:40
msgid "Go Login"
msgstr "去登录"
#: tickets/views/approve.py:26
msgid ""
"This ticket does not exist, the process has ended, or this link has expired"
msgstr "工单不存在,或者工单流程已经结束,或者此链接已经过期"
#: tickets/views/approve.py:55
msgid "Click the button to approve directly"
msgstr "点击按钮,即可直接审批成功"
#: tickets/views/approve.py:57
msgid "After successful authentication, this ticket can be approved directly"
msgstr "认证成功后,工单可直接审批"
#: tickets/views/approve.py:84
msgid "This user is not authorized to approve this ticket"
msgstr "此用户无权审批此工单"
#: users/api/user.py:183
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置"

View File

@ -0,0 +1,42 @@
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
<title>{% block html_title %}{% endblock %}</title>
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script src="{% static "js/jumpserver.js" %}"></script>
<style>
.outerBox {
max-width: 80%;
margin: 0 auto;
padding: 100px 20px 20px 20px;
}
</style>
{% block custom_head_css_js %} {% endblock %}
</head>
<body class="gray-bg">
<div class="outerBox animated fadeInDown">
<div class="row">
<div class="col-md-12">
{% block content %} {% endblock %}
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-12">
{% include '_copyright.html' %}
</div>
</div>
</div>
</body>
{% include '_foot_js.html' %}
{% block custom_foot_js %} {% endblock %}
</html>

View File

@ -1,13 +1,15 @@
from urllib.parse import urljoin
from django.conf import settings
from django.core.cache import cache
from django.shortcuts import reverse
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from . import const
from notifications.notifications import UserMessage
from common.utils import get_logger
from common.utils import get_logger, random_string
from .models import Ticket
from . import const
logger = get_logger(__file__)
@ -57,6 +59,13 @@ class TicketAppliedToAssignee(BaseTicketMessage):
def __init__(self, user, ticket):
self.ticket = ticket
super().__init__(user)
self._token = None
@property
def token(self):
if self._token is None:
self._token = random_string(32)
return self._token
@property
def content_title(self):
@ -71,6 +80,29 @@ class TicketAppliedToAssignee(BaseTicketMessage):
)
return title
def get_ticket_approval_url(self):
url = reverse('tickets:direct-approve', kwargs={'token': self.token})
return urljoin(settings.SITE_URL, url)
def get_html_msg(self) -> dict:
body = self.ticket.body.replace('\n', '<br/>')
context = dict(
title=self.content_title,
ticket_detail_url=self.ticket_detail_url,
body=body,
)
ticket_approval_url = self.get_ticket_approval_url()
context.update({'ticket_approval_url': ticket_approval_url})
message = render_to_string('tickets/_msg_ticket.html', context)
cache.set(self.token, {
'body': body, 'ticket_id': self.ticket.id
}, 3600)
return {
'subject': self.subject,
'message': message
}
@classmethod
def gen_test_msg(cls):
from .models import Ticket

View File

@ -9,7 +9,13 @@
<br>
<div>
<a href="{{ ticket_detail_url }}" target="_blank">
{% trans 'Click here to review' %}
{% trans 'View details' %}
</a>
<br>
{% if ticket_approval_url %}
<a href="{{ ticket_approval_url }}" target="_blank">
{% trans 'Direct approval' %}
</a>
{% endif %}
</div>
</div>
</div>

View File

@ -0,0 +1,52 @@
{% extends '_base_double_screen.html' %}
{% load bootstrap3 %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="col-lg-12">
<div class="col-lg-6">
<div class="ibox-content">
<h2 class="font-bold" style="display: inline">{% trans 'Ticket information' %}</h2>
<h1></h1>
<div style="word-break: break-all">{{ ticket_info | safe }}</div>
</div>
</div>
<div class="col-lg-6">
<div class="ibox-content">
<img src="{{ LOGO_URL }}" style="margin: auto" width="50" height="50">
<h2 class="font-bold" style="display: inline">{% trans 'Ticket approval' %}</h2>
<h1></h1>
<div class="ibox-content">
<p>
{% trans 'Hello' %} {{ user.name }},
</p>
<p style="text-indent: 3em">
{{ prompt_msg }}
</p>
<br>
<form id="approve-form" action="" method="post" role="form" novalidate="novalidate">
{% csrf_token %}
<div class="form-group" style="">
{% if user.is_authenticated %}
<button id='approve_button' class="btn btn-primary block full-width m-b"
type="submit">
{% trans 'Ticket direct approval' %}
</button>
{% else %}
<a id='login_button' class="btn btn-primary block full-width m-b"
href="{{ login_url }}">
{% trans 'Go Login' %}
</a>
{% endif %}
</div>
</form>
</div>
</div>
</div>
</div>
<style>
</style>
{% endblock %}

View File

@ -0,0 +1,12 @@
# coding:utf-8
#
from django.urls import path
from .. import views
app_name = 'tickets'
urlpatterns = [
path('direct-approve/<str:token>/', views.TicketDirectApproveView.as_view(), name='direct-approve'),
]

View File

@ -0,0 +1 @@
from .approve import *

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
from django.views.generic.base import TemplateView
from django.shortcuts import redirect, reverse
from django.core.cache import cache
from django.utils.translation import ugettext as _
from tickets.models import Ticket
from tickets.errors import AlreadyClosed
from common.utils import get_logger, FlashMessageUtil
logger = get_logger(__name__)
__all__ = ['TicketDirectApproveView']
class TicketDirectApproveView(TemplateView):
template_name = 'tickets/approve_check_password.html'
redirect_field_name = 'next'
@property
def message_data(self):
return {
'title': _('Ticket direct approval'),
'error': _("This ticket does not exist, "
"the process has ended, or this link has expired"),
'redirect_url': self.login_url,
'auto_redirect': False
}
@property
def login_url(self):
return reverse('authentication:login') + '?admin=1'
def redirect_message_response(self, **kwargs):
message_data = self.message_data
for key, value in kwargs.items():
if isinstance(value, str):
message_data[key] = value
if message_data.get('message'):
message_data.pop('error')
redirect_url = FlashMessageUtil.gen_message_url(message_data)
return redirect(redirect_url)
@staticmethod
def clear(token):
cache.delete(token)
def get_context_data(self, **kwargs):
# 放入工单信息
token = kwargs.get('token')
ticket_info = cache.get(token, {}).get('body', '')
if self.request.user.is_authenticated:
prompt_msg = _('Click the button to approve directly')
else:
prompt_msg = _('After successful authentication, this ticket can be approved directly')
kwargs.update({
'ticket_info': ticket_info, 'prompt_msg': prompt_msg,
'login_url': '%s&next=%s' % (
self.login_url,
reverse('tickets:direct-approve', kwargs={'token': token})
),
})
return super().get_context_data(**kwargs)
def get(self, request, *args, **kwargs):
token = kwargs.get('token')
ticket_info = cache.get(token)
if not ticket_info:
return self.redirect_message_response(redirect_url=self.login_url)
return super().get(request, *args, **kwargs)
def post(self, request, **kwargs):
user = request.user
token = kwargs.get('token')
ticket_info = cache.get(token)
if not ticket_info:
return self.redirect_message_response(redirect_url=self.login_url)
try:
ticket_id = ticket_info.get('ticket_id')
ticket = Ticket.all().get(id=ticket_id)
if not ticket.has_current_assignee(user):
raise Exception(_("This user is not authorized to approve this ticket"))
ticket.approve(user)
except AlreadyClosed as e:
self.clear(token)
return self.redirect_message_response(error=str(e), redirect_url=self.login_url)
except Exception as e:
return self.redirect_message_response(error=str(e), redirect_url=self.login_url)
self.clear(token)
return self.redirect_message_response(message=_("Success"), redirect_url=self.login_url)