diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index b4fd2cede..0ca03749e 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -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() \ No newline at end of file + return self.redirect_to_guard_view() diff --git a/apps/authentication/views/mixins.py b/apps/authentication/views/mixins.py new file mode 100644 index 000000000..3571dfac7 --- /dev/null +++ b/apps/authentication/views/mixins.py @@ -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 diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index 87afd08ea..5546bf619 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -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() \ No newline at end of file + return self.redirect_to_guard_view() diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 6a9ae69a9..be8c059ce 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -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.*)', views.celery_flower_view, name='flower-view'), path('download/', views.ResourceDownload.as_view(), name='download'), diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo deleted file mode 100644 index 7feeaece9..000000000 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e70a491494af861945bde8a0b03c9b6e78dde7016446236ead362362b76b09a8 -size 125713 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index a0b05f272..e26cb7538 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: LANGUAGE \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をリセットできませんでした、代わりにプロファイルリセットを使用" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo deleted file mode 100644 index c651fb2c5..000000000 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:95e9f6addbdb6811647fd2bb5ae64bfc2572a80702c371eab0a1bb041a1e8476 -size 104032 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index e091a35c8..e362871c6 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: JumpServer team\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 多因子认证, 请去个人信息页面重置" diff --git a/apps/templates/_base_double_screen.html b/apps/templates/_base_double_screen.html new file mode 100644 index 000000000..cd610fea5 --- /dev/null +++ b/apps/templates/_base_double_screen.html @@ -0,0 +1,42 @@ +{% load static %} +{% load i18n %} + + + + + + + + {% block html_title %}{% endblock %} + + {% include '_head_css_js.html' %} + + + + {% block custom_head_css_js %} {% endblock %} + + + +
+
+
+ {% block content %} {% endblock %} +
+
+
+
+
+ {% include '_copyright.html' %} +
+
+
+ +{% include '_foot_js.html' %} +{% block custom_foot_js %} {% endblock %} + diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py index ffca227be..c29c8d00e 100644 --- a/apps/tickets/notifications.py +++ b/apps/tickets/notifications.py @@ -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', '
') + 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 diff --git a/apps/tickets/templates/tickets/_msg_ticket.html b/apps/tickets/templates/tickets/_msg_ticket.html index 15e2b2a3f..5f268592c 100644 --- a/apps/tickets/templates/tickets/_msg_ticket.html +++ b/apps/tickets/templates/tickets/_msg_ticket.html @@ -9,7 +9,13 @@
- \ No newline at end of file + diff --git a/apps/tickets/templates/tickets/approve_check_password.html b/apps/tickets/templates/tickets/approve_check_password.html new file mode 100644 index 000000000..66ee91e1b --- /dev/null +++ b/apps/tickets/templates/tickets/approve_check_password.html @@ -0,0 +1,52 @@ +{% extends '_base_double_screen.html' %} +{% load bootstrap3 %} +{% load static %} +{% load i18n %} + + +{% block content %} +
+
+
+

{% trans 'Ticket information' %}

+

+
{{ ticket_info | safe }}
+
+
+
+
+ +

{% trans 'Ticket approval' %}

+

+
+

+ {% trans 'Hello' %} {{ user.name }}, +

+

+ {{ prompt_msg }} +

+
+
+ {% csrf_token %} +
+ {% if user.is_authenticated %} + + {% else %} + + {% trans 'Go Login' %} + + {% endif %} +
+
+
+
+
+
+ +{% endblock %} diff --git a/apps/tickets/urls/view_urls.py b/apps/tickets/urls/view_urls.py new file mode 100644 index 000000000..f9fcaab84 --- /dev/null +++ b/apps/tickets/urls/view_urls.py @@ -0,0 +1,12 @@ +# coding:utf-8 +# + +from django.urls import path + +from .. import views + +app_name = 'tickets' + +urlpatterns = [ + path('direct-approve//', views.TicketDirectApproveView.as_view(), name='direct-approve'), +] diff --git a/apps/tickets/views/__init__.py b/apps/tickets/views/__init__.py new file mode 100644 index 000000000..ece19ff38 --- /dev/null +++ b/apps/tickets/views/__init__.py @@ -0,0 +1 @@ +from .approve import * diff --git a/apps/tickets/views/approve.py b/apps/tickets/views/approve.py new file mode 100644 index 000000000..8c014c425 --- /dev/null +++ b/apps/tickets/views/approve.py @@ -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)