Commit 9e487483 authored by ibuler's avatar ibuler

[Update] 修改tickets

parent bd323d60
...@@ -10,7 +10,7 @@ from common.utils import get_logger, get_object_or_none ...@@ -10,7 +10,7 @@ from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin from common.permissions import IsOrgAdmin
from ..models import LoginConfirmSetting from ..models import LoginConfirmSetting
from ..serializers import LoginConfirmSettingSerializer from ..serializers import LoginConfirmSettingSerializer
from .. import errors from .. import errors, mixins
__all__ = ['LoginConfirmSettingUpdateApi', 'LoginConfirmTicketStatusApi'] __all__ = ['LoginConfirmSettingUpdateApi', 'LoginConfirmTicketStatusApi']
logger = get_logger(__name__) logger = get_logger(__name__)
...@@ -31,7 +31,7 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView): ...@@ -31,7 +31,7 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
return s return s
class LoginConfirmTicketStatusApi(APIView): class LoginConfirmTicketStatusApi(mixins.AuthMixin, APIView):
permission_classes = () permission_classes = ()
def get_ticket(self): def get_ticket(self):
...@@ -45,24 +45,9 @@ class LoginConfirmTicketStatusApi(APIView): ...@@ -45,24 +45,9 @@ class LoginConfirmTicketStatusApi(APIView):
return ticket return ticket
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
ticket_id = self.request.session.get("auth_ticket_id")
ticket = self.get_ticket()
try: try:
if not ticket: self.check_user_login_confirm()
raise errors.LoginConfirmOtherError(ticket_id, _("not found")) return Response({"msg": "ok"})
if ticket.status == 'open':
raise errors.LoginConfirmWaitError(ticket_id)
elif ticket.action == ticket.ACTION_APPROVE:
self.request.session["auth_confirm"] = "1"
return Response({"msg": "ok"})
elif ticket.action == ticket.ACTION_REJECT:
raise errors.LoginConfirmOtherError(
ticket_id, ticket.get_action_display()
)
else:
raise errors.LoginConfirmOtherError(
ticket_id, ticket.get_status_display()
)
except errors.NeedMoreInfoError as e: except errors.NeedMoreInfoError as e:
return Response(e.as_data(), status=200) return Response(e.as_data(), status=200)
......
...@@ -28,7 +28,7 @@ class TokenCreateApi(AuthMixin, CreateAPIView): ...@@ -28,7 +28,7 @@ class TokenCreateApi(AuthMixin, CreateAPIView):
self.create_session_if_need() self.create_session_if_need()
# 如果认证没有过,检查账号密码 # 如果认证没有过,检查账号密码
try: try:
user = self.check_user_auth() user = self.check_user_auth_if_need()
self.check_user_mfa_if_need(user) self.check_user_mfa_if_need(user)
self.check_user_login_confirm_if_need(user) self.check_user_login_confirm_if_need(user)
self.send_auth_signal(success=True, user=user) self.send_auth_signal(success=True, user=user)
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import time import time
from django.conf import settings
from common.utils import get_object_or_none, get_request_ip, get_logger from common.utils import get_object_or_none, get_request_ip, get_logger
from users.models import User from users.models import User
...@@ -49,8 +50,8 @@ class AuthMixin: ...@@ -49,8 +50,8 @@ class AuthMixin:
raise errors.BlockLoginError(username=username, ip=ip) raise errors.BlockLoginError(username=username, ip=ip)
def check_user_auth(self): def check_user_auth(self):
request = self.request
self.check_is_block() self.check_is_block()
request = self.request
if hasattr(request, 'data'): if hasattr(request, 'data'):
username = request.data.get('username', '') username = request.data.get('username', '')
password = request.data.get('password', '') password = request.data.get('password', '')
...@@ -73,11 +74,20 @@ class AuthMixin: ...@@ -73,11 +74,20 @@ class AuthMixin:
request.session['user_id'] = str(user.id) request.session['user_id'] = str(user.id)
return user return user
def check_user_auth_if_need(self):
request = self.request
if request.session.get('auth_password') and \
request.session.get('user_id'):
user = self.get_user_from_session()
if user:
return user
return self.check_user_auth()
def check_user_mfa_if_need(self, user): def check_user_mfa_if_need(self, user):
if self.request.session.get('auth_mfa'): if self.request.session.get('auth_mfa'):
return True return
if not user.otp_enabled or not user.otp_secret_key: if not user.otp_enabled or not user.otp_secret_key:
return True return
raise errors.MFARequiredError() raise errors.MFARequiredError()
def check_user_mfa(self, code): def check_user_mfa(self, code):
...@@ -90,28 +100,53 @@ class AuthMixin: ...@@ -90,28 +100,53 @@ class AuthMixin:
return return
raise errors.MFAFailedError(username=user.username, request=self.request) raise errors.MFAFailedError(username=user.username, request=self.request)
def check_user_login_confirm_if_need(self, user): def get_ticket(self):
from tickets.models import LoginConfirmTicket from tickets.models import LoginConfirmTicket
confirm_setting = user.get_login_confirm_setting() ticket_id = self.request.session.get("auth_ticket_id")
if self.request.session.get('auth_confirm') or not confirm_setting: logger.debug('Login confirm ticket id: {}'.format(ticket_id))
return if not ticket_id:
ticket = None ticket = None
if self.request.session.get('auth_ticket_id'): else:
ticket_id = self.request.session['auth_ticket_id']
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id) ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
return ticket
def get_ticket_or_create(self, confirm_setting):
ticket = self.get_ticket()
if not ticket: if not ticket:
ticket = confirm_setting.create_confirm_ticket(self.request) ticket = confirm_setting.create_confirm_ticket(self.request)
self.request.session['auth_ticket_id'] = str(ticket.id) self.request.session['auth_ticket_id'] = str(ticket.id)
return ticket
if ticket.status == "accepted": def check_user_login_confirm(self):
ticket = self.get_ticket()
if not ticket:
raise errors.LoginConfirmOtherError('', "Not found")
if ticket.status == ticket.STATUS_OPEN:
raise errors.LoginConfirmWaitError(ticket.id)
elif ticket.action == ticket.ACTION_APPROVE:
self.request.session["auth_confirm"] = "1"
return return
elif ticket.status == "rejected": elif ticket.action == ticket.ACTION_REJECT:
raise errors.LoginConfirmOtherError(ticket.id) raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_action_display()
)
else: else:
raise errors.LoginConfirmWaitError(ticket.id) raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_status_display()
)
def check_user_login_confirm_if_need(self, user):
if not settings.CONFIG.LOGIN_CONFIRM_ENABLE:
return
confirm_setting = user.get_login_confirm_setting()
if self.request.session.get('auth_confirm') or not confirm_setting:
return
self.get_ticket_or_create(confirm_setting)
self.check_user_login_confirm()
def clear_auth_mark(self): def clear_auth_mark(self):
self.request.session['auth_password'] = '' self.request.session['auth_password'] = ''
self.request.session['auth_user_id'] = ''
self.request.session['auth_mfa'] = '' self.request.session['auth_mfa'] = ''
self.request.session['auth_confirm'] = '' self.request.session['auth_confirm'] = ''
self.request.session['auth_ticket_id'] = '' self.request.session['auth_ticket_id'] = ''
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
</a> </a>
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<a class="btn btn-primary btn-sm block btn-copy" data-link="{{ order_detail_url }}"> <a class="btn btn-primary btn-sm block btn-copy" data-link="{{ ticket_detail_url }}">
<i class="fa fa-clipboard"></i> {% trans 'Copy link' %} <i class="fa fa-clipboard"></i> {% trans 'Copy link' %}
</a> </a>
</div> </div>
...@@ -132,7 +132,11 @@ $(document).ready(function () { ...@@ -132,7 +132,11 @@ $(document).ready(function () {
checkInterval = setInterval(doRequestAuth, 5000); checkInterval = setInterval(doRequestAuth, 5000);
doRequestAuth(); doRequestAuth();
initClipboard(); initClipboard();
window.onbeforeunload = function (e) {
return "{% trans "Confirm" %}";
};
}).on('click', '.btn-refresh', function () { }).on('click', '.btn-refresh', function () {
window.onbeforeunload = function() {};
window.location.reload(); window.location.reload();
}) })
......
...@@ -19,9 +19,7 @@ from django.conf import settings ...@@ -19,9 +19,7 @@ from django.conf import settings
from django.urls import reverse_lazy from django.urls import reverse_lazy
from common.utils import get_request_ip, get_object_or_none from common.utils import get_request_ip, get_object_or_none
from users.models import User
from users.utils import ( from users.utils import (
get_user_or_tmp_user, increase_login_failed_count,
redirect_user_first_login_or_index redirect_user_first_login_or_index
) )
from ..signals import post_auth_success, post_auth_failed from ..signals import post_auth_success, post_auth_failed
...@@ -117,42 +115,28 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView): ...@@ -117,42 +115,28 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
return url return url
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
if not self.request.session.get('auth_password'): try:
user = self.check_user_auth_if_need()
self.check_user_mfa_if_need(user)
self.check_user_login_confirm_if_need(user)
except errors.CredentialError:
return self.format_redirect_url(self.login_url) return self.format_redirect_url(self.login_url)
user = self.get_user_from_session() except errors.MFARequiredError:
# 启用并设置了otp
if user.otp_enabled and user.otp_secret_key and \
not self.request.session.get('auth_mfa'):
return self.format_redirect_url(self.login_otp_url) return self.format_redirect_url(self.login_otp_url)
confirm_setting = user.get_login_confirm_setting() except errors.LoginConfirmBaseError:
if confirm_setting and not self.request.session.get('auth_confirm'): return self.format_redirect_url(self.login_confirm_url)
ticket = confirm_setting.create_confirm_ticket(self.request)
self.request.session['auth_ticket_id'] = str(ticket.id)
url = self.format_redirect_url(self.login_confirm_url)
return url
self.login_success(user)
self.clear_auth_mark()
# 启用但是没有设置otp
if user.otp_enabled and not user.otp_secret_key:
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
url = redirect_user_first_login_or_index(
self.request, self.redirect_field_name
)
return url
def login_success(self, user):
auth_login(self.request, user)
self.send_auth_signal(success=True, user=user)
def send_auth_signal(self, success=True, user=None, username='', reason=''):
if success:
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
else: else:
post_auth_failed.send( auth_login(self.request, user)
sender=self.__class__, username=username, self.send_auth_signal(success=True, user=user)
request=self.request, reason=reason self.clear_auth_mark()
# 启用但是没有设置otp
if user.otp_enabled and not user.otp_secret_key:
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
url = redirect_user_first_login_or_index(
self.request, self.redirect_field_name
) )
return url
class UserLoginWaitConfirmView(TemplateView): class UserLoginWaitConfirmView(TemplateView):
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-08 15:42+0800\n" "POT-Creation-Date: 2019-11-08 17:27+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
...@@ -354,6 +354,7 @@ msgstr "重置" ...@@ -354,6 +354,7 @@ msgstr "重置"
#: terminal/templates/terminal/command_list.html:47 #: terminal/templates/terminal/command_list.html:47
#: terminal/templates/terminal/session_list.html:52 #: terminal/templates/terminal/session_list.html:52
#: terminal/templates/terminal/terminal_update.html:46 #: 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/_user.html:52
#: users/templates/users/forgot_password.html:42 #: users/templates/users/forgot_password.html:42
#: users/templates/users/user_bulk_update.html:24 #: users/templates/users/user_bulk_update.html:24
...@@ -528,7 +529,7 @@ msgstr "创建远程应用" ...@@ -528,7 +529,7 @@ msgstr "创建远程应用"
#: terminal/templates/terminal/session_list.html:36 #: terminal/templates/terminal/session_list.html:36
#: terminal/templates/terminal/terminal_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:18
#: tickets/templates/tickets/login_confirm_ticket_list.html:92 #: tickets/templates/tickets/login_confirm_ticket_list.html:105
#: users/templates/users/_granted_assets.html:34 #: users/templates/users/_granted_assets.html:34
#: users/templates/users/user_group_list.html:38 #: users/templates/users/user_group_list.html:38
#: users/templates/users/user_list.html:41 #: users/templates/users/user_list.html:41
...@@ -1480,7 +1481,7 @@ msgid "Asset user auth" ...@@ -1480,7 +1481,7 @@ msgid "Asset user auth"
msgstr "资产用户信息" msgstr "资产用户信息"
#: assets/templates/assets/_asset_user_auth_view_modal.html:54 #: assets/templates/assets/_asset_user_auth_view_modal.html:54
#: authentication/templates/authentication/login_wait_confirm.html:114 #: authentication/templates/authentication/login_wait_confirm.html:115
msgid "Copy success" msgid "Copy success"
msgstr "复制成功" msgstr "复制成功"
...@@ -1666,6 +1667,7 @@ msgstr "选择节点" ...@@ -1666,6 +1667,7 @@ msgstr "选择节点"
#: assets/templates/assets/system_user_detail.html:182 #: assets/templates/assets/system_user_detail.html:182
#: assets/templates/assets/system_user_list.html:135 #: assets/templates/assets/system_user_list.html:135
#: authentication/templates/authentication/_mfa_confirm_modal.html:20 #: authentication/templates/authentication/_mfa_confirm_modal.html:20
#: authentication/templates/authentication/login_wait_confirm.html:136
#: settings/templates/settings/terminal_setting.html:168 #: settings/templates/settings/terminal_setting.html:168
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112 #: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
#: users/templates/users/user_detail.html:271 #: users/templates/users/user_detail.html:271
...@@ -2294,7 +2296,7 @@ msgstr "原因" ...@@ -2294,7 +2296,7 @@ msgstr "原因"
#: audits/models.py:88 audits/templates/audits/login_log_list.html:64 #: 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:16
#: tickets/templates/tickets/login_confirm_ticket_list.html:88 #: tickets/templates/tickets/login_confirm_ticket_list.html:101
#: tickets/templates/tickets/ticket_detail.html:34 #: tickets/templates/tickets/ticket_detail.html:34
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310 #: 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_history.html:70
...@@ -2390,10 +2392,6 @@ msgstr "登录日志" ...@@ -2390,10 +2392,6 @@ msgstr "登录日志"
msgid "Command execution log" msgid "Command execution log"
msgstr "命令执行" msgstr "命令执行"
#: authentication/api/login_confirm.py:52
msgid "not found"
msgstr "没有发现"
#: authentication/backends/api.py:53 #: authentication/backends/api.py:53
msgid "Invalid signature header. No credentials provided." msgid "Invalid signature header. No credentials provided."
msgstr "" msgstr ""
...@@ -2703,11 +2701,11 @@ msgstr "返回" ...@@ -2703,11 +2701,11 @@ msgstr "返回"
msgid "Welcome back, please enter username and password to login" msgid "Welcome back, please enter username and password to login"
msgstr "欢迎回来,请输入用户名和密码登录" msgstr "欢迎回来,请输入用户名和密码登录"
#: authentication/views/login.py:73 #: authentication/views/login.py:71
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:172 #: authentication/views/login.py:170
msgid "" msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n" "Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page" " Don't close this page"
...@@ -2715,15 +2713,15 @@ msgstr "" ...@@ -2715,15 +2713,15 @@ msgstr ""
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n" "等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
" 不要关闭本页面" " 不要关闭本页面"
#: authentication/views/login.py:177 #: authentication/views/login.py:175
msgid "No ticket found" msgid "No ticket found"
msgstr "没有发现工单" msgstr "没有发现工单"
#: authentication/views/login.py:200 #: authentication/views/login.py:198
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: authentication/views/login.py:201 #: authentication/views/login.py:199
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
...@@ -4571,8 +4569,8 @@ msgstr "接受" ...@@ -4571,8 +4569,8 @@ msgstr "接受"
#: terminal/templates/terminal/terminal_list.html:80 #: terminal/templates/terminal/terminal_list.html:80
#: tickets/models/login_confirm.py:16 #: tickets/models/login_confirm.py:16
#: tickets/templates/tickets/login_confirm_ticket_detail.html:10 #: tickets/templates/tickets/login_confirm_ticket_detail.html:10
#: tickets/templates/tickets/login_confirm_ticket_list.html:57 #: tickets/templates/tickets/login_confirm_ticket_list.html:70
#: tickets/templates/tickets/login_confirm_ticket_list.html:94 #: tickets/templates/tickets/login_confirm_ticket_list.html:107
msgid "Reject" msgid "Reject"
msgstr "拒绝" msgstr "拒绝"
...@@ -4610,12 +4608,12 @@ msgid "" ...@@ -4610,12 +4608,12 @@ msgid ""
msgstr "你可以使用ssh客户端工具连接终端" msgstr "你可以使用ssh客户端工具连接终端"
#: tickets/models/base.py:16 tickets/models/base.py:52 #: tickets/models/base.py:16 tickets/models/base.py:52
#: tickets/templates/tickets/login_confirm_ticket_list.html:89 #: tickets/templates/tickets/login_confirm_ticket_list.html:102
msgid "Open" msgid "Open"
msgstr "" msgstr "开启"
#: tickets/models/base.py:17 #: tickets/models/base.py:17
#: tickets/templates/tickets/login_confirm_ticket_list.html:90 #: tickets/templates/tickets/login_confirm_ticket_list.html:103
msgid "Closed" msgid "Closed"
msgstr "关闭" msgstr "关闭"
...@@ -4629,7 +4627,7 @@ msgstr "用户显示名称" ...@@ -4629,7 +4627,7 @@ msgstr "用户显示名称"
#: tickets/models/base.py:28 #: tickets/models/base.py:28
#: tickets/templates/tickets/login_confirm_ticket_list.html:14 #: tickets/templates/tickets/login_confirm_ticket_list.html:14
#: tickets/templates/tickets/login_confirm_ticket_list.html:87 #: tickets/templates/tickets/login_confirm_ticket_list.html:100
msgid "Title" msgid "Title"
msgstr "标题" msgstr "标题"
...@@ -4659,8 +4657,8 @@ msgstr "{} {} 这个工单" ...@@ -4659,8 +4657,8 @@ msgstr "{} {} 这个工单"
#: tickets/models/login_confirm.py:15 #: tickets/models/login_confirm.py:15
#: tickets/templates/tickets/login_confirm_ticket_detail.html:9 #: tickets/templates/tickets/login_confirm_ticket_detail.html:9
#: tickets/templates/tickets/login_confirm_ticket_list.html:56 #: tickets/templates/tickets/login_confirm_ticket_list.html:69
#: tickets/templates/tickets/login_confirm_ticket_list.html:93 #: tickets/templates/tickets/login_confirm_ticket_list.html:106
msgid "Approve" msgid "Approve"
msgstr "同意" msgstr "同意"
...@@ -4668,6 +4666,14 @@ msgstr "同意" ...@@ -4668,6 +4666,14 @@ msgstr "同意"
msgid "this order" msgid "this order"
msgstr "这个工单" msgstr "这个工单"
#: tickets/templates/tickets/login_confirm_ticket_list.html:27
msgid "Approve selected"
msgstr "同意所选"
#: tickets/templates/tickets/login_confirm_ticket_list.html:28
msgid "Reject selected"
msgstr "拒绝所选"
#: tickets/templates/tickets/ticket_detail.html:66 #: tickets/templates/tickets/ticket_detail.html:66
#: tickets/templates/tickets/ticket_detail.html:81 #: tickets/templates/tickets/ticket_detail.html:81
msgid "ago" msgid "ago"
...@@ -6431,6 +6437,12 @@ msgstr "密码匣子" ...@@ -6431,6 +6437,12 @@ msgstr "密码匣子"
msgid "vault create" msgid "vault create"
msgstr "创建" msgstr "创建"
#~ msgid "selected"
#~ msgstr "所选"
#~ msgid "not found"
#~ msgstr "没有发现"
#~ msgid "Log in frequently and try again later" #~ msgid "Log in frequently and try again later"
#~ msgstr "登录频繁, 稍后重试" #~ msgstr "登录频繁, 稍后重试"
...@@ -6446,9 +6458,6 @@ msgstr "创建" ...@@ -6446,9 +6458,6 @@ msgstr "创建"
#~ msgid "Accepted" #~ msgid "Accepted"
#~ msgstr "已接受" #~ msgstr "已接受"
#~ msgid "Rejected"
#~ msgstr "已拒绝"
#~ msgid "New order" #~ msgid "New order"
#~ msgstr "新工单" #~ msgstr "新工单"
......
...@@ -1319,5 +1319,5 @@ function initDateRangePicker(selector, options) { ...@@ -1319,5 +1319,5 @@ function initDateRangePicker(selector, options) {
} }
function reloadPage() { function reloadPage() {
window.location.reload(); setTimeout( function () {window.location.reload();}, 300);
} }
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import viewsets, generics from rest_framework_bulk import BulkModelViewSet
from rest_framework.serializers import ValidationError
from django.shortcuts import get_object_or_404
from common.permissions import IsValidUser from common.permissions import IsValidUser
from common.mixins import CommonApiMixin from common.mixins import CommonApiMixin
...@@ -10,21 +8,9 @@ from .. import serializers, mixins ...@@ -10,21 +8,9 @@ from .. import serializers, mixins
from ..models import LoginConfirmTicket from ..models import LoginConfirmTicket
class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, viewsets.ModelViewSet): class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, BulkModelViewSet):
serializer_class = serializers.LoginConfirmTicketSerializer serializer_class = serializers.LoginConfirmTicketSerializer
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
queryset = LoginConfirmTicket.objects.all() queryset = LoginConfirmTicket.objects.all()
filter_fields = ['status', 'title', 'action', 'ip'] filter_fields = ['status', 'title', 'action', 'ip']
search_fields = ['user_display', 'title', 'ip', 'city'] search_fields = ['user_display', 'title', 'ip', 'city']
# def check_update_permission(self, serializer):
# data = serializer.validated_data
# action = data.get("action")
# user = self.request.user
# instance = serializer.instance
# if action and user not in instance.assignees.all():
# error = {"action": "Only assignees can update"}
# raise ValidationError(error)
#
# def perform_update(self, serializer):
# self.check_update_permission(serializer)
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
# #
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from common.mixins.serializers import BulkSerializerMixin
from .base import TicketSerializer from .base import TicketSerializer
from ..models import LoginConfirmTicket from ..models import LoginConfirmTicket
...@@ -9,8 +11,9 @@ from ..models import LoginConfirmTicket ...@@ -9,8 +11,9 @@ from ..models import LoginConfirmTicket
__all__ = ['LoginConfirmTicketSerializer', 'LoginConfirmTicketActionSerializer'] __all__ = ['LoginConfirmTicketSerializer', 'LoginConfirmTicketActionSerializer']
class LoginConfirmTicketSerializer(serializers.ModelSerializer): class LoginConfirmTicketSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta: class Meta:
list_serializer_class = AdaptedBulkListSerializer
model = LoginConfirmTicket model = LoginConfirmTicket
fields = TicketSerializer.Meta.fields + [ fields = TicketSerializer.Meta.fields + [
'ip', 'city', 'action' 'ip', 'city', 'action'
...@@ -24,11 +27,14 @@ class LoginConfirmTicketSerializer(serializers.ModelSerializer): ...@@ -24,11 +27,14 @@ class LoginConfirmTicketSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data): def update(self, instance, validated_data):
action = validated_data.get("action") action = validated_data.get("action")
user = self.context["request"].user user = self.context["request"].user
if action and user not in instance.assignees.all(): if action and user not in instance.assignees.all():
error = {"action": "Only assignees can update"} error = {"action": "Only assignees can update"}
raise serializers.ValidationError(error) raise serializers.ValidationError(error)
if instance.status == instance.STATUS_CLOSED:
validated_data.pop('action')
instance = super().update(instance, validated_data) instance = super().update(instance, validated_data)
if action: if not instance.status == instance.STATUS_CLOSED:
instance.perform_action(action, user) instance.perform_action(action, user)
return instance return instance
......
...@@ -21,6 +21,19 @@ ...@@ -21,6 +21,19 @@
<tbody> <tbody>
</tbody> </tbody>
</table> </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' %} {% include '_filter_dropdown.html' %}
{% endblock %} {% endblock %}
{% block content_bottom_left %}{% endblock %} {% block content_bottom_left %}{% endblock %}
...@@ -38,9 +51,9 @@ function initTable() { ...@@ -38,9 +51,9 @@ function initTable() {
$(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id)); $(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id));
}}, }},
{targets: 3, createdCell: function (td, cellData, rowData) { {targets: 3, createdCell: function (td, cellData, rowData) {
if (cellData === "approval") { if (cellData === "approve") {
$(td).html('<i class="fa fa-check text-navy"></i>') $(td).html('<i class="fa fa-check text-navy"></i>')
} else if (cellData === "rejected") { } else if (cellData === "reject") {
$(td).html('<i class="fa fa-times text-danger"></i>') $(td).html('<i class="fa fa-times text-danger"></i>')
} else if (cellData === "open") { } else if (cellData === "open") {
$(td).html('<i class="fa fa-spinner text-info"></i>') $(td).html('<i class="fa fa-spinner text-info"></i>')
...@@ -70,9 +83,9 @@ function initTable() { ...@@ -70,9 +83,9 @@ function initTable() {
columns: [ columns: [
{data: "id"}, {data: "title"}, {data: "id"}, {data: "title"},
{data: "user_display"}, {data: "user_display"},
{data: "status", ticketable: false}, {data: "action", width: "40px"},
{data: "date_created", width: "120px"}, {data: "date_created", width: "120px"},
{data: "id", ticketable: false} {data: "id", orderable: false}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
...@@ -85,6 +98,7 @@ $(document).ready(function(){ ...@@ -85,6 +98,7 @@ $(document).ready(function(){
var menu = [ var menu = [
{title: "IP", value: "ip"}, {title: "IP", value: "ip"},
{title: "{% trans 'Title' %}", value: "title"}, {title: "{% trans 'Title' %}", value: "title"},
{title: "{% trans 'User' %}", value: "user_display"},
{title: "{% trans 'Status' %}", value: "status", submenu: [ {title: "{% trans 'Status' %}", value: "status", submenu: [
{title: "{% trans 'Open' %}", value: "open"}, {title: "{% trans 'Open' %}", value: "open"},
{title: "{% trans 'Closed' %}", value: "closed"}, {title: "{% trans 'Closed' %}", value: "closed"},
...@@ -107,6 +121,33 @@ $(document).ready(function(){ ...@@ -107,6 +121,33 @@ $(document).ready(function(){
success: reloadPage success: reloadPage
}; };
requestApi(data); 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> </script>
{% endblock %} {% endblock %}
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.urls import path from rest_framework_bulk.routes import BulkRouter
from rest_framework.routers import DefaultRouter
from .. import api from .. import api
app_name = 'tickets' app_name = 'tickets'
router = DefaultRouter() router = BulkRouter()
router.register('tickets', api.TicketViewSet, 'ticket') router.register('tickets', api.TicketViewSet, 'ticket')
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment') router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment