mirror of https://github.com/jumpserver/jumpserver
[Update] 修改工单
parent
18f8864720
commit
ebe129b3c2
|
@ -12,7 +12,7 @@ from ..models import LoginConfirmSetting
|
|||
from ..serializers import LoginConfirmSettingSerializer
|
||||
from .. import errors, mixins
|
||||
|
||||
__all__ = ['LoginConfirmSettingUpdateApi', 'LoginConfirmTicketStatusApi']
|
||||
__all__ = ['LoginConfirmSettingUpdateApi', 'TicketStatusApi']
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
|
@ -31,17 +31,17 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
|
|||
return s
|
||||
|
||||
|
||||
class LoginConfirmTicketStatusApi(mixins.AuthMixin, APIView):
|
||||
class TicketStatusApi(mixins.AuthMixin, APIView):
|
||||
permission_classes = ()
|
||||
|
||||
def get_ticket(self):
|
||||
from tickets.models import LoginConfirmTicket
|
||||
from tickets.models import Ticket
|
||||
ticket_id = self.request.session.get("auth_ticket_id")
|
||||
logger.debug('Login confirm ticket id: {}'.format(ticket_id))
|
||||
if not ticket_id:
|
||||
ticket = None
|
||||
else:
|
||||
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
|
||||
ticket = get_object_or_none(Ticket, pk=ticket_id)
|
||||
return ticket
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
|
|
@ -104,13 +104,13 @@ class AuthMixin:
|
|||
raise errors.MFAFailedError(username=user.username, request=self.request)
|
||||
|
||||
def get_ticket(self):
|
||||
from tickets.models import LoginConfirmTicket
|
||||
from tickets.models import Ticket
|
||||
ticket_id = self.request.session.get("auth_ticket_id")
|
||||
logger.debug('Login confirm ticket id: {}'.format(ticket_id))
|
||||
if not ticket_id:
|
||||
ticket = None
|
||||
else:
|
||||
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
|
||||
ticket = get_object_or_none(Ticket, pk=ticket_id)
|
||||
return ticket
|
||||
|
||||
def get_ticket_or_create(self, confirm_setting):
|
||||
|
|
|
@ -49,23 +49,25 @@ class LoginConfirmSetting(CommonModelMixin):
|
|||
return get_object_or_none(cls, user=user)
|
||||
|
||||
def create_confirm_ticket(self, request=None):
|
||||
from tickets.models import LoginConfirmTicket
|
||||
title = _('User login confirm: {}').format(self.user)
|
||||
from tickets.models import Ticket
|
||||
title = '[' + __('Login confirm') + ']: {}'.format(self.user)
|
||||
if request:
|
||||
remote_addr = get_request_ip(request)
|
||||
city = get_ip_city(remote_addr)
|
||||
body = _("User: {}\nIP: {}\nCity: {}\nDate: {}\n").format(
|
||||
self.user, remote_addr, city, timezone.now()
|
||||
body = __("{user_key}: {username}<br>"
|
||||
"IP: {ip}<br>"
|
||||
"{city_key}: {city}<br>"
|
||||
"{date_key}: {date}<br>").format(
|
||||
user_key=__("User"), username=self.user,
|
||||
ip=remote_addr, city_key=_("City"), city=city,
|
||||
date_key=__("Datetime"), date=timezone.now()
|
||||
)
|
||||
else:
|
||||
city = 'Localhost'
|
||||
remote_addr = '127.0.0.1'
|
||||
body = ''
|
||||
reviewer = self.reviewers.all()
|
||||
ticket = LoginConfirmTicket.objects.create(
|
||||
ticket = Ticket.objects.create(
|
||||
user=self.user, title=title, body=body,
|
||||
city=city, ip=remote_addr,
|
||||
type=LoginConfirmTicket.TYPE_LOGIN_CONFIRM,
|
||||
type=Ticket.TYPE_LOGIN_CONFIRM,
|
||||
)
|
||||
ticket.assignees.set(reviewer)
|
||||
return ticket
|
||||
|
|
|
@ -126,7 +126,7 @@ function handleProgressBar() {
|
|||
progressBarRef.attr('aria-valuenow', offset);
|
||||
}
|
||||
|
||||
function cancelLoginConfirmTicket() {
|
||||
function cancelTicket() {
|
||||
requestApi({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
|
@ -144,7 +144,7 @@ function setCloseConfirm() {
|
|||
return 'Confirm';
|
||||
};
|
||||
window.onunload = function (e) {
|
||||
cancelLoginConfirmTicket();
|
||||
cancelTicket();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ urlpatterns = [
|
|||
path('connection-token/',
|
||||
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
|
||||
path('login-confirm-ticket/status/', api.LoginConfirmTicketStatusApi.as_view(), name='login-confirm-ticket-status'),
|
||||
path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'),
|
||||
path('login-confirm-settings/<uuid:user_id>/', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update')
|
||||
]
|
||||
|
||||
|
|
|
@ -144,16 +144,16 @@ class UserLoginWaitConfirmView(TemplateView):
|
|||
template_name = 'authentication/login_wait_confirm.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
from tickets.models import LoginConfirmTicket
|
||||
from tickets.models import Ticket
|
||||
ticket_id = self.request.session.get("auth_ticket_id")
|
||||
if not ticket_id:
|
||||
ticket = None
|
||||
else:
|
||||
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
|
||||
ticket = get_object_or_none(Ticket, pk=ticket_id)
|
||||
context = super().get_context_data(**kwargs)
|
||||
if ticket:
|
||||
timestamp_created = datetime.datetime.timestamp(ticket.date_created)
|
||||
ticket_detail_url = reverse('tickets:login-confirm-ticket-detail', kwargs={'pk': ticket_id})
|
||||
ticket_detail_url = reverse('tickets:ticket-detail', kwargs={'pk': ticket_id})
|
||||
msg = _("""Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>
|
||||
Don't close this page""").format(ticket.assignees_display)
|
||||
else:
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Jumpserver 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-11-14 19:29+0800\n"
|
||||
"POT-Creation-Date: 2019-11-15 17:39+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
||||
|
@ -354,7 +354,6 @@ msgstr "重置"
|
|||
#: terminal/templates/terminal/command_list.html:47
|
||||
#: terminal/templates/terminal/session_list.html:52
|
||||
#: terminal/templates/terminal/terminal_update.html:46
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:32
|
||||
#: users/templates/users/_user.html:52
|
||||
#: users/templates/users/forgot_password.html:42
|
||||
#: users/templates/users/user_bulk_update.html:24
|
||||
|
@ -528,8 +527,7 @@ msgstr "创建远程应用"
|
|||
#: settings/templates/settings/terminal_setting.html:107
|
||||
#: terminal/templates/terminal/session_list.html:36
|
||||
#: terminal/templates/terminal/terminal_list.html:36
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:18
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:106
|
||||
#: tickets/templates/tickets/ticket_list.html:93
|
||||
#: users/templates/users/_granted_assets.html:34
|
||||
#: users/templates/users/user_group_list.html:38
|
||||
#: users/templates/users/user_list.html:41
|
||||
|
@ -1046,7 +1044,8 @@ msgstr "过滤器"
|
|||
#: settings/templates/settings/replay_storage_create.html:31
|
||||
#: settings/templates/settings/terminal_setting.html:84
|
||||
#: settings/templates/settings/terminal_setting.html:106
|
||||
#: tickets/models/base.py:34 tickets/templates/tickets/ticket_detail.html:33
|
||||
#: tickets/models/ticket.py:42 tickets/templates/tickets/ticket_detail.html:33
|
||||
#: tickets/templates/tickets/ticket_list.html:23
|
||||
msgid "Type"
|
||||
msgstr "类型"
|
||||
|
||||
|
@ -1127,11 +1126,10 @@ msgstr "默认资产组"
|
|||
#: terminal/models.py:156 terminal/templates/terminal/command_list.html:29
|
||||
#: terminal/templates/terminal/command_list.html:65
|
||||
#: terminal/templates/terminal/session_list.html:27
|
||||
#: terminal/templates/terminal/session_list.html:71 tickets/models/base.py:25
|
||||
#: tickets/models/base.py:68
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:15
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:101
|
||||
#: tickets/templates/tickets/ticket_detail.html:32 users/forms.py:339
|
||||
#: terminal/templates/terminal/session_list.html:71 tickets/models/ticket.py:32
|
||||
#: tickets/models/ticket.py:85 tickets/templates/tickets/ticket_detail.html:32
|
||||
#: tickets/templates/tickets/ticket_list.html:22
|
||||
#: tickets/templates/tickets/ticket_list.html:88 users/forms.py:339
|
||||
#: users/models/user.py:149 users/models/user.py:165 users/models/user.py:537
|
||||
#: users/serializers/group.py:21
|
||||
#: users/templates/users/user_group_detail.html:78
|
||||
|
@ -1506,7 +1504,7 @@ msgstr "获取认证信息错误"
|
|||
#: authentication/templates/authentication/_access_key_modal.html:142
|
||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:53
|
||||
#: settings/templates/settings/_ldap_list_users_modal.html:171
|
||||
#: templates/_modal.html:22 tickets/models/base.py:50
|
||||
#: templates/_modal.html:22 tickets/models/ticket.py:67
|
||||
#: tickets/templates/tickets/ticket_detail.html:103
|
||||
msgid "Close"
|
||||
msgstr "关闭"
|
||||
|
@ -1517,7 +1515,7 @@ msgstr "关闭"
|
|||
#: ops/templates/ops/task_adhoc.html:63
|
||||
#: terminal/templates/terminal/command_list.html:33
|
||||
#: terminal/templates/terminal/session_detail.html:50
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:17
|
||||
#: tickets/templates/tickets/ticket_list.html:25
|
||||
msgid "Datetime"
|
||||
msgstr "日期"
|
||||
|
||||
|
@ -1717,7 +1715,7 @@ msgstr "Jumpserver 使用该用户来 `推送系统用户`、`获取资产硬件
|
|||
#: users/templates/users/user_group_list.html:10
|
||||
#: users/templates/users/user_list.html:10
|
||||
#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:49
|
||||
#: xpack/plugins/vault/templates/vault/vault.html:55
|
||||
#: xpack/plugins/vault/templates/vault/vault.html:47
|
||||
msgid "Export"
|
||||
msgstr "导出"
|
||||
|
||||
|
@ -1728,7 +1726,7 @@ msgstr "导出"
|
|||
#: users/templates/users/user_group_list.html:15
|
||||
#: users/templates/users/user_list.html:15
|
||||
#: xpack/plugins/license/templates/license/license_detail.html:110
|
||||
#: xpack/plugins/vault/templates/vault/vault.html:60
|
||||
#: xpack/plugins/vault/templates/vault/vault.html:52
|
||||
msgid "Import"
|
||||
msgstr "导入"
|
||||
|
||||
|
@ -1747,7 +1745,7 @@ msgstr "创建管理用户"
|
|||
#: users/templates/users/user_group_list.html:195
|
||||
#: users/templates/users/user_list.html:165
|
||||
#: users/templates/users/user_list.html:197
|
||||
#: xpack/plugins/vault/templates/vault/vault.html:224
|
||||
#: xpack/plugins/vault/templates/vault/vault.html:200
|
||||
msgid "Please select file"
|
||||
msgstr "选择文件"
|
||||
|
||||
|
@ -2239,7 +2237,7 @@ msgstr "成功"
|
|||
|
||||
#: audits/models.py:33
|
||||
#: authentication/templates/authentication/_access_key_modal.html:22
|
||||
#: xpack/plugins/vault/templates/vault/vault.html:46
|
||||
#: xpack/plugins/vault/templates/vault/vault.html:38
|
||||
msgid "Create"
|
||||
msgstr "创建"
|
||||
|
||||
|
@ -2306,9 +2304,9 @@ msgid "Reason"
|
|||
msgstr "原因"
|
||||
|
||||
#: audits/models.py:88 audits/templates/audits/login_log_list.html:64
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:16
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:102
|
||||
#: tickets/templates/tickets/ticket_detail.html:34
|
||||
#: tickets/templates/tickets/ticket_list.html:24
|
||||
#: tickets/templates/tickets/ticket_list.html:89
|
||||
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310
|
||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
|
||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65
|
||||
|
@ -2368,7 +2366,7 @@ msgstr "ID"
|
|||
msgid "UA"
|
||||
msgstr "Agent"
|
||||
|
||||
#: audits/templates/audits/login_log_list.html:61
|
||||
#: audits/templates/audits/login_log_list.html:61 authentication/models.py:62
|
||||
msgid "City"
|
||||
msgstr "城市"
|
||||
|
||||
|
@ -2379,23 +2377,23 @@ msgid "Date"
|
|||
msgstr "日期"
|
||||
|
||||
#: audits/views.py:86 audits/views.py:130 audits/views.py:167
|
||||
#: audits/views.py:212 audits/views.py:244 templates/_nav.html:139
|
||||
#: audits/views.py:212 audits/views.py:244 templates/_nav.html:137
|
||||
msgid "Audits"
|
||||
msgstr "日志审计"
|
||||
|
||||
#: audits/views.py:87 templates/_nav.html:143
|
||||
#: audits/views.py:87 templates/_nav.html:141
|
||||
msgid "FTP log"
|
||||
msgstr "FTP日志"
|
||||
|
||||
#: audits/views.py:131 templates/_nav.html:144
|
||||
#: audits/views.py:131 templates/_nav.html:142
|
||||
msgid "Operate log"
|
||||
msgstr "操作日志"
|
||||
|
||||
#: audits/views.py:168 templates/_nav.html:145
|
||||
#: audits/views.py:168 templates/_nav.html:143
|
||||
msgid "Password change log"
|
||||
msgstr "改密日志"
|
||||
|
||||
#: audits/views.py:213 templates/_nav.html:142
|
||||
#: audits/views.py:213 templates/_nav.html:140
|
||||
msgid "Login log"
|
||||
msgstr "登录日志"
|
||||
|
||||
|
@ -2530,22 +2528,6 @@ msgstr "ssh密钥"
|
|||
msgid "Reviewers"
|
||||
msgstr "审批人"
|
||||
|
||||
#: authentication/models.py:53
|
||||
msgid "User login confirm: {}"
|
||||
msgstr "用户登录复核: {}"
|
||||
|
||||
#: authentication/models.py:57
|
||||
msgid ""
|
||||
"User: {}\n"
|
||||
"IP: {}\n"
|
||||
"City: {}\n"
|
||||
"Date: {}\n"
|
||||
msgstr ""
|
||||
"用户: {}\n"
|
||||
"IP: {}\n"
|
||||
"城市: {}\n"
|
||||
"日期: {}\n"
|
||||
|
||||
#: authentication/templates/authentication/_access_key_modal.html:6
|
||||
msgid "API key list"
|
||||
msgstr "API Key列表"
|
||||
|
@ -4025,7 +4007,7 @@ msgstr "在ou:{}中没有匹配条目"
|
|||
|
||||
#: settings/views.py:20 settings/views.py:47 settings/views.py:74
|
||||
#: settings/views.py:105 settings/views.py:133 settings/views.py:146
|
||||
#: settings/views.py:160 settings/views.py:187 templates/_nav.html:180
|
||||
#: settings/views.py:160 settings/views.py:187 templates/_nav.html:178
|
||||
msgid "Settings"
|
||||
msgstr "系统设置"
|
||||
|
||||
|
@ -4218,7 +4200,7 @@ msgstr "终端管理"
|
|||
msgid "Job Center"
|
||||
msgstr "作业中心"
|
||||
|
||||
#: templates/_nav.html:116 templates/_nav.html:146
|
||||
#: templates/_nav.html:116 templates/_nav.html:144
|
||||
msgid "Batch command"
|
||||
msgstr "批量命令"
|
||||
|
||||
|
@ -4226,24 +4208,19 @@ msgstr "批量命令"
|
|||
msgid "Task monitor"
|
||||
msgstr "任务监控"
|
||||
|
||||
#: templates/_nav.html:127 tickets/views.py:16 tickets/views.py:30
|
||||
#: templates/_nav.html:128 tickets/views.py:17 tickets/views.py:32
|
||||
msgid "Tickets"
|
||||
msgstr "工单管理"
|
||||
|
||||
#: templates/_nav.html:130 tickets/models/base.py:23
|
||||
#: users/templates/users/user_detail.html:257
|
||||
msgid "Login confirm"
|
||||
msgstr "登录复核"
|
||||
|
||||
#: templates/_nav.html:156
|
||||
#: templates/_nav.html:154
|
||||
msgid "XPack"
|
||||
msgstr ""
|
||||
|
||||
#: templates/_nav.html:164 xpack/plugins/cloud/views.py:28
|
||||
#: templates/_nav.html:162 xpack/plugins/cloud/views.py:28
|
||||
msgid "Account list"
|
||||
msgstr "账户列表"
|
||||
|
||||
#: templates/_nav.html:165
|
||||
#: templates/_nav.html:163
|
||||
msgid "Sync instance"
|
||||
msgstr "同步实例"
|
||||
|
||||
|
@ -4585,10 +4562,7 @@ msgid "Accept"
|
|||
msgstr "接受"
|
||||
|
||||
#: terminal/templates/terminal/terminal_list.html:80
|
||||
#: tickets/models/login_confirm.py:16
|
||||
#: tickets/templates/tickets/login_confirm_ticket_detail.html:10
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:70
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:108
|
||||
#: tickets/models/ticket.py:30 tickets/templates/tickets/ticket_list.html:95
|
||||
msgid "Reject"
|
||||
msgstr "拒绝"
|
||||
|
||||
|
@ -4625,78 +4599,77 @@ msgid ""
|
|||
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
|
||||
msgstr "你可以使用ssh客户端工具连接终端"
|
||||
|
||||
#: tickets/models/base.py:16 tickets/models/base.py:52
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:103
|
||||
#: tickets/models/ticket.py:17 tickets/models/ticket.py:69
|
||||
#: tickets/templates/tickets/ticket_list.html:90
|
||||
msgid "Open"
|
||||
msgstr "开启"
|
||||
|
||||
#: tickets/models/base.py:17
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:104
|
||||
#: tickets/models/ticket.py:18 tickets/templates/tickets/ticket_list.html:91
|
||||
msgid "Closed"
|
||||
msgstr "关闭"
|
||||
|
||||
#: tickets/models/base.py:22
|
||||
#: tickets/models/ticket.py:23
|
||||
msgid "General"
|
||||
msgstr "一般"
|
||||
|
||||
#: tickets/models/base.py:26 tickets/models/base.py:69
|
||||
msgid "User display name"
|
||||
msgstr "用户显示名称"
|
||||
#: tickets/models/ticket.py:24 users/templates/users/user_detail.html:257
|
||||
msgid "Login confirm"
|
||||
msgstr "登录复核"
|
||||
|
||||
#: tickets/models/base.py:28
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:14
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:100
|
||||
msgid "Title"
|
||||
msgstr "标题"
|
||||
|
||||
#: tickets/models/base.py:29 tickets/models/base.py:70
|
||||
msgid "Body"
|
||||
msgstr "内容"
|
||||
|
||||
#: tickets/models/base.py:30 tickets/templates/tickets/ticket_detail.html:51
|
||||
msgid "Assignee"
|
||||
msgstr "处理人"
|
||||
|
||||
#: tickets/models/base.py:31
|
||||
msgid "Assignee display name"
|
||||
msgstr "处理人名称"
|
||||
|
||||
#: tickets/models/base.py:32 tickets/templates/tickets/ticket_detail.html:50
|
||||
msgid "Assignees"
|
||||
msgstr "待处理人"
|
||||
|
||||
#: tickets/models/base.py:33
|
||||
msgid "Assignees display name"
|
||||
msgstr "待处理人名称"
|
||||
|
||||
#: tickets/models/base.py:53
|
||||
msgid "{} {} this ticket"
|
||||
msgstr "{} {} 这个工单"
|
||||
|
||||
#: tickets/models/login_confirm.py:15
|
||||
#: tickets/templates/tickets/login_confirm_ticket_detail.html:9
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:69
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:107
|
||||
#: tickets/models/ticket.py:29 tickets/templates/tickets/ticket_list.html:94
|
||||
msgid "Approve"
|
||||
msgstr "同意"
|
||||
|
||||
#: tickets/models/login_confirm.py:24
|
||||
msgid "this order"
|
||||
msgstr "这个工单"
|
||||
#: tickets/models/ticket.py:33 tickets/models/ticket.py:86
|
||||
msgid "User display name"
|
||||
msgstr "用户显示名称"
|
||||
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:27
|
||||
msgid "Approve selected"
|
||||
msgstr "同意所选"
|
||||
#: tickets/models/ticket.py:35 tickets/templates/tickets/ticket_list.html:21
|
||||
#: tickets/templates/tickets/ticket_list.html:87
|
||||
msgid "Title"
|
||||
msgstr "标题"
|
||||
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:28
|
||||
msgid "Reject selected"
|
||||
msgstr "拒绝所选"
|
||||
#: tickets/models/ticket.py:36 tickets/models/ticket.py:87
|
||||
msgid "Body"
|
||||
msgstr "内容"
|
||||
|
||||
#: tickets/models/ticket.py:37
|
||||
msgid "Meta"
|
||||
msgstr ""
|
||||
|
||||
#: tickets/models/ticket.py:38 tickets/templates/tickets/ticket_detail.html:51
|
||||
msgid "Assignee"
|
||||
msgstr "处理人"
|
||||
|
||||
#: tickets/models/ticket.py:39
|
||||
msgid "Assignee display name"
|
||||
msgstr "处理人名称"
|
||||
|
||||
#: tickets/models/ticket.py:40 tickets/templates/tickets/ticket_detail.html:50
|
||||
msgid "Assignees"
|
||||
msgstr "待处理人"
|
||||
|
||||
#: tickets/models/ticket.py:41
|
||||
msgid "Assignees display name"
|
||||
msgstr "待处理人名称"
|
||||
|
||||
#: tickets/models/ticket.py:70
|
||||
msgid "{} {} this ticket"
|
||||
msgstr "{} {} 这个工单"
|
||||
|
||||
#: tickets/templates/tickets/ticket_detail.html:66
|
||||
#: tickets/templates/tickets/ticket_detail.html:81
|
||||
msgid "ago"
|
||||
msgstr "前"
|
||||
|
||||
#: tickets/templates/tickets/ticket_list.html:9
|
||||
msgid "My tickets"
|
||||
msgstr "我的工单"
|
||||
|
||||
#: tickets/templates/tickets/ticket_list.html:10
|
||||
msgid "Assigned me"
|
||||
msgstr "待处理"
|
||||
|
||||
#: tickets/utils.py:18
|
||||
msgid "New ticket"
|
||||
msgstr "新工单"
|
||||
|
@ -4708,15 +4681,7 @@ msgid ""
|
|||
" <div>\n"
|
||||
" <p>Your has a new ticket</p>\n"
|
||||
" <div>\n"
|
||||
" <b>Title:</b> {ticket.title}\n"
|
||||
" <br/>\n"
|
||||
" <b>User:</b> {user}\n"
|
||||
" <br/>\n"
|
||||
" <b>Assignees:</b> {ticket.assignees_display}\n"
|
||||
" <br/>\n"
|
||||
" <b>City:</b> {ticket.city}\n"
|
||||
" <br/>\n"
|
||||
" <b>IP:</b> {ticket.ip}\n"
|
||||
" {body}\n"
|
||||
" <br/>\n"
|
||||
" <a href={url}>click here to review</a> \n"
|
||||
" </div>\n"
|
||||
|
@ -4725,28 +4690,20 @@ msgid ""
|
|||
msgstr ""
|
||||
"\n"
|
||||
" <div>\n"
|
||||
" <p>您有一个新工单</p>\n"
|
||||
" <p>你有一个新工单</p>\n"
|
||||
" <div>\n"
|
||||
" <b>标题:</b> {ticket.title}\n"
|
||||
" {body}\n"
|
||||
" <br/>\n"
|
||||
" <b>用户:</b> {user}\n"
|
||||
" <br/>\n"
|
||||
" <b>待处理人:</b> {ticket.assignees_display}\n"
|
||||
" <br/>\n"
|
||||
" <b>城市:</b> {ticket.city}\n"
|
||||
" <br/>\n"
|
||||
" <b>IP:</b> {ticket.ip}\n"
|
||||
" <br/>\n"
|
||||
" <a href={url}>点我查看</a> \n"
|
||||
" <a href={url}>点击我查看</a> \n"
|
||||
" </div>\n"
|
||||
" </div>\n"
|
||||
" "
|
||||
|
||||
#: tickets/utils.py:48
|
||||
#: tickets/utils.py:40
|
||||
msgid "Ticket has been reply"
|
||||
msgstr "工单已被回复"
|
||||
|
||||
#: tickets/utils.py:49
|
||||
#: tickets/utils.py:41
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -4777,13 +4734,13 @@ msgstr ""
|
|||
" </div>\n"
|
||||
" "
|
||||
|
||||
#: tickets/views.py:17
|
||||
msgid "Login confirm ticket list"
|
||||
msgstr "登录复核工单列表"
|
||||
#: tickets/views.py:18
|
||||
msgid "Ticket list"
|
||||
msgstr "工单列表"
|
||||
|
||||
#: tickets/views.py:31
|
||||
msgid "Login confirm ticket detail"
|
||||
msgstr "登录复核工单详情"
|
||||
#: tickets/views.py:33
|
||||
msgid "Ticket detail"
|
||||
msgstr "工单详情"
|
||||
|
||||
#: users/api/user.py:173
|
||||
msgid "Could not reset self otp, use profile reset instead"
|
||||
|
@ -6455,6 +6412,80 @@ msgstr "密码匣子"
|
|||
msgid "vault create"
|
||||
msgstr "创建"
|
||||
|
||||
#~ msgid "Assigned ticket"
|
||||
#~ msgstr "处理人"
|
||||
|
||||
#~ msgid "My ticket"
|
||||
#~ msgstr "我的工单"
|
||||
|
||||
#~ msgid "User login confirm: {}"
|
||||
#~ msgstr "用户登录复核: {}"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "User: {}\n"
|
||||
#~ "IP: {}\n"
|
||||
#~ "City: {}\n"
|
||||
#~ "Date: {}\n"
|
||||
#~ msgstr ""
|
||||
#~ "用户: {}\n"
|
||||
#~ "IP: {}\n"
|
||||
#~ "城市: {}\n"
|
||||
#~ "日期: {}\n"
|
||||
|
||||
#~ msgid "this order"
|
||||
#~ msgstr "这个工单"
|
||||
|
||||
#~ msgid "Approve selected"
|
||||
#~ msgstr "同意所选"
|
||||
|
||||
#~ msgid "Reject selected"
|
||||
#~ msgstr "拒绝所选"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "\n"
|
||||
#~ " <div>\n"
|
||||
#~ " <p>Your has a new ticket</p>\n"
|
||||
#~ " <div>\n"
|
||||
#~ " <b>Title:</b> {ticket.title}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <b>User:</b> {user}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <b>Assignees:</b> {ticket.assignees_display}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <b>City:</b> {ticket.city}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <b>IP:</b> {ticket.ip}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <a href={url}>click here to review</a> \n"
|
||||
#~ " </div>\n"
|
||||
#~ " </div>\n"
|
||||
#~ " "
|
||||
#~ msgstr ""
|
||||
#~ "\n"
|
||||
#~ " <div>\n"
|
||||
#~ " <p>您有一个新工单</p>\n"
|
||||
#~ " <div>\n"
|
||||
#~ " <b>标题:</b> {ticket.title}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <b>用户:</b> {user}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <b>待处理人:</b> {ticket.assignees_display}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <b>城市:</b> {ticket.city}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <b>IP:</b> {ticket.ip}\n"
|
||||
#~ " <br/>\n"
|
||||
#~ " <a href={url}>点我查看</a> \n"
|
||||
#~ " </div>\n"
|
||||
#~ " </div>\n"
|
||||
#~ " "
|
||||
|
||||
#~ msgid "Login confirm ticket list"
|
||||
#~ msgstr "登录复核工单列表"
|
||||
|
||||
#~ msgid "Login confirm ticket detail"
|
||||
#~ msgstr "登录复核工单详情"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Login"
|
||||
#~ msgid "Login IP"
|
||||
|
|
|
@ -123,12 +123,10 @@
|
|||
|
||||
{% if request.user.can_admin_current_org and LOGIN_CONFIRM_ENABLE %}
|
||||
<li id="tickets">
|
||||
<a>
|
||||
<i class="fa fa-check-square-o" style="width: 14px"></i> <span class="nav-label">{% trans 'Tickets' %}</span><span class="fa arrow"></span>
|
||||
<a href="{% url 'tickets:ticket-list' %}">
|
||||
<i class="fa fa-check-square-o" style="width: 14px"></i>
|
||||
<span class="nav-label">{% trans 'Tickets' %}</span>
|
||||
</a>
|
||||
<ul class="nav nav-second-level">
|
||||
<li id="login-confirm-tickets"><a href="{% url 'tickets:login-confirm-ticket-list' %}">{% trans 'Login confirm' %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .base import *
|
||||
from .login_confirm import *
|
||||
from .ticket import *
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.permissions import IsValidUser
|
||||
from common.mixins import CommonApiMixin
|
||||
from .. import serializers, mixins
|
||||
from ..models import LoginConfirmTicket
|
||||
|
||||
|
||||
class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, BulkModelViewSet):
|
||||
serializer_class = serializers.LoginConfirmTicketSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
queryset = LoginConfirmTicket.objects.all()
|
||||
filter_fields = ['status', 'title', 'action', 'ip']
|
||||
search_fields = ['user_display', 'title', 'ip', 'city']
|
|
@ -4,6 +4,7 @@
|
|||
from rest_framework import viewsets
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.permissions import IsValidUser
|
||||
from common.utils import lazyproperty
|
||||
from .. import serializers, models, mixins
|
||||
|
||||
|
@ -11,14 +12,19 @@ from .. import serializers, models, mixins
|
|||
class TicketViewSet(mixins.TicketMixin, viewsets.ModelViewSet):
|
||||
serializer_class = serializers.TicketSerializer
|
||||
queryset = models.Ticket.objects.all()
|
||||
permission_classes = (IsValidUser,)
|
||||
filter_fields = ['status', 'title', 'action']
|
||||
search_fields = ['user_display', 'title']
|
||||
|
||||
|
||||
class TicketCommentViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = serializers.CommentSerializer
|
||||
http_method_names = ['get', 'post']
|
||||
|
||||
def check_permissions(self, request):
|
||||
ticket = self.ticket
|
||||
if request.user == ticket.user or request.user in ticket.assignees.all():
|
||||
if request.user == ticket.user or \
|
||||
request.user in ticket.assignees.all():
|
||||
return True
|
||||
return False
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
# Generated by Django 2.2.5 on 2019-11-07 08:02
|
||||
# Generated by Django 2.2.5 on 2019-11-15 06:57
|
||||
|
||||
import common.fields.model
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
@ -25,10 +26,12 @@ class Migration(migrations.Migration):
|
|||
('user_display', models.CharField(max_length=128, verbose_name='User display name')),
|
||||
('title', models.CharField(max_length=256, verbose_name='Title')),
|
||||
('body', models.TextField(verbose_name='Body')),
|
||||
('meta', common.fields.model.JsonDictTextField(default='{}', verbose_name='Meta')),
|
||||
('assignee_display', models.CharField(blank=True, max_length=128, null=True, verbose_name='Assignee display name')),
|
||||
('assignees_display', models.CharField(blank=True, max_length=128, verbose_name='Assignees display name')),
|
||||
('type', models.CharField(default='general', max_length=16, verbose_name='Type')),
|
||||
('type', models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm')], default='general', max_length=16, verbose_name='Type')),
|
||||
('status', models.CharField(choices=[('open', 'Open'), ('closed', 'Closed')], default='open', max_length=16)),
|
||||
('action', models.CharField(blank=True, choices=[('approve', 'Approve'), ('reject', 'Reject')], default='', max_length=16)),
|
||||
('assignee', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticket_handled', to=settings.AUTH_USER_MODEL, verbose_name='Assignee')),
|
||||
('assignees', models.ManyToManyField(related_name='ticket_assigned', to=settings.AUTH_USER_MODEL, verbose_name='Assignees')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticket_requested', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
|
@ -37,19 +40,6 @@ class Migration(migrations.Migration):
|
|||
'ordering': ('-date_created',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LoginConfirmTicket',
|
||||
fields=[
|
||||
('ticket_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tickets.Ticket')),
|
||||
('ip', models.GenericIPAddressField(blank=True, null=True)),
|
||||
('city', models.CharField(blank=True, default='', max_length=16)),
|
||||
('action', models.CharField(blank=True, choices=[('approve', 'Approve'), ('reject', 'Reject')], default='', max_length=16)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('tickets.ticket',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Comment',
|
||||
fields=[
|
||||
|
@ -59,7 +49,7 @@ class Migration(migrations.Migration):
|
|||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('user_display', models.CharField(max_length=128, verbose_name='User display name')),
|
||||
('body', models.TextField(verbose_name='Body')),
|
||||
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.Ticket')),
|
||||
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='tickets.Ticket')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='comments', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# Generated by Django 2.2.5 on 2019-11-14 03:05
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='comment',
|
||||
name='ticket',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='tickets.Ticket'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ticket',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm')], default='general', max_length=16, verbose_name='Type'),
|
||||
),
|
||||
]
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .base import *
|
||||
from .login_confirm import *
|
||||
from .ticket import *
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .base import Ticket
|
||||
|
||||
__all__ = ['LoginConfirmTicket']
|
||||
|
||||
|
||||
class LoginConfirmTicket(Ticket):
|
||||
ACTION_APPROVE = 'approve'
|
||||
ACTION_REJECT = 'reject'
|
||||
ACTION_CHOICES = (
|
||||
(ACTION_APPROVE, _('Approve')),
|
||||
(ACTION_REJECT, _('Reject')),
|
||||
)
|
||||
ip = models.GenericIPAddressField(blank=True, null=True)
|
||||
city = models.CharField(max_length=16, blank=True, default='')
|
||||
action = models.CharField(choices=ACTION_CHOICES, max_length=16, default='', blank=True)
|
||||
|
||||
def create_action_comment(self, action, user):
|
||||
action_display = dict(self.ACTION_CHOICES).get(action)
|
||||
body = '{} {} {}'.format(user, action_display, _("this order"))
|
||||
self.comments.create(body=body, user=user, user_display=str(user))
|
||||
|
||||
def perform_action(self, action, user):
|
||||
self.create_action_comment(action, user)
|
||||
self.action = action
|
||||
self.status = self.STATUS_CLOSED
|
||||
self.assignee = user
|
||||
self.assignees_display = str(user)
|
||||
self.save()
|
|
@ -5,6 +5,7 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.mixins.models import CommonModelMixin
|
||||
from common.fields.model import JsonDictTextField
|
||||
|
||||
__all__ = ['Ticket', 'Comment']
|
||||
|
||||
|
@ -22,17 +23,25 @@ class Ticket(CommonModelMixin):
|
|||
(TYPE_GENERAL, _("General")),
|
||||
(TYPE_LOGIN_CONFIRM, _("Login confirm"))
|
||||
)
|
||||
ACTION_APPROVE = 'approve'
|
||||
ACTION_REJECT = 'reject'
|
||||
ACTION_CHOICES = (
|
||||
(ACTION_APPROVE, _('Approve')),
|
||||
(ACTION_REJECT, _('Reject')),
|
||||
)
|
||||
user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='%(class)s_requested', verbose_name=_("User"))
|
||||
user_display = models.CharField(max_length=128, verbose_name=_("User display name"))
|
||||
|
||||
title = models.CharField(max_length=256, verbose_name=_("Title"))
|
||||
body = models.TextField(verbose_name=_("Body"))
|
||||
meta = JsonDictTextField(verbose_name=_("Meta"), default='{}')
|
||||
assignee = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='%(class)s_handled', verbose_name=_("Assignee"))
|
||||
assignee_display = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Assignee display name"))
|
||||
assignees = models.ManyToManyField('users.User', related_name='%(class)s_assigned', verbose_name=_("Assignees"))
|
||||
assignees_display = models.CharField(max_length=128, verbose_name=_("Assignees display name"), blank=True)
|
||||
type = models.CharField(max_length=16, choices=TYPE_CHOICES, default=TYPE_GENERAL, verbose_name=_("Type"))
|
||||
status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='open')
|
||||
action = models.CharField(choices=ACTION_CHOICES, max_length=16, default='', blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return '{}: {}'.format(self.user_display, self.title)
|
||||
|
@ -45,6 +54,14 @@ class Ticket(CommonModelMixin):
|
|||
def status_display(self):
|
||||
return self.get_status_display()
|
||||
|
||||
@property
|
||||
def type_display(self):
|
||||
return self.get_type_display()
|
||||
|
||||
@property
|
||||
def action_display(self):
|
||||
return self.get_action_display()
|
||||
|
||||
def create_status_comment(self, status, user):
|
||||
if status == self.STATUS_CLOSED:
|
||||
action = _("Close")
|
||||
|
@ -59,6 +76,19 @@ class Ticket(CommonModelMixin):
|
|||
self.status = status
|
||||
self.save()
|
||||
|
||||
def create_action_comment(self, action, user):
|
||||
action_display = dict(self.ACTION_CHOICES).get(action)
|
||||
body = '{} {} {}'.format(user, action_display, _("this order"))
|
||||
self.comments.create(body=body, user=user, user_display=str(user))
|
||||
|
||||
def perform_action(self, action, user):
|
||||
self.create_action_comment(action, user)
|
||||
self.action = action
|
||||
self.status = self.STATUS_CLOSED
|
||||
self.assignee = user
|
||||
self.assignees_display = str(user)
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
ordering = ('-date_created',)
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .base import *
|
||||
from .login_confirm import *
|
||||
from .ticket import *
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.mixins.serializers import BulkSerializerMixin
|
||||
from .base import TicketSerializer
|
||||
from ..models import LoginConfirmTicket
|
||||
|
||||
|
||||
__all__ = ['LoginConfirmTicketSerializer', 'LoginConfirmTicketActionSerializer']
|
||||
|
||||
|
||||
class LoginConfirmTicketSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
class Meta:
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
model = LoginConfirmTicket
|
||||
fields = TicketSerializer.Meta.fields + [
|
||||
'ip', 'city', 'action'
|
||||
]
|
||||
read_only_fields = TicketSerializer.Meta.read_only_fields
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data.pop('action')
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
action = validated_data.get("action")
|
||||
user = self.context["request"].user
|
||||
|
||||
if action and user not in instance.assignees.all():
|
||||
error = {"action": "Only assignees can update"}
|
||||
raise serializers.ValidationError(error)
|
||||
if instance.status == instance.STATUS_CLOSED:
|
||||
validated_data.pop('action')
|
||||
instance = super().update(instance, validated_data)
|
||||
if not instance.status == instance.STATUS_CLOSED:
|
||||
instance.perform_action(action, user)
|
||||
return instance
|
||||
|
||||
|
||||
class LoginConfirmTicketActionSerializer(serializers.ModelSerializer):
|
||||
comment = serializers.CharField(allow_blank=True)
|
||||
|
||||
class Meta:
|
||||
model = LoginConfirmTicket
|
||||
fields = ['action']
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
|
@ -14,12 +14,31 @@ class TicketSerializer(serializers.ModelSerializer):
|
|||
'id', 'user', 'user_display', 'title', 'body',
|
||||
'assignees', 'assignees_display',
|
||||
'status', 'date_created', 'date_updated',
|
||||
'type_display', 'action_display',
|
||||
]
|
||||
read_only_fields = [
|
||||
'user_display', 'assignees_display',
|
||||
'date_created', 'date_updated',
|
||||
]
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data.pop('action')
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
action = validated_data.get("action")
|
||||
user = self.context["request"].user
|
||||
|
||||
if action and user not in instance.assignees.all():
|
||||
error = {"action": "Only assignees can update"}
|
||||
raise serializers.ValidationError(error)
|
||||
if instance.status == instance.STATUS_CLOSED:
|
||||
validated_data.pop('action')
|
||||
instance = super().update(instance, validated_data)
|
||||
if not instance.status == instance.STATUS_CLOSED and action:
|
||||
instance.perform_action(action, user)
|
||||
return instance
|
||||
|
||||
|
||||
class CurrentTicket(object):
|
||||
ticket = None
|
|
@ -4,24 +4,24 @@ from django.dispatch import receiver
|
|||
from django.db.models.signals import m2m_changed, post_save, pre_save
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import LoginConfirmTicket, Ticket, Comment
|
||||
from .models import Ticket, Comment
|
||||
from .utils import (
|
||||
send_login_confirm_ticket_mail_to_assignees,
|
||||
send_login_confirm_action_mail_to_user
|
||||
send_new_ticket_mail_to_assignees,
|
||||
send_ticket_action_mail_to_user
|
||||
)
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=LoginConfirmTicket.assignees.through)
|
||||
@receiver(m2m_changed, sender=Ticket.assignees.through)
|
||||
def on_login_confirm_ticket_assignees_set(sender, instance=None, action=None,
|
||||
reverse=False, model=None,
|
||||
pk_set=None, **kwargs):
|
||||
if action == 'post_add':
|
||||
logger.debug('New ticket create, send mail: {}'.format(instance.id))
|
||||
assignees = model.objects.filter(pk__in=pk_set)
|
||||
send_login_confirm_ticket_mail_to_assignees(instance, assignees)
|
||||
send_new_ticket_mail_to_assignees(instance, assignees)
|
||||
if action.startswith('post') and not reverse:
|
||||
instance.assignees_display = ', '.join([
|
||||
str(u) for u in instance.assignees.all()
|
||||
|
@ -29,15 +29,15 @@ def on_login_confirm_ticket_assignees_set(sender, instance=None, action=None,
|
|||
instance.save()
|
||||
|
||||
|
||||
@receiver(post_save, sender=LoginConfirmTicket)
|
||||
@receiver(post_save, sender=Ticket)
|
||||
def on_login_confirm_ticket_status_change(sender, instance=None, created=False, **kwargs):
|
||||
if created or instance.status == "open":
|
||||
return
|
||||
logger.debug('Ticket changed, send mail: {}'.format(instance.id))
|
||||
send_login_confirm_action_mail_to_user(instance)
|
||||
send_ticket_action_mail_to_user(instance)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=LoginConfirmTicket)
|
||||
@receiver(pre_save, sender=Ticket)
|
||||
def on_ticket_create(sender, instance=None, **kwargs):
|
||||
instance.user_display = str(instance.user)
|
||||
if instance.assignee:
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
{% extends 'tickets/ticket_detail.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block status %}
|
||||
{% endblock %}
|
||||
|
||||
{% block action %}
|
||||
<a class="btn btn-sm btn-primary btn-update btn-action" data-action="approve"><i class="fa fa-check"></i> {% trans 'Approve' %}</a>
|
||||
<a class="btn btn-sm btn-danger btn-update btn-action" data-action="reject"><i class="fa fa-times"></i> {% trans 'Reject' %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
{{ block.super }}
|
||||
<script>
|
||||
var ticketDetailUrl = "{% url 'api-tickets:login-confirm-ticket-detail' pk=object.id %}";
|
||||
$(document).ready(function () {
|
||||
}).on('click', '.btn-action', function () {
|
||||
createComment(function () {
|
||||
});
|
||||
var action = $(this).data('action');
|
||||
var data = {
|
||||
url: ticketDetailUrl,
|
||||
body: JSON.stringify({action: action}),
|
||||
method: "PATCH",
|
||||
success: reloadPage
|
||||
};
|
||||
requestApi(data);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
{% block custom_head_css_js %}
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<table class="table table-striped table-bordered table-hover " id="login_confirm_ticket_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input id="" type="checkbox" class="ipt_check_all">
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Title' %}</th>
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'Status' %}</th>
|
||||
<th class="text-center">{% trans 'Datetime' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="approve">{% trans 'Approve selected' %}</option>
|
||||
<option value="reject">{% trans 'Reject selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include '_filter_dropdown.html' %}
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var ticketTable = 0;
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#login_confirm_ticket_list_table'),
|
||||
oSearch: {sSearch: "status:open"},
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
var detailBtn = '<a href="{% url "tickets:login-confirm-ticket-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
if (cellData === "approve") {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else if (cellData === "reject") {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else if (cellData === "open") {
|
||||
$(td).html('<i class="fa fa-spinner text-info"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-info"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var d = toSafeLocalDateStr(cellData);
|
||||
$(td).html(d)
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var acceptBtn = '<a class="btn btn-xs btn-info btn-action" data-action="approve" data-uid="{{ DEFAULT_PK }}" >{% trans "Approve" %}</a> ';
|
||||
var rejectBtn = '<a class="btn btn-xs btn-danger btn-action" data-action="reject" data-uid="{{ DEFAULT_PK }}" >{% trans "Reject" %}</a>';
|
||||
acceptBtn = acceptBtn.replace('{{ DEFAULT_PK }}', cellData);
|
||||
rejectBtn = rejectBtn.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var acceptBtnRef = $(acceptBtn);
|
||||
var rejectBtnRef = $(rejectBtn);
|
||||
if (rowData.action !== "") {
|
||||
acceptBtnRef.attr('disabled', 'disabled');
|
||||
rejectBtnRef.attr('disabled', 'disabled');
|
||||
}
|
||||
var btn = acceptBtnRef.prop('outerHTML') + ' ' + rejectBtnRef.prop('outerHTML');
|
||||
$(td).html(btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-tickets:login-confirm-ticket-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "title"},
|
||||
{data: "user_display"},
|
||||
{data: "action", width: "40px"},
|
||||
{data: "date_created", width: "120px"},
|
||||
{data: "id", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
ticketTable = jumpserver.initServerSideDataTable(options);
|
||||
return ticketTable
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
var menu = [
|
||||
{title: "IP", value: "ip"},
|
||||
{title: "{% trans 'Title' %}", value: "title"},
|
||||
{title: "{% trans 'User' %}", value: "user_display"},
|
||||
{title: "{% trans 'Status' %}", value: "status", submenu: [
|
||||
{title: "{% trans 'Open' %}", value: "open"},
|
||||
{title: "{% trans 'Closed' %}", value: "closed"},
|
||||
]},
|
||||
{title: "{% trans 'Action' %}", value: "action", submenu: [
|
||||
{title: "{% trans 'Approve' %}", value: "approve"},
|
||||
{title: "{% trans 'Reject' %}", value: "reject"},
|
||||
]},
|
||||
];
|
||||
initTableFilterDropdown('#login_confirm_ticket_list_table_filter input', menu)
|
||||
}).on('click', '.btn-action', function () {
|
||||
var ticketId = $(this).data("uid");
|
||||
var action = $(this).data('action');
|
||||
var ticketDetailUrl = "{% url 'api-tickets:login-confirm-ticket-detail' pk=DEFAULT_PK %}";
|
||||
ticketDetailUrl = ticketDetailUrl.replace("{{ DEFAULT_PK }}", ticketId);
|
||||
var data = {
|
||||
url: ticketDetailUrl,
|
||||
body: JSON.stringify({action: action}),
|
||||
method: "PATCH",
|
||||
success: reloadPage
|
||||
};
|
||||
requestApi(data);
|
||||
}).on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var idList = ticketTable.selected;
|
||||
if (idList.length === 0) {
|
||||
return false;
|
||||
}
|
||||
var theUrl = "{% url 'api-tickets:login-confirm-ticket-list' %}";
|
||||
|
||||
function doAction(action) {
|
||||
var data = [];
|
||||
$.each(idList, function(index, object_id) {
|
||||
var obj = {
|
||||
"pk": object_id, "action": action
|
||||
};
|
||||
data.push(obj);
|
||||
});
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
success: function (){
|
||||
$(".ipt_check_all").prop("checked", false)
|
||||
ticketTable.ajax.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
doAction(action)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -72,7 +72,6 @@
|
|||
</div>
|
||||
</div>
|
||||
{% for comment in object.comments.all %}
|
||||
|
||||
<div class="feed-element">
|
||||
<a href="#" class="pull-left">
|
||||
<img alt="image" class="img-circle" src="{% static 'img/avatar/user.png'%}" >
|
||||
|
@ -97,14 +96,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
{% block action %}
|
||||
{% endblock %}
|
||||
{% block status %}
|
||||
<a class="btn btn-sm btn-danger btn-update btn-status" data-uid="close"><i class="fa fa-times"></i> {% trans 'Close' %}</a>
|
||||
{% endblock %}
|
||||
{% block comment %}
|
||||
{% if object.type == object.TYPE_LOGIN_CONFIRM %}
|
||||
<a class="btn btn-sm btn-primary btn-update btn-action" data-action="approve"><i class="fa fa-check"></i> {% trans 'Approve' %}</a>
|
||||
<a class="btn btn-sm btn-warning btn-update btn-action" data-action="reject"><i class="fa fa-ban"></i> {% trans 'Reject' %}</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-danger btn-update btn-status" data-uid="closed"><i class="fa fa-times"></i> {% trans 'Close' %}</a>
|
||||
<a class="btn btn-sm btn-info btn-update btn-comment" data-uid="comment"><i class="fa fa-pencil"></i> {% trans 'Comment' %}</a>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -127,6 +124,7 @@ var ticketId = "{{ object.id }}";
|
|||
var status = "{{ object.status }}";
|
||||
|
||||
var commentUrl = "{% url 'api-tickets:ticket-comment-list' ticket_id=object.id %}";
|
||||
var ticketDetailUrl = "{% url 'api-tickets:ticket-detail' pk=object.id %}";
|
||||
|
||||
function createComment(successCallback) {
|
||||
var commentText = $("#comment").val();
|
||||
|
@ -158,5 +156,26 @@ $(document).ready(function () {
|
|||
.on('click', '.btn-comment', function () {
|
||||
createComment();
|
||||
})
|
||||
.on('click', '.btn-action', function () {
|
||||
createComment(function () {});
|
||||
var action = $(this).data('action');
|
||||
var data = {
|
||||
url: ticketDetailUrl,
|
||||
body: JSON.stringify({action: action}),
|
||||
method: "PATCH",
|
||||
success: reloadPage
|
||||
};
|
||||
requestApi(data);
|
||||
})
|
||||
.on('click', '.btn-status', function () {
|
||||
var status = $(this).data('uid');
|
||||
var data = {
|
||||
url: ticketDetailUrl,
|
||||
body: JSON.stringify({status: status}),
|
||||
method: "PATCH",
|
||||
success: reloadPage
|
||||
};
|
||||
requestApi(data);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeIn">
|
||||
<div class="col-lg-12">
|
||||
<div class="tabs-container">
|
||||
<ul class="nav nav-tabs">
|
||||
<li {% if not assign %} class="active" {% endif %}><a href="{% url 'tickets:ticket-list' %}"> {% trans 'My tickets' %}</a></li>
|
||||
<li {% if assign %}class="active" {% endif %}><a href="{% url 'tickets:ticket-list' %}?assign=1">{% trans 'Assigned me' %}</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div id="my-tickets" class="tab-pane active">
|
||||
<div class="panel-body">
|
||||
<table class="table table-striped table-bordered table-hover" id="ticket-list-table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input id="" type="checkbox" class="ipt_check_all">
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Title' %}</th>
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Status' %}</th>
|
||||
<th class="text-center">{% trans 'Datetime' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include '_filter_dropdown.html' %}
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var assignedTable, myTable, listUrl;
|
||||
{% if assign %}
|
||||
listUrl = '{% url "api-tickets:ticket-list" %}?assign=1';
|
||||
{% else %}
|
||||
listUrl = '{% url "api-tickets:ticket-list" %}?assign=0';
|
||||
{% endif %}
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#ticket-list-table'),
|
||||
oSearch: {sSearch: "status:open"},
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
var detailBtn = '<a href="{% url "tickets:ticket-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (cellData === "open") {
|
||||
$(td).html('<i class="fa fa-check-circle-o text-navy"></i>');
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-times-circle-o text-danger"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var d = toSafeLocalDateStr(cellData);
|
||||
$(td).html(d)
|
||||
}},
|
||||
],
|
||||
ajax_url: listUrl,
|
||||
columns: [
|
||||
{data: "id"}, {data: "title"},
|
||||
{data: "user_display"}, {data: "type_display"},
|
||||
{data: "status", width: "40px"},
|
||||
{data: "date_created"},
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
myTable = jumpserver.initServerSideDataTable(options);
|
||||
return myTable
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
var menu = [
|
||||
{title: "IP", value: "ip"},
|
||||
{title: "{% trans 'Title' %}", value: "title"},
|
||||
{title: "{% trans 'User' %}", value: "user_display"},
|
||||
{title: "{% trans 'Status' %}", value: "status", submenu: [
|
||||
{title: "{% trans 'Open' %}", value: "open"},
|
||||
{title: "{% trans 'Closed' %}", value: "closed"},
|
||||
]},
|
||||
{title: "{% trans 'Action' %}", value: "action", submenu: [
|
||||
{title: "{% trans 'Approve' %}", value: "approve"},
|
||||
{title: "{% trans 'Reject' %}", value: "reject"},
|
||||
]},
|
||||
];
|
||||
initTableFilterDropdown('#assigned-ticket-list-table input', menu)
|
||||
}).on('click', '.btn-action', function () {
|
||||
var ticketId = $(this).data("uid");
|
||||
var action = $(this).data('action');
|
||||
var ticketDetailUrl = "{% url 'api-tickets:ticket-detail' pk=DEFAULT_PK %}";
|
||||
ticketDetailUrl = ticketDetailUrl.replace("{{ DEFAULT_PK }}", ticketId);
|
||||
var data = {
|
||||
url: ticketDetailUrl,
|
||||
body: JSON.stringify({action: action}),
|
||||
method: "PATCH",
|
||||
success: reloadPage
|
||||
};
|
||||
requestApi(data);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -9,7 +9,6 @@ router = BulkRouter()
|
|||
|
||||
router.register('tickets', api.TicketViewSet, 'ticket')
|
||||
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
|
||||
router.register('login-confirm-tickets', api.LoginConfirmTicketViewSet, 'login-confirm-ticket')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -6,6 +6,6 @@ from .. import views
|
|||
app_name = 'tickets'
|
||||
|
||||
urlpatterns = [
|
||||
path('login-confirm-tickets/', views.LoginConfirmTicketListView.as_view(), name='login-confirm-ticket-list'),
|
||||
path('login-confirm-tickets/<uuid:pk>/', views.LoginConfirmTicketDetailView.as_view(), name='login-confirm-ticket-detail')
|
||||
path('tickets/', views.TicketListView.as_view(), name='ticket-list'),
|
||||
path('tickets/<uuid:pk>/', views.TicketDetailView.as_view(), name='ticket-detail'),
|
||||
]
|
||||
|
|
|
@ -9,37 +9,29 @@ from common.tasks import send_mail_async
|
|||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def send_login_confirm_ticket_mail_to_assignees(ticket, assignees):
|
||||
def send_new_ticket_mail_to_assignees(ticket, assignees):
|
||||
recipient_list = [user.email for user in assignees]
|
||||
user = ticket.user
|
||||
if not recipient_list:
|
||||
logger.error("Ticket not has assignees: {}".format(ticket.id))
|
||||
return
|
||||
subject = '{}: {}'.format(_("New ticket"), ticket.title)
|
||||
detail_url = reverse('tickets:login-confirm-ticket-detail',
|
||||
detail_url = reverse('tickets:ticket-detail',
|
||||
kwargs={'pk': ticket.id}, external=True)
|
||||
message = _("""
|
||||
<div>
|
||||
<p>Your has a new ticket</p>
|
||||
<div>
|
||||
<b>Title:</b> {ticket.title}
|
||||
<br/>
|
||||
<b>User:</b> {user}
|
||||
<br/>
|
||||
<b>Assignees:</b> {ticket.assignees_display}
|
||||
<br/>
|
||||
<b>City:</b> {ticket.city}
|
||||
<br/>
|
||||
<b>IP:</b> {ticket.ip}
|
||||
{body}
|
||||
<br/>
|
||||
<a href={url}>click here to review</a>
|
||||
</div>
|
||||
</div>
|
||||
""").format(ticket=ticket, user=user, url=detail_url)
|
||||
""").format(body=ticket.body, user=user, url=detail_url)
|
||||
send_mail_async.delay(subject, message, recipient_list, html_message=message)
|
||||
|
||||
|
||||
def send_login_confirm_action_mail_to_user(ticket):
|
||||
def send_ticket_action_mail_to_user(ticket):
|
||||
if not ticket.user:
|
||||
logger.error("Ticket not has user: {}".format(ticket.id))
|
||||
return
|
||||
|
|
|
@ -2,32 +2,34 @@ from django.views.generic import TemplateView, DetailView
|
|||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.permissions import PermissionsMixin, IsValidUser
|
||||
from .models import LoginConfirmTicket
|
||||
from .models import Ticket
|
||||
from . import mixins
|
||||
|
||||
|
||||
class LoginConfirmTicketListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'tickets/login_confirm_ticket_list.html'
|
||||
class TicketListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'tickets/ticket_list.html'
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assign = self.request.GET.get('assign', '0') == '1'
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _("Tickets"),
|
||||
'action': _("Ticket list"),
|
||||
'assign': assign,
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class TicketDetailView(PermissionsMixin, mixins.TicketMixin, DetailView):
|
||||
template_name = 'tickets/ticket_detail.html'
|
||||
permission_classes = (IsValidUser,)
|
||||
queryset = Ticket.objects.all()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _("Tickets"),
|
||||
'action': _("Login confirm ticket list")
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class LoginConfirmTicketDetailView(PermissionsMixin, mixins.TicketMixin, DetailView):
|
||||
template_name = 'tickets/login_confirm_ticket_detail.html'
|
||||
queryset = LoginConfirmTicket.objects.all()
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _("Tickets"),
|
||||
'action': _("Login confirm ticket detail")
|
||||
'action': _("Ticket detail")
|
||||
})
|
||||
return context
|
||||
|
|
Loading…
Reference in New Issue