mirror of https://github.com/jumpserver/jumpserver
[Update] 修改tickets
parent
bd323d608e
commit
9e4874834f
|
@ -10,7 +10,7 @@ from common.utils import get_logger, get_object_or_none
|
|||
from common.permissions import IsOrgAdmin
|
||||
from ..models import LoginConfirmSetting
|
||||
from ..serializers import LoginConfirmSettingSerializer
|
||||
from .. import errors
|
||||
from .. import errors, mixins
|
||||
|
||||
__all__ = ['LoginConfirmSettingUpdateApi', 'LoginConfirmTicketStatusApi']
|
||||
logger = get_logger(__name__)
|
||||
|
@ -31,7 +31,7 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
|
|||
return s
|
||||
|
||||
|
||||
class LoginConfirmTicketStatusApi(APIView):
|
||||
class LoginConfirmTicketStatusApi(mixins.AuthMixin, APIView):
|
||||
permission_classes = ()
|
||||
|
||||
def get_ticket(self):
|
||||
|
@ -45,24 +45,9 @@ class LoginConfirmTicketStatusApi(APIView):
|
|||
return ticket
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
ticket_id = self.request.session.get("auth_ticket_id")
|
||||
ticket = self.get_ticket()
|
||||
try:
|
||||
if not ticket:
|
||||
raise errors.LoginConfirmOtherError(ticket_id, _("not found"))
|
||||
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()
|
||||
)
|
||||
self.check_user_login_confirm()
|
||||
return Response({"msg": "ok"})
|
||||
except errors.NeedMoreInfoError as e:
|
||||
return Response(e.as_data(), status=200)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class TokenCreateApi(AuthMixin, CreateAPIView):
|
|||
self.create_session_if_need()
|
||||
# 如果认证没有过,检查账号密码
|
||||
try:
|
||||
user = self.check_user_auth()
|
||||
user = self.check_user_auth_if_need()
|
||||
self.check_user_mfa_if_need(user)
|
||||
self.check_user_login_confirm_if_need(user)
|
||||
self.send_auth_signal(success=True, user=user)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import time
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import get_object_or_none, get_request_ip, get_logger
|
||||
from users.models import User
|
||||
|
@ -49,8 +50,8 @@ class AuthMixin:
|
|||
raise errors.BlockLoginError(username=username, ip=ip)
|
||||
|
||||
def check_user_auth(self):
|
||||
request = self.request
|
||||
self.check_is_block()
|
||||
request = self.request
|
||||
if hasattr(request, 'data'):
|
||||
username = request.data.get('username', '')
|
||||
password = request.data.get('password', '')
|
||||
|
@ -73,11 +74,20 @@ class AuthMixin:
|
|||
request.session['user_id'] = str(user.id)
|
||||
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):
|
||||
if self.request.session.get('auth_mfa'):
|
||||
return True
|
||||
return
|
||||
if not user.otp_enabled or not user.otp_secret_key:
|
||||
return True
|
||||
return
|
||||
raise errors.MFARequiredError()
|
||||
|
||||
def check_user_mfa(self, code):
|
||||
|
@ -90,28 +100,53 @@ class AuthMixin:
|
|||
return
|
||||
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
|
||||
confirm_setting = user.get_login_confirm_setting()
|
||||
if self.request.session.get('auth_confirm') or not confirm_setting:
|
||||
return
|
||||
ticket = None
|
||||
if self.request.session.get('auth_ticket_id'):
|
||||
ticket_id = self.request.session['auth_ticket_id']
|
||||
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)
|
||||
return ticket
|
||||
|
||||
def get_ticket_or_create(self, confirm_setting):
|
||||
ticket = self.get_ticket()
|
||||
if not ticket:
|
||||
ticket = confirm_setting.create_confirm_ticket(self.request)
|
||||
self.request.session['auth_ticket_id'] = str(ticket.id)
|
||||
return ticket
|
||||
|
||||
if ticket.status == "accepted":
|
||||
return
|
||||
elif ticket.status == "rejected":
|
||||
raise errors.LoginConfirmOtherError(ticket.id)
|
||||
else:
|
||||
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
|
||||
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()
|
||||
)
|
||||
|
||||
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):
|
||||
self.request.session['auth_password'] = ''
|
||||
self.request.session['auth_user_id'] = ''
|
||||
self.request.session['auth_mfa'] = ''
|
||||
self.request.session['auth_confirm'] = ''
|
||||
self.request.session['auth_ticket_id'] = ''
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<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' %}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -132,7 +132,11 @@ $(document).ready(function () {
|
|||
checkInterval = setInterval(doRequestAuth, 5000);
|
||||
doRequestAuth();
|
||||
initClipboard();
|
||||
window.onbeforeunload = function (e) {
|
||||
return "{% trans "Confirm" %}";
|
||||
};
|
||||
}).on('click', '.btn-refresh', function () {
|
||||
window.onbeforeunload = function() {};
|
||||
window.location.reload();
|
||||
})
|
||||
|
||||
|
|
|
@ -19,9 +19,7 @@ from django.conf import settings
|
|||
from django.urls import reverse_lazy
|
||||
|
||||
from common.utils import get_request_ip, get_object_or_none
|
||||
from users.models import User
|
||||
from users.utils import (
|
||||
get_user_or_tmp_user, increase_login_failed_count,
|
||||
redirect_user_first_login_or_index
|
||||
)
|
||||
from ..signals import post_auth_success, post_auth_failed
|
||||
|
@ -117,42 +115,28 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
|
|||
return url
|
||||
|
||||
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)
|
||||
user = self.get_user_from_session()
|
||||
# 启用并设置了otp
|
||||
if user.otp_enabled and user.otp_secret_key and \
|
||||
not self.request.session.get('auth_mfa'):
|
||||
except errors.MFARequiredError:
|
||||
return self.format_redirect_url(self.login_otp_url)
|
||||
confirm_setting = user.get_login_confirm_setting()
|
||||
if confirm_setting and not self.request.session.get('auth_confirm'):
|
||||
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)
|
||||
except errors.LoginConfirmBaseError:
|
||||
return self.format_redirect_url(self.login_confirm_url)
|
||||
else:
|
||||
post_auth_failed.send(
|
||||
sender=self.__class__, username=username,
|
||||
request=self.request, reason=reason
|
||||
auth_login(self.request, user)
|
||||
self.send_auth_signal(success=True, user=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
|
||||
|
||||
|
||||
class UserLoginWaitConfirmView(TemplateView):
|
||||
|
|
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-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"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
||||
|
@ -354,6 +354,7 @@ 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,7 +529,7 @@ msgstr "创建远程应用"
|
|||
#: 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:92
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:105
|
||||
#: users/templates/users/_granted_assets.html:34
|
||||
#: users/templates/users/user_group_list.html:38
|
||||
#: users/templates/users/user_list.html:41
|
||||
|
@ -1480,7 +1481,7 @@ msgid "Asset user auth"
|
|||
msgstr "资产用户信息"
|
||||
|
||||
#: 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"
|
||||
msgstr "复制成功"
|
||||
|
||||
|
@ -1666,6 +1667,7 @@ msgstr "选择节点"
|
|||
#: assets/templates/assets/system_user_detail.html:182
|
||||
#: assets/templates/assets/system_user_list.html:135
|
||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:20
|
||||
#: authentication/templates/authentication/login_wait_confirm.html:136
|
||||
#: settings/templates/settings/terminal_setting.html:168
|
||||
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
|
||||
#: users/templates/users/user_detail.html:271
|
||||
|
@ -2294,7 +2296,7 @@ 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:88
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:101
|
||||
#: tickets/templates/tickets/ticket_detail.html:34
|
||||
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310
|
||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
|
||||
|
@ -2390,10 +2392,6 @@ msgstr "登录日志"
|
|||
msgid "Command execution log"
|
||||
msgstr "命令执行"
|
||||
|
||||
#: authentication/api/login_confirm.py:52
|
||||
msgid "not found"
|
||||
msgstr "没有发现"
|
||||
|
||||
#: authentication/backends/api.py:53
|
||||
msgid "Invalid signature header. No credentials provided."
|
||||
msgstr ""
|
||||
|
@ -2703,11 +2701,11 @@ msgstr "返回"
|
|||
msgid "Welcome back, please enter username and password to login"
|
||||
msgstr "欢迎回来,请输入用户名和密码登录"
|
||||
|
||||
#: authentication/views/login.py:73
|
||||
#: authentication/views/login.py:71
|
||||
msgid "Please enable cookies and try again."
|
||||
msgstr "设置你的浏览器支持cookie"
|
||||
|
||||
#: authentication/views/login.py:172
|
||||
#: authentication/views/login.py:170
|
||||
msgid ""
|
||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||
" Don't close this page"
|
||||
|
@ -2715,15 +2713,15 @@ msgstr ""
|
|||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||
" 不要关闭本页面"
|
||||
|
||||
#: authentication/views/login.py:177
|
||||
#: authentication/views/login.py:175
|
||||
msgid "No ticket found"
|
||||
msgstr "没有发现工单"
|
||||
|
||||
#: authentication/views/login.py:200
|
||||
#: authentication/views/login.py:198
|
||||
msgid "Logout success"
|
||||
msgstr "退出登录成功"
|
||||
|
||||
#: authentication/views/login.py:201
|
||||
#: authentication/views/login.py:199
|
||||
msgid "Logout success, return login page"
|
||||
msgstr "退出登录成功,返回到登录页面"
|
||||
|
||||
|
@ -4571,8 +4569,8 @@ 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:57
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:94
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:70
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:107
|
||||
msgid "Reject"
|
||||
msgstr "拒绝"
|
||||
|
||||
|
@ -4610,12 +4608,12 @@ msgid ""
|
|||
msgstr "你可以使用ssh客户端工具连接终端"
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
msgstr "开启"
|
||||
|
||||
#: 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"
|
||||
msgstr "关闭"
|
||||
|
||||
|
@ -4629,7 +4627,7 @@ msgstr "用户显示名称"
|
|||
|
||||
#: tickets/models/base.py:28
|
||||
#: 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"
|
||||
msgstr "标题"
|
||||
|
||||
|
@ -4659,8 +4657,8 @@ msgstr "{} {} 这个工单"
|
|||
|
||||
#: tickets/models/login_confirm.py:15
|
||||
#: 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:93
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:69
|
||||
#: tickets/templates/tickets/login_confirm_ticket_list.html:106
|
||||
msgid "Approve"
|
||||
msgstr "同意"
|
||||
|
||||
|
@ -4668,6 +4666,14 @@ msgstr "同意"
|
|||
msgid "this order"
|
||||
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:81
|
||||
msgid "ago"
|
||||
|
@ -6431,6 +6437,12 @@ msgstr "密码匣子"
|
|||
msgid "vault create"
|
||||
msgstr "创建"
|
||||
|
||||
#~ msgid "selected"
|
||||
#~ msgstr "所选"
|
||||
|
||||
#~ msgid "not found"
|
||||
#~ msgstr "没有发现"
|
||||
|
||||
#~ msgid "Log in frequently and try again later"
|
||||
#~ msgstr "登录频繁, 稍后重试"
|
||||
|
||||
|
@ -6446,9 +6458,6 @@ msgstr "创建"
|
|||
#~ msgid "Accepted"
|
||||
#~ msgstr "已接受"
|
||||
|
||||
#~ msgid "Rejected"
|
||||
#~ msgstr "已拒绝"
|
||||
|
||||
#~ msgid "New order"
|
||||
#~ msgstr "新工单"
|
||||
|
||||
|
|
|
@ -1319,5 +1319,5 @@ function initDateRangePicker(selector, options) {
|
|||
}
|
||||
|
||||
function reloadPage() {
|
||||
window.location.reload();
|
||||
setTimeout( function () {window.location.reload();}, 300);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import viewsets, generics
|
||||
from rest_framework.serializers import ValidationError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.permissions import IsValidUser
|
||||
from common.mixins import CommonApiMixin
|
||||
|
@ -10,21 +8,9 @@ from .. import serializers, mixins
|
|||
from ..models import LoginConfirmTicket
|
||||
|
||||
|
||||
class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, viewsets.ModelViewSet):
|
||||
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']
|
||||
|
||||
# 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 @@
|
|||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.mixins.serializers import BulkSerializerMixin
|
||||
from .base import TicketSerializer
|
||||
from ..models import LoginConfirmTicket
|
||||
|
||||
|
@ -9,8 +11,9 @@ from ..models import LoginConfirmTicket
|
|||
__all__ = ['LoginConfirmTicketSerializer', 'LoginConfirmTicketActionSerializer']
|
||||
|
||||
|
||||
class LoginConfirmTicketSerializer(serializers.ModelSerializer):
|
||||
class LoginConfirmTicketSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
class Meta:
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
model = LoginConfirmTicket
|
||||
fields = TicketSerializer.Meta.fields + [
|
||||
'ip', 'city', 'action'
|
||||
|
@ -24,11 +27,14 @@ class LoginConfirmTicketSerializer(serializers.ModelSerializer):
|
|||
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 action:
|
||||
if not instance.status == instance.STATUS_CLOSED:
|
||||
instance.perform_action(action, user)
|
||||
return instance
|
||||
|
||||
|
|
|
@ -21,6 +21,19 @@
|
|||
<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 %}
|
||||
|
@ -38,9 +51,9 @@ function initTable() {
|
|||
$(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
if (cellData === "approval") {
|
||||
if (cellData === "approve") {
|
||||
$(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>')
|
||||
} else if (cellData === "open") {
|
||||
$(td).html('<i class="fa fa-spinner text-info"></i>')
|
||||
|
@ -70,9 +83,9 @@ function initTable() {
|
|||
columns: [
|
||||
{data: "id"}, {data: "title"},
|
||||
{data: "user_display"},
|
||||
{data: "status", ticketable: false},
|
||||
{data: "action", width: "40px"},
|
||||
{data: "date_created", width: "120px"},
|
||||
{data: "id", ticketable: false}
|
||||
{data: "id", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
|
@ -85,6 +98,7 @@ $(document).ready(function(){
|
|||
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"},
|
||||
|
@ -107,6 +121,33 @@ $(document).ready(function(){
|
|||
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 %}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'tickets'
|
||||
router = DefaultRouter()
|
||||
router = BulkRouter()
|
||||
|
||||
router.register('tickets', api.TicketViewSet, 'ticket')
|
||||
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
|
||||
|
|
Loading…
Reference in New Issue