mirror of https://github.com/jumpserver/jumpserver
feat: 工单支持审批时修改资产 (#8549)
Co-authored-by: feng626 <1304903146@qq.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>pull/8574/head
parent
b64727e04c
commit
b5cfc6831b
|
@ -4,6 +4,7 @@ import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils import timezone as dj_timezone
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ class ModelJSONFieldEncoder(json.JSONEncoder):
|
||||||
if isinstance(obj, str_cls):
|
if isinstance(obj, str_cls):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
elif isinstance(obj, datetime):
|
elif isinstance(obj, datetime):
|
||||||
|
obj = dj_timezone.localtime(obj)
|
||||||
return obj.strftime(settings.DATETIME_DISPLAY_FORMAT)
|
return obj.strftime(settings.DATETIME_DISPLAY_FORMAT)
|
||||||
elif isinstance(obj, (list, tuple)) and len(obj) > 0 \
|
elif isinstance(obj, (list, tuple)) and len(obj) > 0 \
|
||||||
and isinstance(obj[0], models.Model):
|
and isinstance(obj[0], models.Model):
|
||||||
|
|
|
@ -63,7 +63,7 @@ msgstr "アクティブ"
|
||||||
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
|
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
|
||||||
#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:92
|
#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:92
|
||||||
#: terminal/models/storage.py:29 terminal/models/terminal.py:114
|
#: terminal/models/storage.py:29 terminal/models/terminal.py:114
|
||||||
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:278
|
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
|
||||||
#: users/models/group.py:16 users/models/user.py:698
|
#: users/models/group.py:16 users/models/user.py:698
|
||||||
#: xpack/plugins/change_auth_plan/models/base.py:44
|
#: xpack/plugins/change_auth_plan/models/base.py:44
|
||||||
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116
|
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116
|
||||||
|
@ -309,10 +309,10 @@ msgstr "カテゴリ"
|
||||||
#: assets/models/cmd_filter.py:82 assets/models/user.py:250
|
#: assets/models/cmd_filter.py:82 assets/models/user.py:250
|
||||||
#: authentication/models.py:69 perms/models/application_permission.py:24
|
#: authentication/models.py:69 perms/models/application_permission.py:24
|
||||||
#: perms/serializers/application/user_permission.py:34
|
#: perms/serializers/application/user_permission.py:34
|
||||||
#: terminal/models/storage.py:58 terminal/models/storage.py:142
|
#: terminal/models/storage.py:58 terminal/models/storage.py:143
|
||||||
#: tickets/models/comment.py:26 tickets/models/flow.py:57
|
#: tickets/models/comment.py:26 tickets/models/flow.py:57
|
||||||
#: tickets/models/ticket/apply_application.py:17
|
#: tickets/models/ticket/apply_application.py:17
|
||||||
#: tickets/models/ticket/general.py:263
|
#: tickets/models/ticket/general.py:273
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:28
|
#: xpack/plugins/change_auth_plan/models/app.py:28
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:153
|
#: xpack/plugins/change_auth_plan/models/app.py:153
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
|
@ -416,6 +416,8 @@ msgstr "アプリケーションパス"
|
||||||
|
|
||||||
#: applications/serializers/attrs/application_category/remote_app.py:44
|
#: applications/serializers/attrs/application_category/remote_app.py:44
|
||||||
#: assets/serializers/system_user.py:167
|
#: assets/serializers/system_user.py:167
|
||||||
|
#: tickets/serializers/ticket/apply_application.py:35
|
||||||
|
#: tickets/serializers/ticket/common.py:59
|
||||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:67
|
#: xpack/plugins/change_auth_plan/serializers/asset.py:67
|
||||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:70
|
#: xpack/plugins/change_auth_plan/serializers/asset.py:70
|
||||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:73
|
#: xpack/plugins/change_auth_plan/serializers/asset.py:73
|
||||||
|
@ -503,7 +505,7 @@ msgid "Charset"
|
||||||
msgstr "シャーセット"
|
msgstr "シャーセット"
|
||||||
|
|
||||||
#: assets/models/asset.py:141 assets/serializers/asset.py:176
|
#: assets/models/asset.py:141 assets/serializers/asset.py:176
|
||||||
#: tickets/models/ticket/general.py:288
|
#: tickets/models/ticket/general.py:298
|
||||||
msgid "Meta"
|
msgid "Meta"
|
||||||
msgstr "メタ"
|
msgstr "メタ"
|
||||||
|
|
||||||
|
@ -525,7 +527,7 @@ msgstr "ベンダー"
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "モデル"
|
msgstr "モデル"
|
||||||
|
|
||||||
#: assets/models/asset.py:170 tickets/models/ticket/general.py:286
|
#: assets/models/asset.py:170 tickets/models/ticket/general.py:296
|
||||||
msgid "Serial number"
|
msgid "Serial number"
|
||||||
msgstr "シリアル番号"
|
msgstr "シリアル番号"
|
||||||
|
|
||||||
|
@ -1525,7 +1527,7 @@ msgid "MFA"
|
||||||
msgstr "MFA"
|
msgstr "MFA"
|
||||||
|
|
||||||
#: audits/models.py:128 terminal/models/status.py:33
|
#: audits/models.py:128 terminal/models/status.py:33
|
||||||
#: tickets/models/ticket/general.py:271 xpack/plugins/cloud/models.py:175
|
#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175
|
||||||
#: xpack/plugins/cloud/models.py:227
|
#: xpack/plugins/cloud/models.py:227
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "ステータス"
|
msgstr "ステータス"
|
||||||
|
@ -2581,7 +2583,7 @@ msgstr "%(name)s が正常に作成されました"
|
||||||
msgid "%(name)s was updated successfully"
|
msgid "%(name)s was updated successfully"
|
||||||
msgstr "%(name)s は正常に更新されました"
|
msgstr "%(name)s は正常に更新されました"
|
||||||
|
|
||||||
#: common/db/encoder.py:10
|
#: common/db/encoder.py:11
|
||||||
msgid "ugettext_lazy"
|
msgid "ugettext_lazy"
|
||||||
msgstr "ugettext_lazy"
|
msgstr "ugettext_lazy"
|
||||||
|
|
||||||
|
@ -3009,7 +3011,7 @@ msgstr "アプリ組織"
|
||||||
#: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80
|
#: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80
|
||||||
#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48
|
#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48
|
||||||
#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62
|
#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62
|
||||||
#: tickets/models/ticket/general.py:290 tickets/serializers/ticket/ticket.py:64
|
#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:64
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr "組織"
|
msgstr "組織"
|
||||||
|
|
||||||
|
@ -3417,7 +3419,7 @@ msgstr "マイアプリ"
|
||||||
msgid "Ticket comment"
|
msgid "Ticket comment"
|
||||||
msgstr "チケットコメント"
|
msgstr "チケットコメント"
|
||||||
|
|
||||||
#: rbac/tree.py:115 tickets/models/ticket/general.py:295
|
#: rbac/tree.py:115 tickets/models/ticket/general.py:305
|
||||||
msgid "Ticket"
|
msgid "Ticket"
|
||||||
msgstr "チケット"
|
msgstr "チケット"
|
||||||
|
|
||||||
|
@ -4939,7 +4941,7 @@ msgstr "リンク期限切れ"
|
||||||
msgid "User not allowed to join"
|
msgid "User not allowed to join"
|
||||||
msgstr "IPは許可されていません"
|
msgstr "IPは許可されていません"
|
||||||
|
|
||||||
#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:58
|
#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:59
|
||||||
msgid "Joiner"
|
msgid "Joiner"
|
||||||
msgstr "ジョイナー"
|
msgstr "ジョイナー"
|
||||||
|
|
||||||
|
@ -4996,11 +4998,11 @@ msgstr "ブート時間"
|
||||||
msgid "Default storage"
|
msgid "Default storage"
|
||||||
msgstr "デフォルトのストレージ"
|
msgstr "デフォルトのストレージ"
|
||||||
|
|
||||||
#: terminal/models/storage.py:136 terminal/models/terminal.py:108
|
#: terminal/models/storage.py:137 terminal/models/terminal.py:108
|
||||||
msgid "Command storage"
|
msgid "Command storage"
|
||||||
msgstr "コマンドストレージ"
|
msgstr "コマンドストレージ"
|
||||||
|
|
||||||
#: terminal/models/storage.py:196 terminal/models/terminal.py:109
|
#: terminal/models/storage.py:197 terminal/models/terminal.py:109
|
||||||
msgid "Replay storage"
|
msgid "Replay storage"
|
||||||
msgstr "再生ストレージ"
|
msgstr "再生ストレージ"
|
||||||
|
|
||||||
|
@ -5253,7 +5255,19 @@ msgstr ""
|
||||||
"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: "
|
"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: "
|
||||||
"{}"
|
"{}"
|
||||||
|
|
||||||
#: tickets/handlers/base.py:72
|
#: tickets/handlers/base.py:79
|
||||||
|
msgid "Change field"
|
||||||
|
msgstr "フィールドを変更"
|
||||||
|
|
||||||
|
#: tickets/handlers/base.py:79
|
||||||
|
msgid "Before change"
|
||||||
|
msgstr "変更前"
|
||||||
|
|
||||||
|
#: tickets/handlers/base.py:79
|
||||||
|
msgid "After change"
|
||||||
|
msgstr "変更後"
|
||||||
|
|
||||||
|
#: tickets/handlers/base.py:91
|
||||||
msgid "{} {} the ticket"
|
msgid "{} {} the ticket"
|
||||||
msgstr "{} {} チケット"
|
msgstr "{} {} チケット"
|
||||||
|
|
||||||
|
@ -5269,8 +5283,8 @@ msgstr "応用ログイン都市"
|
||||||
msgid "Applied login datetime"
|
msgid "Applied login datetime"
|
||||||
msgstr "適用されたログインの日付時間"
|
msgstr "適用されたログインの日付時間"
|
||||||
|
|
||||||
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:39
|
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:41
|
||||||
#: tickets/models/ticket/general.py:267
|
#: tickets/models/ticket/general.py:277
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "状態"
|
msgstr "状態"
|
||||||
|
|
||||||
|
@ -5287,7 +5301,7 @@ msgid "Body"
|
||||||
msgstr "ボディ"
|
msgstr "ボディ"
|
||||||
|
|
||||||
#: tickets/models/flow.py:20 tickets/models/flow.py:62
|
#: tickets/models/flow.py:20 tickets/models/flow.py:62
|
||||||
#: tickets/models/ticket/general.py:35
|
#: tickets/models/ticket/general.py:37
|
||||||
msgid "Approve level"
|
msgid "Approve level"
|
||||||
msgstr "レベルを承認する"
|
msgstr "レベルを承認する"
|
||||||
|
|
||||||
|
@ -5313,8 +5327,8 @@ msgstr "チケットセッションの関係"
|
||||||
|
|
||||||
#: tickets/models/ticket/apply_application.py:11
|
#: tickets/models/ticket/apply_application.py:11
|
||||||
#: tickets/models/ticket/apply_asset.py:13
|
#: tickets/models/ticket/apply_asset.py:13
|
||||||
msgid "Apply name"
|
msgid "Permission name"
|
||||||
msgstr "名前を適用"
|
msgstr "認可ルール名"
|
||||||
|
|
||||||
#: tickets/models/ticket/apply_application.py:20
|
#: tickets/models/ticket/apply_application.py:20
|
||||||
msgid "Apply applications"
|
msgid "Apply applications"
|
||||||
|
@ -5362,39 +5376,39 @@ msgstr "コマンドフィルタ規則から"
|
||||||
msgid "From cmd filter rule"
|
msgid "From cmd filter rule"
|
||||||
msgstr "コマンドフィルタ規則から"
|
msgstr "コマンドフィルタ規則から"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:70
|
#: tickets/models/ticket/general.py:72
|
||||||
msgid "Ticket step"
|
msgid "Ticket step"
|
||||||
msgstr "チケットステップ"
|
msgstr "チケットステップ"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:88
|
#: tickets/models/ticket/general.py:90
|
||||||
msgid "Ticket assignee"
|
msgid "Ticket assignee"
|
||||||
msgstr "割り当てられたチケット"
|
msgstr "割り当てられたチケット"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:260
|
#: tickets/models/ticket/general.py:270
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "タイトル"
|
msgstr "タイトル"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:276
|
#: tickets/models/ticket/general.py:286
|
||||||
msgid "Applicant"
|
msgid "Applicant"
|
||||||
msgstr "応募者"
|
msgstr "応募者"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:281
|
#: tickets/models/ticket/general.py:291
|
||||||
msgid "TicketFlow"
|
msgid "TicketFlow"
|
||||||
msgstr "作業指示プロセス"
|
msgstr "作業指示プロセス"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:284
|
#: tickets/models/ticket/general.py:294
|
||||||
msgid "Approval step"
|
msgid "Approval step"
|
||||||
msgstr "承認ステップ"
|
msgstr "承認ステップ"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:287
|
#: tickets/models/ticket/general.py:297
|
||||||
msgid "Relation snapshot"
|
msgid "Relation snapshot"
|
||||||
msgstr "製造オーダスナップショット"
|
msgstr "製造オーダスナップショット"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:380
|
#: tickets/models/ticket/general.py:390
|
||||||
msgid "Please try again"
|
msgid "Please try again"
|
||||||
msgstr "もう一度お試しください"
|
msgstr "もう一度お試しください"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:387
|
#: tickets/models/ticket/general.py:421
|
||||||
msgid "Super ticket"
|
msgid "Super ticket"
|
||||||
msgstr "スーパーチケット"
|
msgstr "スーパーチケット"
|
||||||
|
|
||||||
|
@ -5414,27 +5428,27 @@ msgstr "ログインシステムユーザー"
|
||||||
msgid "Login datetime"
|
msgid "Login datetime"
|
||||||
msgstr "ログイン日時"
|
msgstr "ログイン日時"
|
||||||
|
|
||||||
#: tickets/notifications.py:64
|
#: tickets/notifications.py:63
|
||||||
msgid "Ticket basic info"
|
msgid "Ticket basic info"
|
||||||
msgstr "チケット基本情報"
|
msgstr "チケット基本情報"
|
||||||
|
|
||||||
#: tickets/notifications.py:65
|
#: tickets/notifications.py:64
|
||||||
msgid "Ticket applied info"
|
msgid "Ticket applied info"
|
||||||
msgstr "チケット適用情報"
|
msgstr "チケット適用情報"
|
||||||
|
|
||||||
#: tickets/notifications.py:116
|
#: tickets/notifications.py:109
|
||||||
msgid "Your has a new ticket"
|
msgid "Your has a new ticket, applicant - {}"
|
||||||
msgstr "新しいチケットがあります- {}"
|
msgstr "新しいチケットがあります- {}"
|
||||||
|
|
||||||
#: tickets/notifications.py:120
|
#: tickets/notifications.py:113
|
||||||
msgid "{}: New Ticket - {} ({})"
|
msgid "{}: New Ticket - {} ({})"
|
||||||
msgstr "新しいチケット- {} ({})"
|
msgstr "新しいチケット- {} ({})"
|
||||||
|
|
||||||
#: tickets/notifications.py:164
|
#: tickets/notifications.py:157
|
||||||
msgid "Your ticket has been processed, processor - {}"
|
msgid "Your ticket has been processed, processor - {}"
|
||||||
msgstr "チケットが処理されました。プロセッサー- {}"
|
msgstr "チケットが処理されました。プロセッサー- {}"
|
||||||
|
|
||||||
#: tickets/notifications.py:168
|
#: tickets/notifications.py:161
|
||||||
msgid "Ticket has processed - {} ({})"
|
msgid "Ticket has processed - {} ({})"
|
||||||
msgstr "チケットが処理済み- {} ({})"
|
msgstr "チケットが処理済み- {} ({})"
|
||||||
|
|
||||||
|
@ -5455,19 +5469,19 @@ msgid "Processor"
|
||||||
msgstr "プロセッサ"
|
msgstr "プロセッサ"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/common.py:16
|
#: tickets/serializers/ticket/common.py:16
|
||||||
#: tickets/serializers/ticket/common.py:68
|
#: tickets/serializers/ticket/common.py:79
|
||||||
msgid "Created by ticket ({}-{})"
|
msgid "Created by ticket ({}-{})"
|
||||||
msgstr "チケットで作成 ({}-{})"
|
msgstr "チケットで作成 ({}-{})"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/common.py:58
|
#: tickets/serializers/ticket/common.py:69
|
||||||
msgid "The expiration date should be greater than the start date"
|
msgid "The expiration date should be greater than the start date"
|
||||||
msgstr "有効期限は開始日より大きくする必要があります"
|
msgstr "有効期限は開始日より大きくする必要があります"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/common.py:74
|
#: tickets/serializers/ticket/common.py:85
|
||||||
msgid "Permission named `{}` already exists"
|
msgid "Permission named `{}` already exists"
|
||||||
msgstr "'{}'という名前の権限は既に存在します"
|
msgstr "'{}'という名前の権限は既に存在します"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/ticket.py:89
|
#: tickets/serializers/ticket/ticket.py:92
|
||||||
msgid "The ticket flow `{}` does not exist"
|
msgid "The ticket flow `{}` does not exist"
|
||||||
msgstr "チケットフロー '{}'が存在しない"
|
msgstr "チケットフロー '{}'が存在しない"
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ msgstr "激活中"
|
||||||
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
|
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
|
||||||
#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:92
|
#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:92
|
||||||
#: terminal/models/storage.py:29 terminal/models/terminal.py:114
|
#: terminal/models/storage.py:29 terminal/models/terminal.py:114
|
||||||
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:278
|
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
|
||||||
#: users/models/group.py:16 users/models/user.py:698
|
#: users/models/group.py:16 users/models/user.py:698
|
||||||
#: xpack/plugins/change_auth_plan/models/base.py:44
|
#: xpack/plugins/change_auth_plan/models/base.py:44
|
||||||
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116
|
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116
|
||||||
|
@ -304,10 +304,10 @@ msgstr "类别"
|
||||||
#: assets/models/cmd_filter.py:82 assets/models/user.py:250
|
#: assets/models/cmd_filter.py:82 assets/models/user.py:250
|
||||||
#: authentication/models.py:69 perms/models/application_permission.py:24
|
#: authentication/models.py:69 perms/models/application_permission.py:24
|
||||||
#: perms/serializers/application/user_permission.py:34
|
#: perms/serializers/application/user_permission.py:34
|
||||||
#: terminal/models/storage.py:58 terminal/models/storage.py:142
|
#: terminal/models/storage.py:58 terminal/models/storage.py:143
|
||||||
#: tickets/models/comment.py:26 tickets/models/flow.py:57
|
#: tickets/models/comment.py:26 tickets/models/flow.py:57
|
||||||
#: tickets/models/ticket/apply_application.py:17
|
#: tickets/models/ticket/apply_application.py:17
|
||||||
#: tickets/models/ticket/general.py:263
|
#: tickets/models/ticket/general.py:273
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:28
|
#: xpack/plugins/change_auth_plan/models/app.py:28
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:153
|
#: xpack/plugins/change_auth_plan/models/app.py:153
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
|
@ -411,6 +411,8 @@ msgstr "应用路径"
|
||||||
|
|
||||||
#: applications/serializers/attrs/application_category/remote_app.py:44
|
#: applications/serializers/attrs/application_category/remote_app.py:44
|
||||||
#: assets/serializers/system_user.py:167
|
#: assets/serializers/system_user.py:167
|
||||||
|
#: tickets/serializers/ticket/apply_application.py:35
|
||||||
|
#: tickets/serializers/ticket/common.py:59
|
||||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:67
|
#: xpack/plugins/change_auth_plan/serializers/asset.py:67
|
||||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:70
|
#: xpack/plugins/change_auth_plan/serializers/asset.py:70
|
||||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:73
|
#: xpack/plugins/change_auth_plan/serializers/asset.py:73
|
||||||
|
@ -498,7 +500,7 @@ msgid "Charset"
|
||||||
msgstr "编码"
|
msgstr "编码"
|
||||||
|
|
||||||
#: assets/models/asset.py:141 assets/serializers/asset.py:176
|
#: assets/models/asset.py:141 assets/serializers/asset.py:176
|
||||||
#: tickets/models/ticket/general.py:288
|
#: tickets/models/ticket/general.py:298
|
||||||
msgid "Meta"
|
msgid "Meta"
|
||||||
msgstr "元数据"
|
msgstr "元数据"
|
||||||
|
|
||||||
|
@ -520,7 +522,7 @@ msgstr "制造商"
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "型号"
|
msgstr "型号"
|
||||||
|
|
||||||
#: assets/models/asset.py:170 tickets/models/ticket/general.py:286
|
#: assets/models/asset.py:170 tickets/models/ticket/general.py:296
|
||||||
msgid "Serial number"
|
msgid "Serial number"
|
||||||
msgstr "序列号"
|
msgstr "序列号"
|
||||||
|
|
||||||
|
@ -1513,7 +1515,7 @@ msgid "MFA"
|
||||||
msgstr "MFA"
|
msgstr "MFA"
|
||||||
|
|
||||||
#: audits/models.py:128 terminal/models/status.py:33
|
#: audits/models.py:128 terminal/models/status.py:33
|
||||||
#: tickets/models/ticket/general.py:271 xpack/plugins/cloud/models.py:175
|
#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175
|
||||||
#: xpack/plugins/cloud/models.py:227
|
#: xpack/plugins/cloud/models.py:227
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "状态"
|
msgstr "状态"
|
||||||
|
@ -2547,7 +2549,7 @@ msgstr "%(name)s 创建成功"
|
||||||
msgid "%(name)s was updated successfully"
|
msgid "%(name)s was updated successfully"
|
||||||
msgstr "%(name)s 更新成功"
|
msgstr "%(name)s 更新成功"
|
||||||
|
|
||||||
#: common/db/encoder.py:10
|
#: common/db/encoder.py:11
|
||||||
msgid "ugettext_lazy"
|
msgid "ugettext_lazy"
|
||||||
msgstr "ugettext_lazy"
|
msgstr "ugettext_lazy"
|
||||||
|
|
||||||
|
@ -2969,7 +2971,7 @@ msgstr "组织管理"
|
||||||
#: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80
|
#: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80
|
||||||
#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48
|
#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48
|
||||||
#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62
|
#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62
|
||||||
#: tickets/models/ticket/general.py:290 tickets/serializers/ticket/ticket.py:64
|
#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:64
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr "组织"
|
msgstr "组织"
|
||||||
|
|
||||||
|
@ -3374,7 +3376,7 @@ msgstr "我的应用"
|
||||||
msgid "Ticket comment"
|
msgid "Ticket comment"
|
||||||
msgstr "工单评论"
|
msgstr "工单评论"
|
||||||
|
|
||||||
#: rbac/tree.py:115 tickets/models/ticket/general.py:295
|
#: rbac/tree.py:115 tickets/models/ticket/general.py:305
|
||||||
msgid "Ticket"
|
msgid "Ticket"
|
||||||
msgstr "工单管理"
|
msgstr "工单管理"
|
||||||
|
|
||||||
|
@ -4863,7 +4865,7 @@ msgstr "链接过期"
|
||||||
msgid "User not allowed to join"
|
msgid "User not allowed to join"
|
||||||
msgstr "来源 IP 不被允许登录"
|
msgstr "来源 IP 不被允许登录"
|
||||||
|
|
||||||
#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:58
|
#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:59
|
||||||
msgid "Joiner"
|
msgid "Joiner"
|
||||||
msgstr "加入者"
|
msgstr "加入者"
|
||||||
|
|
||||||
|
@ -4920,11 +4922,11 @@ msgstr "运行时间"
|
||||||
msgid "Default storage"
|
msgid "Default storage"
|
||||||
msgstr "默认存储"
|
msgstr "默认存储"
|
||||||
|
|
||||||
#: terminal/models/storage.py:136 terminal/models/terminal.py:108
|
#: terminal/models/storage.py:137 terminal/models/terminal.py:108
|
||||||
msgid "Command storage"
|
msgid "Command storage"
|
||||||
msgstr "命令存储"
|
msgstr "命令存储"
|
||||||
|
|
||||||
#: terminal/models/storage.py:196 terminal/models/terminal.py:109
|
#: terminal/models/storage.py:197 terminal/models/terminal.py:109
|
||||||
msgid "Replay storage"
|
msgid "Replay storage"
|
||||||
msgstr "录像存储"
|
msgstr "录像存储"
|
||||||
|
|
||||||
|
@ -5173,7 +5175,19 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
|
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}"
|
||||||
|
|
||||||
#: tickets/handlers/base.py:72
|
#: tickets/handlers/base.py:79
|
||||||
|
msgid "Change field"
|
||||||
|
msgstr "变更字段"
|
||||||
|
|
||||||
|
#: tickets/handlers/base.py:79
|
||||||
|
msgid "Before change"
|
||||||
|
msgstr "变更前"
|
||||||
|
|
||||||
|
#: tickets/handlers/base.py:79
|
||||||
|
msgid "After change"
|
||||||
|
msgstr "变更后"
|
||||||
|
|
||||||
|
#: tickets/handlers/base.py:91
|
||||||
msgid "{} {} the ticket"
|
msgid "{} {} the ticket"
|
||||||
msgstr "{} {} 工单"
|
msgstr "{} {} 工单"
|
||||||
|
|
||||||
|
@ -5189,8 +5203,8 @@ msgstr "申请登录的城市"
|
||||||
msgid "Applied login datetime"
|
msgid "Applied login datetime"
|
||||||
msgstr "申请登录的日期"
|
msgstr "申请登录的日期"
|
||||||
|
|
||||||
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:39
|
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:41
|
||||||
#: tickets/models/ticket/general.py:267
|
#: tickets/models/ticket/general.py:277
|
||||||
msgid "State"
|
msgid "State"
|
||||||
msgstr "状态"
|
msgstr "状态"
|
||||||
|
|
||||||
|
@ -5207,7 +5221,7 @@ msgid "Body"
|
||||||
msgstr "内容"
|
msgstr "内容"
|
||||||
|
|
||||||
#: tickets/models/flow.py:20 tickets/models/flow.py:62
|
#: tickets/models/flow.py:20 tickets/models/flow.py:62
|
||||||
#: tickets/models/ticket/general.py:35
|
#: tickets/models/ticket/general.py:37
|
||||||
msgid "Approve level"
|
msgid "Approve level"
|
||||||
msgstr "审批级别"
|
msgstr "审批级别"
|
||||||
|
|
||||||
|
@ -5233,8 +5247,8 @@ msgstr "工单会话"
|
||||||
|
|
||||||
#: tickets/models/ticket/apply_application.py:11
|
#: tickets/models/ticket/apply_application.py:11
|
||||||
#: tickets/models/ticket/apply_asset.py:13
|
#: tickets/models/ticket/apply_asset.py:13
|
||||||
msgid "Apply name"
|
msgid "Permission name"
|
||||||
msgstr "应用名称"
|
msgstr "授权规则名称"
|
||||||
|
|
||||||
#: tickets/models/ticket/apply_application.py:20
|
#: tickets/models/ticket/apply_application.py:20
|
||||||
msgid "Apply applications"
|
msgid "Apply applications"
|
||||||
|
@ -5282,39 +5296,39 @@ msgstr "来自命令过滤规则"
|
||||||
msgid "From cmd filter rule"
|
msgid "From cmd filter rule"
|
||||||
msgstr "来自命令过滤规则"
|
msgstr "来自命令过滤规则"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:70
|
#: tickets/models/ticket/general.py:72
|
||||||
msgid "Ticket step"
|
msgid "Ticket step"
|
||||||
msgstr "工单步骤"
|
msgstr "工单步骤"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:88
|
#: tickets/models/ticket/general.py:90
|
||||||
msgid "Ticket assignee"
|
msgid "Ticket assignee"
|
||||||
msgstr "工单受理人"
|
msgstr "工单受理人"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:260
|
#: tickets/models/ticket/general.py:270
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "标题"
|
msgstr "标题"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:276
|
#: tickets/models/ticket/general.py:286
|
||||||
msgid "Applicant"
|
msgid "Applicant"
|
||||||
msgstr "申请人"
|
msgstr "申请人"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:281
|
#: tickets/models/ticket/general.py:291
|
||||||
msgid "TicketFlow"
|
msgid "TicketFlow"
|
||||||
msgstr "工单流程"
|
msgstr "工单流程"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:284
|
#: tickets/models/ticket/general.py:294
|
||||||
msgid "Approval step"
|
msgid "Approval step"
|
||||||
msgstr "审批步骤"
|
msgstr "审批步骤"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:287
|
#: tickets/models/ticket/general.py:297
|
||||||
msgid "Relation snapshot"
|
msgid "Relation snapshot"
|
||||||
msgstr "工单快照"
|
msgstr "工单快照"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:380
|
#: tickets/models/ticket/general.py:390
|
||||||
msgid "Please try again"
|
msgid "Please try again"
|
||||||
msgstr "请再次尝试"
|
msgstr "请再次尝试"
|
||||||
|
|
||||||
#: tickets/models/ticket/general.py:387
|
#: tickets/models/ticket/general.py:421
|
||||||
msgid "Super ticket"
|
msgid "Super ticket"
|
||||||
msgstr "超级工单"
|
msgstr "超级工单"
|
||||||
|
|
||||||
|
@ -5334,27 +5348,27 @@ msgstr "登录系统用户"
|
||||||
msgid "Login datetime"
|
msgid "Login datetime"
|
||||||
msgstr "登录日期"
|
msgstr "登录日期"
|
||||||
|
|
||||||
#: tickets/notifications.py:64
|
#: tickets/notifications.py:63
|
||||||
msgid "Ticket basic info"
|
msgid "Ticket basic info"
|
||||||
msgstr "工单基本信息"
|
msgstr "工单基本信息"
|
||||||
|
|
||||||
#: tickets/notifications.py:65
|
#: tickets/notifications.py:64
|
||||||
msgid "Ticket applied info"
|
msgid "Ticket applied info"
|
||||||
msgstr "工单申请信息"
|
msgstr "工单申请信息"
|
||||||
|
|
||||||
#: tickets/notifications.py:116
|
#: tickets/notifications.py:109
|
||||||
msgid "Your has a new ticket"
|
msgid "Your has a new ticket, applicant - {}"
|
||||||
msgstr "你有一个新的工单, 申请人 - {}"
|
msgstr "你有一个新的工单, 申请人 - {}"
|
||||||
|
|
||||||
#: tickets/notifications.py:120
|
#: tickets/notifications.py:113
|
||||||
msgid "{}: New Ticket - {} ({})"
|
msgid "{}: New Ticket - {} ({})"
|
||||||
msgstr "新工单 - {} ({})"
|
msgstr "新工单 - {} ({})"
|
||||||
|
|
||||||
#: tickets/notifications.py:164
|
#: tickets/notifications.py:157
|
||||||
msgid "Your ticket has been processed, processor - {}"
|
msgid "Your ticket has been processed, processor - {}"
|
||||||
msgstr "你的工单已被处理, 处理人 - {}"
|
msgstr "你的工单已被处理, 处理人 - {}"
|
||||||
|
|
||||||
#: tickets/notifications.py:168
|
#: tickets/notifications.py:161
|
||||||
msgid "Ticket has processed - {} ({})"
|
msgid "Ticket has processed - {} ({})"
|
||||||
msgstr "你的工单已被处理, 处理人 - {} ({})"
|
msgstr "你的工单已被处理, 处理人 - {} ({})"
|
||||||
|
|
||||||
|
@ -5375,19 +5389,19 @@ msgid "Processor"
|
||||||
msgstr "处理人"
|
msgstr "处理人"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/common.py:16
|
#: tickets/serializers/ticket/common.py:16
|
||||||
#: tickets/serializers/ticket/common.py:68
|
#: tickets/serializers/ticket/common.py:79
|
||||||
msgid "Created by ticket ({}-{})"
|
msgid "Created by ticket ({}-{})"
|
||||||
msgstr "通过工单创建 ({}-{})"
|
msgstr "通过工单创建 ({}-{})"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/common.py:58
|
#: tickets/serializers/ticket/common.py:69
|
||||||
msgid "The expiration date should be greater than the start date"
|
msgid "The expiration date should be greater than the start date"
|
||||||
msgstr "过期时间要大于开始时间"
|
msgstr "过期时间要大于开始时间"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/common.py:74
|
#: tickets/serializers/ticket/common.py:85
|
||||||
msgid "Permission named `{}` already exists"
|
msgid "Permission named `{}` already exists"
|
||||||
msgstr "授权名称 `{}` 已存在"
|
msgstr "授权名称 `{}` 已存在"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/ticket.py:89
|
#: tickets/serializers/ticket/ticket.py:92
|
||||||
msgid "The ticket flow `{}` does not exist"
|
msgid "The ticket flow `{}` does not exist"
|
||||||
msgstr "工单流程 `{}` 不存在"
|
msgstr "工单流程 `{}` 不存在"
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,12 @@ class ActionsField(serializers.MultipleChoiceField):
|
||||||
return Action.value_to_choices(value)
|
return Action.value_to_choices(value)
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
if data is None:
|
if not self.allow_empty and not data:
|
||||||
|
self.fail('empty')
|
||||||
|
|
||||||
|
if not data:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return Action.choices_to_value(data)
|
return Action.choices_to_value(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.exceptions import MethodNotAllowed
|
from rest_framework.exceptions import MethodNotAllowed
|
||||||
|
|
||||||
from common.const.http import POST, PUT
|
from common.const.http import POST, PUT, PATCH
|
||||||
from common.mixins.api import CommonApiMixin
|
from common.mixins.api import CommonApiMixin
|
||||||
from orgs.utils import tmp_to_root_org
|
from orgs.utils import tmp_to_root_org
|
||||||
|
|
||||||
|
@ -71,32 +71,34 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||||
with tmp_to_root_org():
|
with tmp_to_root_org():
|
||||||
return super().create(request, *args, **kwargs)
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
@action(detail=True, methods=[PUT], permission_classes=[IsAssignee, ])
|
@action(detail=True, methods=[PUT, PATCH], permission_classes=[IsAssignee, ])
|
||||||
def approve(self, request, *args, **kwargs):
|
def approve(self, request, *args, **kwargs):
|
||||||
|
partial = kwargs.pop('partial', False)
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
serializer = self.get_serializer(instance)
|
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
instance = serializer.save()
|
||||||
instance.approve(processor=request.user)
|
instance.approve(processor=request.user)
|
||||||
return Response(serializer.data)
|
return Response('ok')
|
||||||
|
|
||||||
@action(detail=True, methods=[PUT], permission_classes=[IsAssignee, ])
|
@action(detail=True, methods=[PUT], permission_classes=[IsAssignee, ])
|
||||||
def reject(self, request, *args, **kwargs):
|
def reject(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
serializer = self.get_serializer(instance)
|
|
||||||
instance.reject(processor=request.user)
|
instance.reject(processor=request.user)
|
||||||
return Response(serializer.data)
|
return Response('ok')
|
||||||
|
|
||||||
@action(detail=True, methods=[PUT], permission_classes=[IsApplicant, ])
|
@action(detail=True, methods=[PUT], permission_classes=[IsApplicant, ])
|
||||||
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)
|
|
||||||
instance.close()
|
instance.close()
|
||||||
return Response(serializer.data)
|
return Response('ok')
|
||||||
|
|
||||||
|
|
||||||
class ApplyAssetTicketViewSet(TicketViewSet):
|
class ApplyAssetTicketViewSet(TicketViewSet):
|
||||||
serializer_class = serializers.ApplyAssetDisplaySerializer
|
serializer_class = serializers.ApplyAssetDisplaySerializer
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'open': serializers.ApplyAssetSerializer
|
'open': serializers.ApplyAssetSerializer,
|
||||||
|
'approve': serializers.ApproveAssetSerializer
|
||||||
}
|
}
|
||||||
model = ApplyAssetTicket
|
model = ApplyAssetTicket
|
||||||
filterset_class = filters.ApplyAssetTicketFilter
|
filterset_class = filters.ApplyAssetTicketFilter
|
||||||
|
@ -105,7 +107,8 @@ class ApplyAssetTicketViewSet(TicketViewSet):
|
||||||
class ApplyApplicationTicketViewSet(TicketViewSet):
|
class ApplyApplicationTicketViewSet(TicketViewSet):
|
||||||
serializer_class = serializers.ApplyApplicationDisplaySerializer
|
serializer_class = serializers.ApplyApplicationDisplaySerializer
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'open': serializers.ApplyApplicationSerializer
|
'open': serializers.ApplyApplicationSerializer,
|
||||||
|
'approve': serializers.ApproveApplicationSerializer
|
||||||
}
|
}
|
||||||
model = ApplyApplicationTicket
|
model = ApplyApplicationTicket
|
||||||
filterset_class = filters.ApplyApplicationTicketFilter
|
filterset_class = filters.ApplyApplicationTicketFilter
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from tickets.utils import (
|
from tickets.utils import (
|
||||||
send_ticket_processed_mail_to_applicant,
|
send_ticket_processed_mail_to_applicant,
|
||||||
send_ticket_applied_mail_to_assignees
|
send_ticket_applied_mail_to_assignees
|
||||||
)
|
)
|
||||||
from tickets.const import TicketState, TicketStatus
|
from tickets.const import TicketState, TicketType
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -59,6 +60,25 @@ class BaseHandler:
|
||||||
logger.debug('Send processed mail to applicant: {}'.format(applicant))
|
logger.debug('Send processed mail to applicant: {}'.format(applicant))
|
||||||
send_ticket_processed_mail_to_applicant(self.ticket, processor)
|
send_ticket_processed_mail_to_applicant(self.ticket, processor)
|
||||||
|
|
||||||
|
def _diff_prev_approve_context(self, state):
|
||||||
|
diff_context = {}
|
||||||
|
if state != TicketState.approved:
|
||||||
|
return diff_context
|
||||||
|
if self.ticket.type not in [TicketType.apply_asset, TicketType.apply_application]:
|
||||||
|
return diff_context
|
||||||
|
|
||||||
|
old_rel_snapshot = self.ticket.old_rel_snapshot
|
||||||
|
current_rel_snapshot = self.ticket.get_local_snapshot()
|
||||||
|
diff = set(current_rel_snapshot.items()) - set(old_rel_snapshot.items())
|
||||||
|
if not diff:
|
||||||
|
return diff_context
|
||||||
|
|
||||||
|
content = []
|
||||||
|
for k, v in sorted(list(diff), reverse=True):
|
||||||
|
content.append([k, old_rel_snapshot[k], v])
|
||||||
|
headers = [_('Change field'), _('Before change'), _('After change')]
|
||||||
|
return {'headers': headers, 'content': content}
|
||||||
|
|
||||||
def _create_state_change_comment(self, state):
|
def _create_state_change_comment(self, state):
|
||||||
# 打开或关闭工单,备注显示是自己,其他是受理人
|
# 打开或关闭工单,备注显示是自己,其他是受理人
|
||||||
if state in [TicketState.reopen, TicketState.pending, TicketState.closed]:
|
if state in [TicketState.reopen, TicketState.pending, TicketState.closed]:
|
||||||
|
@ -68,8 +88,11 @@ class BaseHandler:
|
||||||
|
|
||||||
user_display = str(user)
|
user_display = str(user)
|
||||||
state_display = getattr(TicketState, state).label
|
state_display = getattr(TicketState, state).label
|
||||||
|
approve_info = _('{} {} the ticket').format(user_display, state_display)
|
||||||
|
context = self._diff_prev_approve_context(state)
|
||||||
|
context.update({'approve_info': approve_info})
|
||||||
data = {
|
data = {
|
||||||
'body': _('{} {} the ticket').format(user_display, state_display),
|
'body': render_to_string('tickets/ticket_approve_diff.html', context),
|
||||||
'user': user,
|
'user': user,
|
||||||
'user_display': str(user),
|
'user_display': str(user),
|
||||||
'type': 'state',
|
'type': 'state',
|
||||||
|
|
|
@ -102,7 +102,7 @@ def apply_asset_migrate(apps, *args):
|
||||||
'applicant': instance.applicant_display,
|
'applicant': instance.applicant_display,
|
||||||
'apply_nodes': meta.get('apply_nodes_display', []),
|
'apply_nodes': meta.get('apply_nodes_display', []),
|
||||||
'apply_assets': meta.get('apply_assets_display', []),
|
'apply_assets': meta.get('apply_assets_display', []),
|
||||||
'apply_system_users': meta.get('apply_system_users', []),
|
'apply_system_users': meta.get('apply_system_users_display', []),
|
||||||
}
|
}
|
||||||
instance.rel_snapshot = rel_snapshot
|
instance.rel_snapshot = rel_snapshot
|
||||||
instance.save(update_fields=['rel_snapshot'])
|
instance.save(update_fields=['rel_snapshot'])
|
||||||
|
@ -140,7 +140,7 @@ def apply_application_migrate(apps, *args):
|
||||||
rel_snapshot = {
|
rel_snapshot = {
|
||||||
'applicant': instance.applicant_display,
|
'applicant': instance.applicant_display,
|
||||||
'apply_applications': meta.get('apply_applications_display', []),
|
'apply_applications': meta.get('apply_applications_display', []),
|
||||||
'apply_system_users': meta.get('apply_system_users', []),
|
'apply_system_users': meta.get('apply_system_users_display', []),
|
||||||
}
|
}
|
||||||
instance.rel_snapshot = rel_snapshot
|
instance.rel_snapshot = rel_snapshot
|
||||||
instance.save(update_fields=['rel_snapshot'])
|
instance.save(update_fields=['rel_snapshot'])
|
||||||
|
|
|
@ -8,7 +8,7 @@ __all__ = ['ApplyApplicationTicket']
|
||||||
|
|
||||||
|
|
||||||
class ApplyApplicationTicket(Ticket):
|
class ApplyApplicationTicket(Ticket):
|
||||||
apply_permission_name = models.CharField(max_length=128, verbose_name=_('Apply name'))
|
apply_permission_name = models.CharField(max_length=128, verbose_name=_('Permission name'))
|
||||||
# 申请信息
|
# 申请信息
|
||||||
apply_category = models.CharField(
|
apply_category = models.CharField(
|
||||||
max_length=16, choices=AppCategory.choices, verbose_name=_('Category')
|
max_length=16, choices=AppCategory.choices, verbose_name=_('Category')
|
||||||
|
|
|
@ -10,7 +10,7 @@ asset_or_node_help_text = _("Select at least one asset or node")
|
||||||
|
|
||||||
|
|
||||||
class ApplyAssetTicket(Ticket):
|
class ApplyAssetTicket(Ticket):
|
||||||
apply_permission_name = models.CharField(max_length=128, verbose_name=_('Apply name'))
|
apply_permission_name = models.CharField(max_length=128, verbose_name=_('Permission name'))
|
||||||
apply_nodes = models.ManyToManyField('assets.Node', verbose_name=_('Apply nodes'))
|
apply_nodes = models.ManyToManyField('assets.Node', verbose_name=_('Apply nodes'))
|
||||||
# 申请信息
|
# 申请信息
|
||||||
apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets'))
|
apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets'))
|
||||||
|
@ -26,3 +26,6 @@ class ApplyAssetTicket(Ticket):
|
||||||
@property
|
@property
|
||||||
def apply_actions_display(self):
|
def apply_actions_display(self):
|
||||||
return Action.value_to_choices_display(self.apply_actions)
|
return Action.value_to_choices_display(self.apply_actions)
|
||||||
|
|
||||||
|
def get_apply_actions_display(self):
|
||||||
|
return ', '.join(self.apply_actions_display)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import json
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -7,6 +8,7 @@ from django.db.models import Q
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from django.db.models.fields import related
|
from django.db.models.fields import related
|
||||||
|
from django.forms import model_to_dict
|
||||||
|
|
||||||
from common.exceptions import JMSException
|
from common.exceptions import JMSException
|
||||||
from common.utils.timezone import as_current_tz
|
from common.utils.timezone import as_current_tz
|
||||||
|
@ -97,6 +99,7 @@ class StatusMixin:
|
||||||
|
|
||||||
state: str
|
state: str
|
||||||
status: str
|
status: str
|
||||||
|
applicant_id: str
|
||||||
applicant: models.ForeignKey
|
applicant: models.ForeignKey
|
||||||
current_step: TicketStep
|
current_step: TicketStep
|
||||||
save: Callable
|
save: Callable
|
||||||
|
@ -130,6 +133,7 @@ class StatusMixin:
|
||||||
self._open()
|
self._open()
|
||||||
|
|
||||||
def approve(self, processor):
|
def approve(self, processor):
|
||||||
|
self.set_rel_snapshot()
|
||||||
self._change_state(StepState.approved, processor)
|
self._change_state(StepState.approved, processor)
|
||||||
|
|
||||||
def reject(self, processor):
|
def reject(self, processor):
|
||||||
|
@ -178,28 +182,34 @@ class StatusMixin:
|
||||||
@property
|
@property
|
||||||
def process_map(self):
|
def process_map(self):
|
||||||
process_map = []
|
process_map = []
|
||||||
steps = self.ticket_steps.all()
|
for step in self.ticket_steps.all():
|
||||||
for step in steps:
|
processor_id = ''
|
||||||
assignee_ids = []
|
assignee_ids = []
|
||||||
|
processor_display = ''
|
||||||
assignees_display = []
|
assignees_display = []
|
||||||
ticket_assignees = step.ticket_assignees.all()
|
|
||||||
processor = None
|
|
||||||
state = step.state
|
state = step.state
|
||||||
for i in ticket_assignees:
|
for i in step.ticket_assignees.all().prefetch_related('assignee'):
|
||||||
assignee_ids.append(i.assignee_id)
|
assignee_id = i.assignee_id
|
||||||
assignees_display.append(str(i.assignee))
|
assignee_display = str(i.assignee)
|
||||||
|
|
||||||
if state != StepState.pending and state == i.state:
|
if state != StepState.pending and state == i.state:
|
||||||
processor = i.assignee
|
processor_id = assignee_id
|
||||||
|
processor_display = assignee_display
|
||||||
if state == StepState.closed:
|
if state == StepState.closed:
|
||||||
processor = self.applicant
|
processor_id = self.applicant_id
|
||||||
|
processor_display = str(self.applicant)
|
||||||
|
|
||||||
|
assignee_ids.append(assignee_id)
|
||||||
|
assignees_display.append(assignee_display)
|
||||||
|
|
||||||
step_info = {
|
step_info = {
|
||||||
'state': state,
|
'state': state,
|
||||||
'approval_level': step.level,
|
'approval_level': step.level,
|
||||||
'assignees': assignee_ids,
|
'assignees': assignee_ids,
|
||||||
'assignees_display': assignees_display,
|
'assignees_display': assignees_display,
|
||||||
'approval_date': str(step.date_updated),
|
'approval_date': str(step.date_updated),
|
||||||
'processor': processor.id if processor else '',
|
'processor': processor_id,
|
||||||
'processor_display': str(processor) if processor else ''
|
'processor_display': processor_display
|
||||||
}
|
}
|
||||||
process_map.append(step_info)
|
process_map.append(step_info)
|
||||||
return process_map
|
return process_map
|
||||||
|
@ -380,6 +390,30 @@ class Ticket(StatusMixin, CommonModelMixin):
|
||||||
raise JMSException(detail=_('Please try again'), code='please_try_again')
|
raise JMSException(detail=_('Please try again'), code='please_try_again')
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
def get_field_display(self, name, field, data: dict):
|
||||||
|
value = data.get(name)
|
||||||
|
if hasattr(self, f'get_{name}_display'):
|
||||||
|
value = getattr(self, f'get_{name}_display')()
|
||||||
|
elif isinstance(field, related.ForeignKey):
|
||||||
|
value = self.rel_snapshot[name]
|
||||||
|
elif isinstance(field, related.ManyToManyField):
|
||||||
|
value = ', '.join(self.rel_snapshot[name])
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_local_snapshot(self):
|
||||||
|
fields = self._meta._forward_fields_map
|
||||||
|
json_data = json.dumps(model_to_dict(self), cls=ModelJSONFieldEncoder)
|
||||||
|
data = json.loads(json_data)
|
||||||
|
snapshot = {}
|
||||||
|
local_fields = self._meta.local_fields + self._meta.local_many_to_many
|
||||||
|
excludes = ['ticket_ptr']
|
||||||
|
item_names = [field.name for field in local_fields if field.name not in excludes]
|
||||||
|
for name in item_names:
|
||||||
|
field = fields[name]
|
||||||
|
value = self.get_field_display(name, field, data)
|
||||||
|
snapshot[field.verbose_name] = value
|
||||||
|
return snapshot
|
||||||
|
|
||||||
|
|
||||||
class SuperTicket(Ticket):
|
class SuperTicket(Ticket):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -4,7 +4,6 @@ import json
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.shortcuts import reverse
|
from django.shortcuts import reverse
|
||||||
from django.db.models.fields import related
|
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.forms import model_to_dict
|
from django.forms import model_to_dict
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -75,13 +74,7 @@ class BaseTicketMessage(UserMessage):
|
||||||
for name in item_names:
|
for name in item_names:
|
||||||
field = fields[name]
|
field = fields[name]
|
||||||
item = {'name': name, 'title': field.verbose_name}
|
item = {'name': name, 'title': field.verbose_name}
|
||||||
value = data.get(name)
|
value = self.ticket.get_field_display(name, field, data)
|
||||||
if hasattr(self.ticket, f'get_{name}_display'):
|
|
||||||
value = getattr(self.ticket, f'get_{name}_display')()
|
|
||||||
elif isinstance(field, related.ForeignKey):
|
|
||||||
value = self.ticket.rel_snapshot[name]
|
|
||||||
elif isinstance(field, related.ManyToManyField):
|
|
||||||
value = ', '.join(self.ticket.rel_snapshot[name])
|
|
||||||
item['value'] = value
|
item['value'] = value
|
||||||
items.append(item)
|
items.append(item)
|
||||||
return items
|
return items
|
||||||
|
@ -113,7 +106,7 @@ class TicketAppliedToAssigneeMessage(BaseTicketMessage):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def content_title(self):
|
def content_title(self):
|
||||||
return _('Your has a new ticket')
|
return _('Your has a new ticket, applicant - {}').format(self.ticket.applicant)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def subject(self):
|
def subject(self):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from perms.models import ApplicationPermission
|
from perms.models import ApplicationPermission
|
||||||
|
@ -7,7 +8,7 @@ from tickets.models import ApplyApplicationTicket
|
||||||
from .ticket import TicketApplySerializer
|
from .ticket import TicketApplySerializer
|
||||||
from .common import BaseApplyAssetApplicationSerializer
|
from .common import BaseApplyAssetApplicationSerializer
|
||||||
|
|
||||||
__all__ = ['ApplyApplicationSerializer', 'ApplyApplicationDisplaySerializer']
|
__all__ = ['ApplyApplicationSerializer', 'ApplyApplicationDisplaySerializer', 'ApproveApplicationSerializer']
|
||||||
|
|
||||||
|
|
||||||
class ApplyApplicationSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer):
|
class ApplyApplicationSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer):
|
||||||
|
@ -24,15 +25,23 @@ class ApplyApplicationSerializer(BaseApplyAssetApplicationSerializer, TicketAppl
|
||||||
read_only_fields = list(set(fields) - set(writeable_fields))
|
read_only_fields = list(set(fields) - set(writeable_fields))
|
||||||
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
|
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'apply_system_users': {'required': True},
|
'apply_applications': {'required': False, 'allow_empty': True},
|
||||||
|
'apply_system_users': {'required': False, 'allow_empty': True},
|
||||||
}
|
}
|
||||||
extra_kwargs.update(ticket_extra_kwargs)
|
extra_kwargs.update(ticket_extra_kwargs)
|
||||||
|
|
||||||
def validate_apply_applications(self, applications):
|
def validate_apply_applications(self, applications):
|
||||||
|
if self.is_final_approval and not applications:
|
||||||
|
raise serializers.ValidationError(_('This field is required.'))
|
||||||
tp = self.initial_data.get('apply_type')
|
tp = self.initial_data.get('apply_type')
|
||||||
return self.filter_many_to_many_field(Application, applications, type=tp)
|
return self.filter_many_to_many_field(Application, applications, type=tp)
|
||||||
|
|
||||||
|
|
||||||
|
class ApproveApplicationSerializer(ApplyApplicationSerializer):
|
||||||
|
class Meta(ApplyApplicationSerializer.Meta):
|
||||||
|
read_only_fields = ApplyApplicationSerializer.Meta.read_only_fields + ['title', 'type']
|
||||||
|
|
||||||
|
|
||||||
class ApplyApplicationDisplaySerializer(ApplyApplicationSerializer):
|
class ApplyApplicationDisplaySerializer(ApplyApplicationSerializer):
|
||||||
apply_applications = serializers.SerializerMethodField()
|
apply_applications = serializers.SerializerMethodField()
|
||||||
apply_system_users = serializers.SerializerMethodField()
|
apply_system_users = serializers.SerializerMethodField()
|
||||||
|
|
|
@ -10,13 +10,13 @@ from tickets.models import ApplyAssetTicket
|
||||||
from .ticket import TicketApplySerializer
|
from .ticket import TicketApplySerializer
|
||||||
from .common import BaseApplyAssetApplicationSerializer
|
from .common import BaseApplyAssetApplicationSerializer
|
||||||
|
|
||||||
__all__ = ['ApplyAssetSerializer', 'ApplyAssetDisplaySerializer']
|
__all__ = ['ApplyAssetSerializer', 'ApplyAssetDisplaySerializer', 'ApproveAssetSerializer']
|
||||||
|
|
||||||
asset_or_node_help_text = _("Select at least one asset or node")
|
asset_or_node_help_text = _("Select at least one asset or node")
|
||||||
|
|
||||||
|
|
||||||
class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer):
|
class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer):
|
||||||
apply_actions = ActionsField(required=True, allow_null=True)
|
apply_actions = ActionsField(required=True, allow_empty=False)
|
||||||
permission_model = AssetPermission
|
permission_model = AssetPermission
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -30,9 +30,9 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria
|
||||||
read_only_fields = list(set(fields) - set(writeable_fields))
|
read_only_fields = list(set(fields) - set(writeable_fields))
|
||||||
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
|
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'apply_nodes': {'required': False, 'help_text': asset_or_node_help_text},
|
'apply_nodes': {'required': False, 'allow_empty': True},
|
||||||
'apply_assets': {'required': False, 'help_text': asset_or_node_help_text},
|
'apply_assets': {'required': False, 'allow_empty': True},
|
||||||
'apply_system_users': {'required': True},
|
'apply_system_users': {'required': False, 'allow_empty': True},
|
||||||
}
|
}
|
||||||
extra_kwargs.update(ticket_extra_kwargs)
|
extra_kwargs.update(ticket_extra_kwargs)
|
||||||
|
|
||||||
|
@ -44,14 +44,22 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
attrs = super().validate(attrs)
|
attrs = super().validate(attrs)
|
||||||
if not attrs.get('apply_nodes') and not attrs.get('apply_assets'):
|
if self.is_final_approval and (
|
||||||
|
not attrs.get('apply_nodes') and not attrs.get('apply_assets')
|
||||||
|
):
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
'apply_nodes': asset_or_node_help_text,
|
'apply_nodes': asset_or_node_help_text,
|
||||||
'apply_assets': asset_or_node_help_text,
|
'apply_assets': asset_or_node_help_text,
|
||||||
})
|
})
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class ApproveAssetSerializer(ApplyAssetSerializer):
|
||||||
|
class Meta(ApplyAssetSerializer.Meta):
|
||||||
|
read_only_fields = ApplyAssetSerializer.Meta.read_only_fields + ['title', 'type']
|
||||||
|
|
||||||
|
|
||||||
class ApplyAssetDisplaySerializer(ApplyAssetSerializer):
|
class ApplyAssetDisplaySerializer(ApplyAssetSerializer):
|
||||||
apply_nodes = serializers.SerializerMethodField()
|
apply_nodes = serializers.SerializerMethodField()
|
||||||
apply_assets = serializers.SerializerMethodField()
|
apply_assets = serializers.SerializerMethodField()
|
||||||
|
|
|
@ -38,14 +38,25 @@ class DefaultPermissionName(object):
|
||||||
class BaseApplyAssetApplicationSerializer(serializers.Serializer):
|
class BaseApplyAssetApplicationSerializer(serializers.Serializer):
|
||||||
permission_model: Model
|
permission_model: Model
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_final_approval(self):
|
||||||
|
instance = self.instance
|
||||||
|
if not instance:
|
||||||
|
return False
|
||||||
|
if instance.approval_step == instance.ticket_steps.count():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def filter_many_to_many_field(self, model, values: list, **kwargs):
|
def filter_many_to_many_field(self, model, values: list, **kwargs):
|
||||||
org_id = self.initial_data.get('org_id')
|
org_id = self.instance.org_id if self.instance else self.initial_data.get('org_id')
|
||||||
ids = [instance.id for instance in values]
|
ids = [instance.id for instance in values]
|
||||||
with tmp_to_org(org_id):
|
with tmp_to_org(org_id):
|
||||||
qs = model.objects.filter(id__in=ids, **kwargs).values_list('id', flat=True)
|
qs = model.objects.filter(id__in=ids, **kwargs).values_list('id', flat=True)
|
||||||
return list(qs)
|
return list(qs)
|
||||||
|
|
||||||
def validate_apply_system_users(self, system_users):
|
def validate_apply_system_users(self, system_users):
|
||||||
|
if self.is_final_approval and not system_users:
|
||||||
|
raise serializers.ValidationError(_('This field is required.'))
|
||||||
return self.filter_many_to_many_field(SystemUser, system_users)
|
return self.filter_many_to_many_field(SystemUser, system_users)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
@ -72,3 +83,10 @@ class BaseApplyAssetApplicationSerializer(serializers.Serializer):
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
raise serializers.ValidationError(_('Permission named `{}` already exists'.format(name)))
|
raise serializers.ValidationError(_('Permission named `{}` already exists'.format(name)))
|
||||||
|
|
||||||
|
@atomic
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
old_rel_snapshot = instance.get_local_snapshot()
|
||||||
|
instance = super().update(instance, validated_data)
|
||||||
|
instance.old_rel_snapshot = old_rel_snapshot
|
||||||
|
return instance
|
||||||
|
|
|
@ -80,6 +80,9 @@ class TicketApplySerializer(TicketSerializer):
|
||||||
return org_id
|
return org_id
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
if self.instance:
|
||||||
|
return attrs
|
||||||
|
|
||||||
ticket_type = attrs.get('type')
|
ticket_type = attrs.get('type')
|
||||||
org_id = attrs.get('org_id')
|
org_id = attrs.get('org_id')
|
||||||
flow = TicketFlow.get_org_related_flows(org_id=org_id).filter(type=ticket_type).first()
|
flow = TicketFlow.get_org_related_flows(org_id=org_id).filter(type=ticket_type).first()
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<p> {{ approve_info }}</p>
|
||||||
|
<br>
|
||||||
|
<div style="width:100%; overflow-x:scroll;">
|
||||||
|
<table style="width:1000px; text-align:left">
|
||||||
|
<tr>
|
||||||
|
{% for item in headers %}
|
||||||
|
<th> {{ item }} </th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% for item in content %}
|
||||||
|
<tr>
|
||||||
|
{% for child in item %}
|
||||||
|
<td>{{ child }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue