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 django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone as dj_timezone
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -18,6 +19,7 @@ class ModelJSONFieldEncoder(json.JSONEncoder):
|
|||
if isinstance(obj, str_cls):
|
||||
return str(obj)
|
||||
elif isinstance(obj, datetime):
|
||||
obj = dj_timezone.localtime(obj)
|
||||
return obj.strftime(settings.DATETIME_DISPLAY_FORMAT)
|
||||
elif isinstance(obj, (list, tuple)) and len(obj) > 0 \
|
||||
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
|
||||
#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:92
|
||||
#: 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
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:44
|
||||
#: 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
|
||||
#: authentication/models.py:69 perms/models/application_permission.py:24
|
||||
#: 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/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:153
|
||||
msgid "Type"
|
||||
|
@ -416,6 +416,8 @@ msgstr "アプリケーションパス"
|
|||
|
||||
#: applications/serializers/attrs/application_category/remote_app.py:44
|
||||
#: 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:70
|
||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:73
|
||||
|
@ -503,7 +505,7 @@ msgid "Charset"
|
|||
msgstr "シャーセット"
|
||||
|
||||
#: assets/models/asset.py:141 assets/serializers/asset.py:176
|
||||
#: tickets/models/ticket/general.py:288
|
||||
#: tickets/models/ticket/general.py:298
|
||||
msgid "Meta"
|
||||
msgstr "メタ"
|
||||
|
||||
|
@ -525,7 +527,7 @@ msgstr "ベンダー"
|
|||
msgid "Model"
|
||||
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"
|
||||
msgstr "シリアル番号"
|
||||
|
||||
|
@ -1525,7 +1527,7 @@ msgid "MFA"
|
|||
msgstr "MFA"
|
||||
|
||||
#: 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
|
||||
msgid "Status"
|
||||
msgstr "ステータス"
|
||||
|
@ -2581,7 +2583,7 @@ msgstr "%(name)s が正常に作成されました"
|
|||
msgid "%(name)s was updated successfully"
|
||||
msgstr "%(name)s は正常に更新されました"
|
||||
|
||||
#: common/db/encoder.py:10
|
||||
#: common/db/encoder.py:11
|
||||
msgid "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/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48
|
||||
#: 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"
|
||||
msgstr "組織"
|
||||
|
||||
|
@ -3417,7 +3419,7 @@ msgstr "マイアプリ"
|
|||
msgid "Ticket comment"
|
||||
msgstr "チケットコメント"
|
||||
|
||||
#: rbac/tree.py:115 tickets/models/ticket/general.py:295
|
||||
#: rbac/tree.py:115 tickets/models/ticket/general.py:305
|
||||
msgid "Ticket"
|
||||
msgstr "チケット"
|
||||
|
||||
|
@ -4939,7 +4941,7 @@ msgstr "リンク期限切れ"
|
|||
msgid "User not allowed to join"
|
||||
msgstr "IPは許可されていません"
|
||||
|
||||
#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:58
|
||||
#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:59
|
||||
msgid "Joiner"
|
||||
msgstr "ジョイナー"
|
||||
|
||||
|
@ -4996,11 +4998,11 @@ msgstr "ブート時間"
|
|||
msgid "Default storage"
|
||||
msgstr "デフォルトのストレージ"
|
||||
|
||||
#: terminal/models/storage.py:136 terminal/models/terminal.py:108
|
||||
#: terminal/models/storage.py:137 terminal/models/terminal.py:108
|
||||
msgid "Command storage"
|
||||
msgstr "コマンドストレージ"
|
||||
|
||||
#: terminal/models/storage.py:196 terminal/models/terminal.py:109
|
||||
#: terminal/models/storage.py:197 terminal/models/terminal.py:109
|
||||
msgid "Replay storage"
|
||||
msgstr "再生ストレージ"
|
||||
|
||||
|
@ -5253,7 +5255,19 @@ msgstr ""
|
|||
"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケット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"
|
||||
msgstr "{} {} チケット"
|
||||
|
||||
|
@ -5269,8 +5283,8 @@ msgstr "応用ログイン都市"
|
|||
msgid "Applied login datetime"
|
||||
msgstr "適用されたログインの日付時間"
|
||||
|
||||
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:39
|
||||
#: tickets/models/ticket/general.py:267
|
||||
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:41
|
||||
#: tickets/models/ticket/general.py:277
|
||||
msgid "State"
|
||||
msgstr "状態"
|
||||
|
||||
|
@ -5287,7 +5301,7 @@ msgid "Body"
|
|||
msgstr "ボディ"
|
||||
|
||||
#: 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"
|
||||
msgstr "レベルを承認する"
|
||||
|
||||
|
@ -5313,8 +5327,8 @@ msgstr "チケットセッションの関係"
|
|||
|
||||
#: tickets/models/ticket/apply_application.py:11
|
||||
#: tickets/models/ticket/apply_asset.py:13
|
||||
msgid "Apply name"
|
||||
msgstr "名前を適用"
|
||||
msgid "Permission name"
|
||||
msgstr "認可ルール名"
|
||||
|
||||
#: tickets/models/ticket/apply_application.py:20
|
||||
msgid "Apply applications"
|
||||
|
@ -5362,39 +5376,39 @@ msgstr "コマンドフィルタ規則から"
|
|||
msgid "From cmd filter rule"
|
||||
msgstr "コマンドフィルタ規則から"
|
||||
|
||||
#: tickets/models/ticket/general.py:70
|
||||
#: tickets/models/ticket/general.py:72
|
||||
msgid "Ticket step"
|
||||
msgstr "チケットステップ"
|
||||
|
||||
#: tickets/models/ticket/general.py:88
|
||||
#: tickets/models/ticket/general.py:90
|
||||
msgid "Ticket assignee"
|
||||
msgstr "割り当てられたチケット"
|
||||
|
||||
#: tickets/models/ticket/general.py:260
|
||||
#: tickets/models/ticket/general.py:270
|
||||
msgid "Title"
|
||||
msgstr "タイトル"
|
||||
|
||||
#: tickets/models/ticket/general.py:276
|
||||
#: tickets/models/ticket/general.py:286
|
||||
msgid "Applicant"
|
||||
msgstr "応募者"
|
||||
|
||||
#: tickets/models/ticket/general.py:281
|
||||
#: tickets/models/ticket/general.py:291
|
||||
msgid "TicketFlow"
|
||||
msgstr "作業指示プロセス"
|
||||
|
||||
#: tickets/models/ticket/general.py:284
|
||||
#: tickets/models/ticket/general.py:294
|
||||
msgid "Approval step"
|
||||
msgstr "承認ステップ"
|
||||
|
||||
#: tickets/models/ticket/general.py:287
|
||||
#: tickets/models/ticket/general.py:297
|
||||
msgid "Relation snapshot"
|
||||
msgstr "製造オーダスナップショット"
|
||||
|
||||
#: tickets/models/ticket/general.py:380
|
||||
#: tickets/models/ticket/general.py:390
|
||||
msgid "Please try again"
|
||||
msgstr "もう一度お試しください"
|
||||
|
||||
#: tickets/models/ticket/general.py:387
|
||||
#: tickets/models/ticket/general.py:421
|
||||
msgid "Super ticket"
|
||||
msgstr "スーパーチケット"
|
||||
|
||||
|
@ -5414,27 +5428,27 @@ msgstr "ログインシステムユーザー"
|
|||
msgid "Login datetime"
|
||||
msgstr "ログイン日時"
|
||||
|
||||
#: tickets/notifications.py:64
|
||||
#: tickets/notifications.py:63
|
||||
msgid "Ticket basic info"
|
||||
msgstr "チケット基本情報"
|
||||
|
||||
#: tickets/notifications.py:65
|
||||
#: tickets/notifications.py:64
|
||||
msgid "Ticket applied info"
|
||||
msgstr "チケット適用情報"
|
||||
|
||||
#: tickets/notifications.py:116
|
||||
msgid "Your has a new ticket"
|
||||
#: tickets/notifications.py:109
|
||||
msgid "Your has a new ticket, applicant - {}"
|
||||
msgstr "新しいチケットがあります- {}"
|
||||
|
||||
#: tickets/notifications.py:120
|
||||
#: tickets/notifications.py:113
|
||||
msgid "{}: New Ticket - {} ({})"
|
||||
msgstr "新しいチケット- {} ({})"
|
||||
|
||||
#: tickets/notifications.py:164
|
||||
#: tickets/notifications.py:157
|
||||
msgid "Your ticket has been processed, processor - {}"
|
||||
msgstr "チケットが処理されました。プロセッサー- {}"
|
||||
|
||||
#: tickets/notifications.py:168
|
||||
#: tickets/notifications.py:161
|
||||
msgid "Ticket has processed - {} ({})"
|
||||
msgstr "チケットが処理済み- {} ({})"
|
||||
|
||||
|
@ -5455,19 +5469,19 @@ msgid "Processor"
|
|||
msgstr "プロセッサ"
|
||||
|
||||
#: tickets/serializers/ticket/common.py:16
|
||||
#: tickets/serializers/ticket/common.py:68
|
||||
#: tickets/serializers/ticket/common.py:79
|
||||
msgid "Created by ticket ({}-{})"
|
||||
msgstr "チケットで作成 ({}-{})"
|
||||
|
||||
#: tickets/serializers/ticket/common.py:58
|
||||
#: tickets/serializers/ticket/common.py:69
|
||||
msgid "The expiration date should be greater than the start date"
|
||||
msgstr "有効期限は開始日より大きくする必要があります"
|
||||
|
||||
#: tickets/serializers/ticket/common.py:74
|
||||
#: tickets/serializers/ticket/common.py:85
|
||||
msgid "Permission named `{}` already exists"
|
||||
msgstr "'{}'という名前の権限は既に存在します"
|
||||
|
||||
#: tickets/serializers/ticket/ticket.py:89
|
||||
#: tickets/serializers/ticket/ticket.py:92
|
||||
msgid "The ticket flow `{}` does not exist"
|
||||
msgstr "チケットフロー '{}'が存在しない"
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ msgstr "激活中"
|
|||
#: 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/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
|
||||
#: xpack/plugins/change_auth_plan/models/base.py:44
|
||||
#: 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
|
||||
#: authentication/models.py:69 perms/models/application_permission.py:24
|
||||
#: 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/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:153
|
||||
msgid "Type"
|
||||
|
@ -411,6 +411,8 @@ msgstr "应用路径"
|
|||
|
||||
#: applications/serializers/attrs/application_category/remote_app.py:44
|
||||
#: 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:70
|
||||
#: xpack/plugins/change_auth_plan/serializers/asset.py:73
|
||||
|
@ -498,7 +500,7 @@ msgid "Charset"
|
|||
msgstr "编码"
|
||||
|
||||
#: assets/models/asset.py:141 assets/serializers/asset.py:176
|
||||
#: tickets/models/ticket/general.py:288
|
||||
#: tickets/models/ticket/general.py:298
|
||||
msgid "Meta"
|
||||
msgstr "元数据"
|
||||
|
||||
|
@ -520,7 +522,7 @@ msgstr "制造商"
|
|||
msgid "Model"
|
||||
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"
|
||||
msgstr "序列号"
|
||||
|
||||
|
@ -1513,7 +1515,7 @@ msgid "MFA"
|
|||
msgstr "MFA"
|
||||
|
||||
#: 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
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
@ -2547,7 +2549,7 @@ msgstr "%(name)s 创建成功"
|
|||
msgid "%(name)s was updated successfully"
|
||||
msgstr "%(name)s 更新成功"
|
||||
|
||||
#: common/db/encoder.py:10
|
||||
#: common/db/encoder.py:11
|
||||
msgid "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/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48
|
||||
#: 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"
|
||||
msgstr "组织"
|
||||
|
||||
|
@ -3374,7 +3376,7 @@ msgstr "我的应用"
|
|||
msgid "Ticket comment"
|
||||
msgstr "工单评论"
|
||||
|
||||
#: rbac/tree.py:115 tickets/models/ticket/general.py:295
|
||||
#: rbac/tree.py:115 tickets/models/ticket/general.py:305
|
||||
msgid "Ticket"
|
||||
msgstr "工单管理"
|
||||
|
||||
|
@ -4863,7 +4865,7 @@ msgstr "链接过期"
|
|||
msgid "User not allowed to join"
|
||||
msgstr "来源 IP 不被允许登录"
|
||||
|
||||
#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:58
|
||||
#: terminal/models/sharing.py:85 terminal/serializers/sharing.py:59
|
||||
msgid "Joiner"
|
||||
msgstr "加入者"
|
||||
|
||||
|
@ -4920,11 +4922,11 @@ msgstr "运行时间"
|
|||
msgid "Default storage"
|
||||
msgstr "默认存储"
|
||||
|
||||
#: terminal/models/storage.py:136 terminal/models/terminal.py:108
|
||||
#: terminal/models/storage.py:137 terminal/models/terminal.py:108
|
||||
msgid "Command storage"
|
||||
msgstr "命令存储"
|
||||
|
||||
#: terminal/models/storage.py:196 terminal/models/terminal.py:109
|
||||
#: terminal/models/storage.py:197 terminal/models/terminal.py:109
|
||||
msgid "Replay storage"
|
||||
msgstr "录像存储"
|
||||
|
||||
|
@ -5173,7 +5175,19 @@ msgid ""
|
|||
msgstr ""
|
||||
"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 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"
|
||||
msgstr "{} {} 工单"
|
||||
|
||||
|
@ -5189,8 +5203,8 @@ msgstr "申请登录的城市"
|
|||
msgid "Applied login datetime"
|
||||
msgstr "申请登录的日期"
|
||||
|
||||
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:39
|
||||
#: tickets/models/ticket/general.py:267
|
||||
#: tickets/models/comment.py:13 tickets/models/ticket/general.py:41
|
||||
#: tickets/models/ticket/general.py:277
|
||||
msgid "State"
|
||||
msgstr "状态"
|
||||
|
||||
|
@ -5207,7 +5221,7 @@ msgid "Body"
|
|||
msgstr "内容"
|
||||
|
||||
#: 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"
|
||||
msgstr "审批级别"
|
||||
|
||||
|
@ -5233,8 +5247,8 @@ msgstr "工单会话"
|
|||
|
||||
#: tickets/models/ticket/apply_application.py:11
|
||||
#: tickets/models/ticket/apply_asset.py:13
|
||||
msgid "Apply name"
|
||||
msgstr "应用名称"
|
||||
msgid "Permission name"
|
||||
msgstr "授权规则名称"
|
||||
|
||||
#: tickets/models/ticket/apply_application.py:20
|
||||
msgid "Apply applications"
|
||||
|
@ -5282,39 +5296,39 @@ msgstr "来自命令过滤规则"
|
|||
msgid "From cmd filter rule"
|
||||
msgstr "来自命令过滤规则"
|
||||
|
||||
#: tickets/models/ticket/general.py:70
|
||||
#: tickets/models/ticket/general.py:72
|
||||
msgid "Ticket step"
|
||||
msgstr "工单步骤"
|
||||
|
||||
#: tickets/models/ticket/general.py:88
|
||||
#: tickets/models/ticket/general.py:90
|
||||
msgid "Ticket assignee"
|
||||
msgstr "工单受理人"
|
||||
|
||||
#: tickets/models/ticket/general.py:260
|
||||
#: tickets/models/ticket/general.py:270
|
||||
msgid "Title"
|
||||
msgstr "标题"
|
||||
|
||||
#: tickets/models/ticket/general.py:276
|
||||
#: tickets/models/ticket/general.py:286
|
||||
msgid "Applicant"
|
||||
msgstr "申请人"
|
||||
|
||||
#: tickets/models/ticket/general.py:281
|
||||
#: tickets/models/ticket/general.py:291
|
||||
msgid "TicketFlow"
|
||||
msgstr "工单流程"
|
||||
|
||||
#: tickets/models/ticket/general.py:284
|
||||
#: tickets/models/ticket/general.py:294
|
||||
msgid "Approval step"
|
||||
msgstr "审批步骤"
|
||||
|
||||
#: tickets/models/ticket/general.py:287
|
||||
#: tickets/models/ticket/general.py:297
|
||||
msgid "Relation snapshot"
|
||||
msgstr "工单快照"
|
||||
|
||||
#: tickets/models/ticket/general.py:380
|
||||
#: tickets/models/ticket/general.py:390
|
||||
msgid "Please try again"
|
||||
msgstr "请再次尝试"
|
||||
|
||||
#: tickets/models/ticket/general.py:387
|
||||
#: tickets/models/ticket/general.py:421
|
||||
msgid "Super ticket"
|
||||
msgstr "超级工单"
|
||||
|
||||
|
@ -5334,27 +5348,27 @@ msgstr "登录系统用户"
|
|||
msgid "Login datetime"
|
||||
msgstr "登录日期"
|
||||
|
||||
#: tickets/notifications.py:64
|
||||
#: tickets/notifications.py:63
|
||||
msgid "Ticket basic info"
|
||||
msgstr "工单基本信息"
|
||||
|
||||
#: tickets/notifications.py:65
|
||||
#: tickets/notifications.py:64
|
||||
msgid "Ticket applied info"
|
||||
msgstr "工单申请信息"
|
||||
|
||||
#: tickets/notifications.py:116
|
||||
msgid "Your has a new ticket"
|
||||
#: tickets/notifications.py:109
|
||||
msgid "Your has a new ticket, applicant - {}"
|
||||
msgstr "你有一个新的工单, 申请人 - {}"
|
||||
|
||||
#: tickets/notifications.py:120
|
||||
#: tickets/notifications.py:113
|
||||
msgid "{}: New Ticket - {} ({})"
|
||||
msgstr "新工单 - {} ({})"
|
||||
|
||||
#: tickets/notifications.py:164
|
||||
#: tickets/notifications.py:157
|
||||
msgid "Your ticket has been processed, processor - {}"
|
||||
msgstr "你的工单已被处理, 处理人 - {}"
|
||||
|
||||
#: tickets/notifications.py:168
|
||||
#: tickets/notifications.py:161
|
||||
msgid "Ticket has processed - {} ({})"
|
||||
msgstr "你的工单已被处理, 处理人 - {} ({})"
|
||||
|
||||
|
@ -5375,19 +5389,19 @@ msgid "Processor"
|
|||
msgstr "处理人"
|
||||
|
||||
#: tickets/serializers/ticket/common.py:16
|
||||
#: tickets/serializers/ticket/common.py:68
|
||||
#: tickets/serializers/ticket/common.py:79
|
||||
msgid "Created by ticket ({}-{})"
|
||||
msgstr "通过工单创建 ({}-{})"
|
||||
|
||||
#: tickets/serializers/ticket/common.py:58
|
||||
#: tickets/serializers/ticket/common.py:69
|
||||
msgid "The expiration date should be greater than the start date"
|
||||
msgstr "过期时间要大于开始时间"
|
||||
|
||||
#: tickets/serializers/ticket/common.py:74
|
||||
#: tickets/serializers/ticket/common.py:85
|
||||
msgid "Permission named `{}` already exists"
|
||||
msgstr "授权名称 `{}` 已存在"
|
||||
|
||||
#: tickets/serializers/ticket/ticket.py:89
|
||||
#: tickets/serializers/ticket/ticket.py:92
|
||||
msgid "The ticket flow `{}` does not exist"
|
||||
msgstr "工单流程 `{}` 不存在"
|
||||
|
||||
|
|
|
@ -21,8 +21,12 @@ class ActionsField(serializers.MultipleChoiceField):
|
|||
return Action.value_to_choices(value)
|
||||
|
||||
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 Action.choices_to_value(data)
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.response import Response
|
||||
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 orgs.utils import tmp_to_root_org
|
||||
|
||||
|
@ -71,32 +71,34 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
|||
with tmp_to_root_org():
|
||||
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):
|
||||
partial = kwargs.pop('partial', False)
|
||||
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)
|
||||
return Response(serializer.data)
|
||||
return Response('ok')
|
||||
|
||||
@action(detail=True, methods=[PUT], permission_classes=[IsAssignee, ])
|
||||
def reject(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
instance.reject(processor=request.user)
|
||||
return Response(serializer.data)
|
||||
return Response('ok')
|
||||
|
||||
@action(detail=True, methods=[PUT], permission_classes=[IsApplicant, ])
|
||||
def close(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
instance.close()
|
||||
return Response(serializer.data)
|
||||
return Response('ok')
|
||||
|
||||
|
||||
class ApplyAssetTicketViewSet(TicketViewSet):
|
||||
serializer_class = serializers.ApplyAssetDisplaySerializer
|
||||
serializer_classes = {
|
||||
'open': serializers.ApplyAssetSerializer
|
||||
'open': serializers.ApplyAssetSerializer,
|
||||
'approve': serializers.ApproveAssetSerializer
|
||||
}
|
||||
model = ApplyAssetTicket
|
||||
filterset_class = filters.ApplyAssetTicketFilter
|
||||
|
@ -105,7 +107,8 @@ class ApplyAssetTicketViewSet(TicketViewSet):
|
|||
class ApplyApplicationTicketViewSet(TicketViewSet):
|
||||
serializer_class = serializers.ApplyApplicationDisplaySerializer
|
||||
serializer_classes = {
|
||||
'open': serializers.ApplyApplicationSerializer
|
||||
'open': serializers.ApplyApplicationSerializer,
|
||||
'approve': serializers.ApproveApplicationSerializer
|
||||
}
|
||||
model = ApplyApplicationTicket
|
||||
filterset_class = filters.ApplyApplicationTicketFilter
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from common.utils import get_logger
|
||||
from tickets.utils import (
|
||||
send_ticket_processed_mail_to_applicant,
|
||||
send_ticket_applied_mail_to_assignees
|
||||
)
|
||||
from tickets.const import TicketState, TicketStatus
|
||||
from tickets.const import TicketState, TicketType
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
@ -59,6 +60,25 @@ class BaseHandler:
|
|||
logger.debug('Send processed mail to applicant: {}'.format(applicant))
|
||||
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):
|
||||
# 打开或关闭工单,备注显示是自己,其他是受理人
|
||||
if state in [TicketState.reopen, TicketState.pending, TicketState.closed]:
|
||||
|
@ -68,8 +88,11 @@ class BaseHandler:
|
|||
|
||||
user_display = str(user)
|
||||
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 = {
|
||||
'body': _('{} {} the ticket').format(user_display, state_display),
|
||||
'body': render_to_string('tickets/ticket_approve_diff.html', context),
|
||||
'user': user,
|
||||
'user_display': str(user),
|
||||
'type': 'state',
|
||||
|
|
|
@ -102,7 +102,7 @@ def apply_asset_migrate(apps, *args):
|
|||
'applicant': instance.applicant_display,
|
||||
'apply_nodes': meta.get('apply_nodes_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.save(update_fields=['rel_snapshot'])
|
||||
|
@ -140,7 +140,7 @@ def apply_application_migrate(apps, *args):
|
|||
rel_snapshot = {
|
||||
'applicant': instance.applicant_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.save(update_fields=['rel_snapshot'])
|
||||
|
|
|
@ -8,7 +8,7 @@ __all__ = ['ApplyApplicationTicket']
|
|||
|
||||
|
||||
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(
|
||||
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):
|
||||
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_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets'))
|
||||
|
@ -26,3 +26,6 @@ class ApplyAssetTicket(Ticket):
|
|||
@property
|
||||
def apply_actions_display(self):
|
||||
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 -*-
|
||||
#
|
||||
import json
|
||||
from typing import Callable
|
||||
|
||||
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.db.utils import IntegrityError
|
||||
from django.db.models.fields import related
|
||||
from django.forms import model_to_dict
|
||||
|
||||
from common.exceptions import JMSException
|
||||
from common.utils.timezone import as_current_tz
|
||||
|
@ -97,6 +99,7 @@ class StatusMixin:
|
|||
|
||||
state: str
|
||||
status: str
|
||||
applicant_id: str
|
||||
applicant: models.ForeignKey
|
||||
current_step: TicketStep
|
||||
save: Callable
|
||||
|
@ -130,6 +133,7 @@ class StatusMixin:
|
|||
self._open()
|
||||
|
||||
def approve(self, processor):
|
||||
self.set_rel_snapshot()
|
||||
self._change_state(StepState.approved, processor)
|
||||
|
||||
def reject(self, processor):
|
||||
|
@ -178,28 +182,34 @@ class StatusMixin:
|
|||
@property
|
||||
def process_map(self):
|
||||
process_map = []
|
||||
steps = self.ticket_steps.all()
|
||||
for step in steps:
|
||||
for step in self.ticket_steps.all():
|
||||
processor_id = ''
|
||||
assignee_ids = []
|
||||
processor_display = ''
|
||||
assignees_display = []
|
||||
ticket_assignees = step.ticket_assignees.all()
|
||||
processor = None
|
||||
state = step.state
|
||||
for i in ticket_assignees:
|
||||
assignee_ids.append(i.assignee_id)
|
||||
assignees_display.append(str(i.assignee))
|
||||
for i in step.ticket_assignees.all().prefetch_related('assignee'):
|
||||
assignee_id = i.assignee_id
|
||||
assignee_display = str(i.assignee)
|
||||
|
||||
if state != StepState.pending and state == i.state:
|
||||
processor = i.assignee
|
||||
processor_id = assignee_id
|
||||
processor_display = assignee_display
|
||||
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 = {
|
||||
'state': state,
|
||||
'approval_level': step.level,
|
||||
'assignees': assignee_ids,
|
||||
'assignees_display': assignees_display,
|
||||
'approval_date': str(step.date_updated),
|
||||
'processor': processor.id if processor else '',
|
||||
'processor_display': str(processor) if processor else ''
|
||||
'processor': processor_id,
|
||||
'processor_display': processor_display
|
||||
}
|
||||
process_map.append(step_info)
|
||||
return process_map
|
||||
|
@ -380,6 +390,30 @@ class Ticket(StatusMixin, CommonModelMixin):
|
|||
raise JMSException(detail=_('Please try again'), code='please_try_again')
|
||||
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 Meta:
|
||||
|
|
|
@ -4,7 +4,6 @@ import json
|
|||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.shortcuts import reverse
|
||||
from django.db.models.fields import related
|
||||
from django.template.loader import render_to_string
|
||||
from django.forms import model_to_dict
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -75,13 +74,7 @@ class BaseTicketMessage(UserMessage):
|
|||
for name in item_names:
|
||||
field = fields[name]
|
||||
item = {'name': name, 'title': field.verbose_name}
|
||||
value = data.get(name)
|
||||
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])
|
||||
value = self.ticket.get_field_display(name, field, data)
|
||||
item['value'] = value
|
||||
items.append(item)
|
||||
return items
|
||||
|
@ -113,7 +106,7 @@ class TicketAppliedToAssigneeMessage(BaseTicketMessage):
|
|||
|
||||
@property
|
||||
def content_title(self):
|
||||
return _('Your has a new ticket')
|
||||
return _('Your has a new ticket, applicant - {}').format(self.ticket.applicant)
|
||||
|
||||
@property
|
||||
def subject(self):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from perms.models import ApplicationPermission
|
||||
|
@ -7,7 +8,7 @@ from tickets.models import ApplyApplicationTicket
|
|||
from .ticket import TicketApplySerializer
|
||||
from .common import BaseApplyAssetApplicationSerializer
|
||||
|
||||
__all__ = ['ApplyApplicationSerializer', 'ApplyApplicationDisplaySerializer']
|
||||
__all__ = ['ApplyApplicationSerializer', 'ApplyApplicationDisplaySerializer', 'ApproveApplicationSerializer']
|
||||
|
||||
|
||||
class ApplyApplicationSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer):
|
||||
|
@ -24,15 +25,23 @@ class ApplyApplicationSerializer(BaseApplyAssetApplicationSerializer, TicketAppl
|
|||
read_only_fields = list(set(fields) - set(writeable_fields))
|
||||
ticket_extra_kwargs = TicketApplySerializer.Meta.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)
|
||||
|
||||
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')
|
||||
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):
|
||||
apply_applications = serializers.SerializerMethodField()
|
||||
apply_system_users = serializers.SerializerMethodField()
|
||||
|
|
|
@ -10,13 +10,13 @@ from tickets.models import ApplyAssetTicket
|
|||
from .ticket import TicketApplySerializer
|
||||
from .common import BaseApplyAssetApplicationSerializer
|
||||
|
||||
__all__ = ['ApplyAssetSerializer', 'ApplyAssetDisplaySerializer']
|
||||
__all__ = ['ApplyAssetSerializer', 'ApplyAssetDisplaySerializer', 'ApproveAssetSerializer']
|
||||
|
||||
asset_or_node_help_text = _("Select at least one asset or node")
|
||||
|
||||
|
||||
class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer):
|
||||
apply_actions = ActionsField(required=True, allow_null=True)
|
||||
apply_actions = ActionsField(required=True, allow_empty=False)
|
||||
permission_model = AssetPermission
|
||||
|
||||
class Meta:
|
||||
|
@ -30,9 +30,9 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria
|
|||
read_only_fields = list(set(fields) - set(writeable_fields))
|
||||
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
|
||||
extra_kwargs = {
|
||||
'apply_nodes': {'required': False, 'help_text': asset_or_node_help_text},
|
||||
'apply_assets': {'required': False, 'help_text': asset_or_node_help_text},
|
||||
'apply_system_users': {'required': True},
|
||||
'apply_nodes': {'required': False, 'allow_empty': True},
|
||||
'apply_assets': {'required': False, 'allow_empty': True},
|
||||
'apply_system_users': {'required': False, 'allow_empty': True},
|
||||
}
|
||||
extra_kwargs.update(ticket_extra_kwargs)
|
||||
|
||||
|
@ -44,14 +44,22 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria
|
|||
|
||||
def validate(self, 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({
|
||||
'apply_nodes': asset_or_node_help_text,
|
||||
'apply_assets': asset_or_node_help_text,
|
||||
})
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class ApproveAssetSerializer(ApplyAssetSerializer):
|
||||
class Meta(ApplyAssetSerializer.Meta):
|
||||
read_only_fields = ApplyAssetSerializer.Meta.read_only_fields + ['title', 'type']
|
||||
|
||||
|
||||
class ApplyAssetDisplaySerializer(ApplyAssetSerializer):
|
||||
apply_nodes = serializers.SerializerMethodField()
|
||||
apply_assets = serializers.SerializerMethodField()
|
||||
|
|
|
@ -38,14 +38,25 @@ class DefaultPermissionName(object):
|
|||
class BaseApplyAssetApplicationSerializer(serializers.Serializer):
|
||||
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):
|
||||
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]
|
||||
with tmp_to_org(org_id):
|
||||
qs = model.objects.filter(id__in=ids, **kwargs).values_list('id', flat=True)
|
||||
return list(qs)
|
||||
|
||||
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)
|
||||
|
||||
def validate(self, attrs):
|
||||
|
@ -72,3 +83,10 @@ class BaseApplyAssetApplicationSerializer(serializers.Serializer):
|
|||
instance.save()
|
||||
return instance
|
||||
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
|
||||
|
||||
def validate(self, attrs):
|
||||
if self.instance:
|
||||
return attrs
|
||||
|
||||
ticket_type = attrs.get('type')
|
||||
org_id = attrs.get('org_id')
|
||||
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