mirror of https://github.com/jumpserver/jumpserver
[Update] Rename app
parent
edce831e46
commit
08775551c2
|
@ -11,7 +11,7 @@ from ..models import LoginConfirmSetting
|
|||
from ..serializers import LoginConfirmSettingSerializer
|
||||
from .. import errors
|
||||
|
||||
__all__ = ['LoginConfirmSettingUpdateApi', 'UserOrderAcceptAuthApi']
|
||||
__all__ = ['LoginConfirmSettingUpdateApi', 'UserTicketAcceptAuthApi']
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
|
@ -30,27 +30,42 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
|
|||
return s
|
||||
|
||||
|
||||
class UserOrderAcceptAuthApi(APIView):
|
||||
class UserTicketAcceptAuthApi(APIView):
|
||||
permission_classes = ()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
from orders.models import LoginConfirmOrder
|
||||
order_id = self.request.session.get("auth_order_id")
|
||||
logger.debug('Login confirm order id: {}'.format(order_id))
|
||||
if not order_id:
|
||||
order = None
|
||||
from tickets.models import LoginConfirmTicket
|
||||
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:
|
||||
order = get_object_or_none(LoginConfirmOrder, pk=order_id)
|
||||
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
|
||||
try:
|
||||
if not order:
|
||||
raise errors.LoginConfirmOrderNotFound(order_id)
|
||||
if order.status == order.STATUS_ACCEPTED:
|
||||
if not ticket:
|
||||
raise errors.LoginConfirmTicketNotFound(ticket_id)
|
||||
if ticket.action == LoginConfirmTicket.ACTION_APPROVE:
|
||||
self.request.session["auth_confirm"] = "1"
|
||||
return Response({"msg": "ok"})
|
||||
elif order.status == order.STATUS_REJECTED:
|
||||
raise errors.LoginConfirmRejectedError(order_id)
|
||||
elif ticket.action == LoginConfirmTicket.ACTION_REJECT:
|
||||
raise errors.LoginConfirmRejectedError(ticket_id)
|
||||
else:
|
||||
raise errors.LoginConfirmWaitError(order_id)
|
||||
raise errors.LoginConfirmWaitError(ticket_id)
|
||||
except errors.AuthFailedError as e:
|
||||
data = e.as_data()
|
||||
return Response(data, status=400)
|
||||
|
||||
|
||||
class UserTicketCancelAuthApi(APIView):
|
||||
permission_classes = ()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
from tickets.models import LoginConfirmTicket
|
||||
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)
|
||||
if not ticket:
|
||||
ticket.status = "close"
|
||||
|
|
|
@ -47,9 +47,9 @@ mfa_failed_msg = _("MFA code invalid, or ntp sync server time")
|
|||
|
||||
mfa_required_msg = _("MFA required")
|
||||
login_confirm_required_msg = _("Login confirm required")
|
||||
login_confirm_wait_msg = _("Wait login confirm order for accept")
|
||||
login_confirm_rejected_msg = _("Login confirm order was rejected")
|
||||
login_confirm_order_not_found_msg = _("Order not found")
|
||||
login_confirm_wait_msg = _("Wait login confirm ticket for accept")
|
||||
login_confirm_rejected_msg = _("Login confirm ticket was rejected")
|
||||
login_confirm_ticket_not_found_msg = _("Ticket not found")
|
||||
|
||||
|
||||
class AuthFailedNeedLogMixin:
|
||||
|
@ -155,8 +155,8 @@ class LoginConfirmError(AuthFailedError):
|
|||
msg = login_confirm_wait_msg
|
||||
error = 'login_confirm_wait'
|
||||
|
||||
def __init__(self, order_id, **kwargs):
|
||||
self.order_id = order_id
|
||||
def __init__(self, ticket_id, **kwargs):
|
||||
self.ticket_id = ticket_id
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def as_data(self):
|
||||
|
@ -164,7 +164,7 @@ class LoginConfirmError(AuthFailedError):
|
|||
"error": self.error,
|
||||
"msg": self.msg,
|
||||
"data": {
|
||||
"order_id": self.order_id
|
||||
"ticket_id": self.ticket_id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,6 +179,6 @@ class LoginConfirmRejectedError(LoginConfirmError):
|
|||
error = 'login_confirm_rejected'
|
||||
|
||||
|
||||
class LoginConfirmOrderNotFound(LoginConfirmError):
|
||||
msg = login_confirm_order_not_found_msg
|
||||
error = 'login_confirm_order_not_found'
|
||||
class LoginConfirmTicketNotFound(LoginConfirmError):
|
||||
msg = login_confirm_ticket_not_found_msg
|
||||
error = 'login_confirm_ticket_not_found'
|
||||
|
|
|
@ -91,30 +91,30 @@ class AuthMixin:
|
|||
raise errors.MFAFailedError(username=user.username, request=self.request)
|
||||
|
||||
def check_user_login_confirm_if_need(self, user):
|
||||
from orders.models import LoginConfirmOrder
|
||||
from tickets.models import LoginConfirmTicket
|
||||
confirm_setting = user.get_login_confirm_setting()
|
||||
if self.request.session.get('auth_confirm') or not confirm_setting:
|
||||
return
|
||||
order = None
|
||||
if self.request.session.get('auth_order_id'):
|
||||
order_id = self.request.session['auth_order_id']
|
||||
order = get_object_or_none(LoginConfirmOrder, pk=order_id)
|
||||
if not order:
|
||||
order = confirm_setting.create_confirm_order(self.request)
|
||||
self.request.session['auth_order_id'] = str(order.id)
|
||||
ticket = None
|
||||
if self.request.session.get('auth_ticket_id'):
|
||||
ticket_id = self.request.session['auth_ticket_id']
|
||||
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
|
||||
if not ticket:
|
||||
ticket = confirm_setting.create_confirm_ticket(self.request)
|
||||
self.request.session['auth_ticket_id'] = str(ticket.id)
|
||||
|
||||
if order.status == "accepted":
|
||||
if ticket.status == "accepted":
|
||||
return
|
||||
elif order.status == "rejected":
|
||||
raise errors.LoginConfirmRejectedError(order.id)
|
||||
elif ticket.status == "rejected":
|
||||
raise errors.LoginConfirmRejectedError(ticket.id)
|
||||
else:
|
||||
raise errors.LoginConfirmWaitError(order.id)
|
||||
raise errors.LoginConfirmWaitError(ticket.id)
|
||||
|
||||
def clear_auth_mark(self):
|
||||
self.request.session['auth_password'] = ''
|
||||
self.request.session['auth_mfa'] = ''
|
||||
self.request.session['auth_confirm'] = ''
|
||||
self.request.session['auth_order_id'] = ''
|
||||
self.request.session['auth_ticket_id'] = ''
|
||||
|
||||
def send_auth_signal(self, success=True, user=None, username='', reason=''):
|
||||
if success:
|
||||
|
|
|
@ -48,8 +48,8 @@ class LoginConfirmSetting(CommonModelMixin):
|
|||
def get_user_confirm_setting(cls, user):
|
||||
return get_object_or_none(cls, user=user)
|
||||
|
||||
def create_confirm_order(self, request=None):
|
||||
from orders.models import LoginConfirmOrder
|
||||
def create_confirm_ticket(self, request=None):
|
||||
from tickets.models import LoginConfirmTicket
|
||||
title = _('User login confirm: {}').format(self.user)
|
||||
if request:
|
||||
remote_addr = get_request_ip(request)
|
||||
|
@ -58,20 +58,20 @@ class LoginConfirmSetting(CommonModelMixin):
|
|||
self.user, remote_addr, city, timezone.now()
|
||||
)
|
||||
else:
|
||||
city = ''
|
||||
remote_addr = ''
|
||||
city = 'Localhost'
|
||||
remote_addr = '127.0.0.1'
|
||||
body = ''
|
||||
reviewer = self.reviewers.all()
|
||||
reviewer_names = ','.join([u.name for u in reviewer])
|
||||
order = LoginConfirmOrder.objects.create(
|
||||
ticket = LoginConfirmTicket.objects.create(
|
||||
user=self.user, user_display=str(self.user),
|
||||
title=title, body=body,
|
||||
city=city, ip=remote_addr,
|
||||
assignees_display=reviewer_names,
|
||||
type=LoginConfirmOrder.TYPE_LOGIN_CONFIRM,
|
||||
type=LoginConfirmTicket.TYPE_LOGIN_CONFIRM,
|
||||
)
|
||||
order.assignees.set(reviewer)
|
||||
return order
|
||||
ticket.assignees.set(reviewer)
|
||||
return ticket
|
||||
|
||||
def __str__(self):
|
||||
return '{} confirm'.format(self.user.username)
|
||||
|
|
|
@ -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('order/auth/', api.UserOrderAcceptAuthApi.as_view(), name='user-order-auth'),
|
||||
path('order/auth/', api.UserTicketAcceptAuthApi.as_view(), name='user-order-auth'),
|
||||
path('login-confirm-settings/<uuid:user_id>/', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update')
|
||||
]
|
||||
|
||||
|
|
|
@ -126,8 +126,8 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
|
|||
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'):
|
||||
order = confirm_setting.create_confirm_order(self.request)
|
||||
self.request.session['auth_order_id'] = str(order.id)
|
||||
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)
|
||||
|
@ -159,26 +159,26 @@ class UserLoginWaitConfirmView(TemplateView):
|
|||
template_name = 'authentication/login_wait_confirm.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
from orders.models import LoginConfirmOrder
|
||||
order_id = self.request.session.get("auth_order_id")
|
||||
if not order_id:
|
||||
order = None
|
||||
from tickets.models import LoginConfirmTicket
|
||||
ticket_id = self.request.session.get("auth_ticket_id")
|
||||
if not ticket_id:
|
||||
ticket = None
|
||||
else:
|
||||
order = get_object_or_none(LoginConfirmOrder, pk=order_id)
|
||||
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
|
||||
context = super().get_context_data(**kwargs)
|
||||
if order:
|
||||
order_detail_url = reverse('orders:login-confirm-order-detail', kwargs={'pk': order_id})
|
||||
timestamp_created = datetime.datetime.timestamp(order.date_created)
|
||||
if ticket:
|
||||
ticket_detail_url = reverse('tickets:login-confirm-ticket-detail', kwargs={'pk': ticket_id})
|
||||
timestamp_created = datetime.datetime.timestamp(ticket.date_created)
|
||||
msg = _("""Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>
|
||||
Don't close this page""").format(order.assignees_display)
|
||||
Don't close this page""").format(ticket.assignees_display)
|
||||
else:
|
||||
timestamp_created = 0
|
||||
order_detail_url = ''
|
||||
msg = _("No order found")
|
||||
ticket_detail_url = ''
|
||||
msg = _("No ticket found")
|
||||
context.update({
|
||||
"msg": msg,
|
||||
"timestamp": timestamp_created,
|
||||
"order_detail_url": order_detail_url
|
||||
"ticket_detail_url": ticket_detail_url
|
||||
})
|
||||
return context
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ INSTALLED_APPS = [
|
|||
'audits.apps.AuditsConfig',
|
||||
'authentication.apps.AuthenticationConfig', # authentication
|
||||
'applications.apps.ApplicationsConfig',
|
||||
'orders.apps.OrdersConfig',
|
||||
'tickets.apps.TicketsConfig',
|
||||
'rest_framework',
|
||||
'rest_framework_swagger',
|
||||
'drf_yasg',
|
||||
|
|
|
@ -24,7 +24,7 @@ api_v1 = [
|
|||
path('authentication/', include('authentication.urls.api_urls', namespace='api-auth')),
|
||||
path('common/', include('common.urls.api_urls', namespace='api-common')),
|
||||
path('applications/', include('applications.urls.api_urls', namespace='api-applications')),
|
||||
path('orders/', include('orders.urls.api_urls', namespace='api-orders')),
|
||||
path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')),
|
||||
]
|
||||
|
||||
api_v2 = [
|
||||
|
@ -43,7 +43,7 @@ app_view_patterns = [
|
|||
path('orgs/', include('orgs.urls.views_urls', namespace='orgs')),
|
||||
path('auth/', include('authentication.urls.view_urls'), name='auth'),
|
||||
path('applications/', include('applications.urls.views_urls', namespace='applications')),
|
||||
path('orders/', include('orders.urls.views_urls', namespace='orders')),
|
||||
path('tickets/', include('tickets.urls.views_urls', namespace='tickets')),
|
||||
re_path(r'flower/(?P<path>.*)', celery_flower_view, name='flower-view'),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import viewsets, generics
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.permissions import IsValidUser
|
||||
from common.mixins import CommonApiMixin
|
||||
from . import serializers
|
||||
from .models import LoginConfirmOrder
|
||||
|
||||
|
||||
class LoginConfirmOrderViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||
serializer_class = serializers.LoginConfirmOrderSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
filter_fields = ['status', 'title']
|
||||
search_fields = ['user_display', 'title', 'ip', 'city']
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = LoginConfirmOrder.objects.all()\
|
||||
.filter(assignees=self.request.user)
|
||||
return queryset
|
||||
|
||||
|
||||
class LoginConfirmOrderCreateActionApi(generics.CreateAPIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = serializers.LoginConfirmOrderActionSerializer
|
||||
|
||||
def get_order(self):
|
||||
order_id = self.kwargs.get('pk')
|
||||
queryset = LoginConfirmOrder.objects.all()\
|
||||
.filter(assignees=self.request.user)
|
||||
order = get_object_or_404(queryset, id=order_id)
|
||||
return order
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
order = self.get_order()
|
||||
context['order'] = order
|
||||
return context
|
|
@ -1,9 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OrdersConfig(AppConfig):
|
||||
name = 'orders'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
return super().ready()
|
|
@ -1,72 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import LoginConfirmOrder, Comment
|
||||
|
||||
|
||||
class LoginConfirmOrderSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = LoginConfirmOrder
|
||||
fields = [
|
||||
'id', 'user', 'user_display', 'title', 'body',
|
||||
'ip', 'city', 'assignees', 'assignees_display',
|
||||
'type', 'status', 'date_created', 'date_updated',
|
||||
]
|
||||
|
||||
|
||||
class LoginConfirmOrderActionSerializer(serializers.Serializer):
|
||||
ACTION_CHOICES = (
|
||||
('accept', _('Accept')),
|
||||
('reject', _('Reject')),
|
||||
('comment', _('Comment'))
|
||||
)
|
||||
action = serializers.ChoiceField(choices=ACTION_CHOICES)
|
||||
comment = serializers.CharField(allow_blank=True)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
||||
def create_comments(self, order, user, validated_data):
|
||||
comment_data = validated_data.get('comment')
|
||||
action = validated_data.get('action')
|
||||
comments_data = []
|
||||
if comment_data:
|
||||
comments_data.append(comment_data)
|
||||
Comment.objects.create(
|
||||
order_id=order.id, body=comment_data, user=user,
|
||||
user_display=str(user)
|
||||
)
|
||||
if action != "comment":
|
||||
action_display = dict(self.ACTION_CHOICES).get(action)
|
||||
comment_data = '{} {} {}'.format(user, action_display, _("this order"))
|
||||
comments_data.append(comment_data)
|
||||
comments = [
|
||||
Comment(order_id=order.id, body=data, user=user, user_display=str(user))
|
||||
for data in comments_data
|
||||
]
|
||||
Comment.objects.bulk_create(comments)
|
||||
|
||||
@staticmethod
|
||||
def perform_action(order, user, validated_data):
|
||||
action = validated_data.get('action')
|
||||
if action == "accept":
|
||||
status = "accepted"
|
||||
elif action == "reject":
|
||||
status = "rejected"
|
||||
else:
|
||||
status = None
|
||||
|
||||
if status:
|
||||
order.status = status
|
||||
order.assignee = user
|
||||
order.assignee_display = str(user)
|
||||
order.save()
|
||||
|
||||
def create(self, validated_data):
|
||||
order = self.context['order']
|
||||
user = self.context['request'].user
|
||||
self.create_comments(order, user, validated_data)
|
||||
self.perform_action(order, user, validated_data)
|
||||
return validated_data
|
|
@ -1,31 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import m2m_changed, post_save
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import LoginConfirmOrder
|
||||
from .utils import (
|
||||
send_login_confirm_order_mail_to_assignees,
|
||||
send_login_confirm_action_mail_to_user
|
||||
)
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=LoginConfirmOrder.assignees.through)
|
||||
def on_login_confirm_order_assignees_set(sender, instance=None, action=None,
|
||||
model=None, pk_set=None, **kwargs):
|
||||
if action == 'post_add':
|
||||
logger.debug('New order create, send mail: {}'.format(instance.id))
|
||||
assignees = model.objects.filter(pk__in=pk_set)
|
||||
send_login_confirm_order_mail_to_assignees(instance, assignees)
|
||||
|
||||
|
||||
@receiver(post_save, sender=LoginConfirmOrder)
|
||||
def on_login_confirm_order_status_change(sender, instance=None, created=False, **kwargs):
|
||||
if created or instance.status == "pending":
|
||||
return
|
||||
logger.debug('Order changed, send mail: {}'.format(instance.id))
|
||||
send_login_confirm_action_mail_to_user(instance)
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'orders'
|
||||
router = DefaultRouter()
|
||||
|
||||
router.register('login-confirm-orders', api.LoginConfirmOrderViewSet, 'login-confirm-order')
|
||||
|
||||
urlpatterns = [
|
||||
path('login-confirm-order/<uuid:pk>/actions/',
|
||||
api.LoginConfirmOrderCreateActionApi.as_view(),
|
||||
name='login-confirm-order-create-action'
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
|
@ -1,11 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
app_name = 'orders'
|
||||
|
||||
urlpatterns = [
|
||||
path('login-confirm-orders/', views.LoginConfirmOrderListView.as_view(), name='login-confirm-order-list'),
|
||||
path('login-confirm-orders/<uuid:pk>/', views.LoginConfirmOrderDetailView.as_view(), name='login-confirm-order-detail')
|
||||
]
|
|
@ -1,62 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_logger, reverse
|
||||
from common.tasks import send_mail_async
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def send_login_confirm_order_mail_to_assignees(order, assignees):
|
||||
recipient_list = [user.email for user in assignees]
|
||||
user = order.user
|
||||
if not recipient_list:
|
||||
logger.error("Order not has assignees: {}".format(order.id))
|
||||
return
|
||||
subject = '{}: {}'.format(_("New order"), order.title)
|
||||
detail_url = reverse('orders:login-confirm-order-detail',
|
||||
kwargs={'pk': order.id}, external=True)
|
||||
message = _("""
|
||||
<div>
|
||||
<p>Your has a new order</p>
|
||||
<div>
|
||||
<b>Title:</b> {order.title}
|
||||
<br/>
|
||||
<b>User:</b> {user}
|
||||
<br/>
|
||||
<b>Assignees:</b> {order.assignees_display}
|
||||
<br/>
|
||||
<b>City:</b> {order.city}
|
||||
<br/>
|
||||
<b>IP:</b> {order.ip}
|
||||
<br/>
|
||||
<a href={url}>click here to review</a>
|
||||
</div>
|
||||
</div>
|
||||
""").format(order=order, user=user, url=detail_url)
|
||||
send_mail_async.delay(subject, message, recipient_list, html_message=message)
|
||||
|
||||
|
||||
def send_login_confirm_action_mail_to_user(order):
|
||||
if not order.user:
|
||||
logger.error("Order not has user: {}".format(order.id))
|
||||
return
|
||||
user = order.user
|
||||
recipient_list = [user.email]
|
||||
subject = '{}: {}'.format(_("Order has been reply"), order.title)
|
||||
message = _("""
|
||||
<div>
|
||||
<p>Your order has been replay</p>
|
||||
<div>
|
||||
<b>Title:</b> {order.title}
|
||||
<br/>
|
||||
<b>Assignee:</b> {order.assignee_display}
|
||||
<br/>
|
||||
<b>Status:</b> {order.status_display}
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
""").format(order=order)
|
||||
send_mail_async.delay(subject, message, recipient_list, html_message=message)
|
|
@ -554,3 +554,7 @@ span.select2-selection__placeholder {
|
|||
height: 22px;
|
||||
max-width: inherit;
|
||||
}
|
||||
|
||||
table.table-striped.table-bordered {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
<style>
|
||||
li.dropdown-submenu {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<ul class="dropdown-menu multi-level search-help" role="menu" aria-labelledby="dropdownMenu">
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -122,12 +122,12 @@
|
|||
{% endif %}
|
||||
|
||||
{% if request.user.can_admin_current_org and LOGIN_CONFIRM_ENABLE %}
|
||||
<li id="orders">
|
||||
<li id="tickets">
|
||||
<a>
|
||||
<i class="fa fa-check-square-o" style="width: 14px"></i> <span class="nav-label">{% trans 'Orders' %}</span><span class="fa arrow"></span>
|
||||
<i class="fa fa-check-square-o" style="width: 14px"></i> <span class="nav-label">{% trans 'Tickets' %}</span><span class="fa arrow"></span>
|
||||
</a>
|
||||
<ul class="nav nav-second-level">
|
||||
<li id="login-confirm-orders"><a href="{% url 'orders:login-confirm-order-list' %}">{% trans 'Login confirm' %}</a></li>
|
||||
<li id="login-confirm-orders"><a href="{% url 'tickets:login-confirm-ticket-list' %}">{% trans 'Login confirm' %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .base import *
|
||||
from .login_confirm import *
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import viewsets, generics
|
||||
|
||||
from .. import serializers, models
|
||||
|
||||
|
||||
class TicketViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = serializers.TicketSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = models.Ticket.objects.all().none()
|
||||
return queryset
|
||||
|
||||
|
||||
class CommentViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = serializers.CommentSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = models.Comment.objects.none()
|
||||
return queryset
|
|
@ -0,0 +1,39 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import viewsets, generics
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.permissions import IsValidUser
|
||||
from common.mixins import CommonApiMixin
|
||||
from .. import serializers
|
||||
from ..models import LoginConfirmTicket
|
||||
|
||||
|
||||
class LoginConfirmTicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
|
||||
serializer_class = serializers.LoginConfirmTicketSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
filter_fields = ['status', 'title']
|
||||
search_fields = ['user_display', 'title', 'ip', 'city']
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = LoginConfirmTicket.objects.all()\
|
||||
.filter(assignees=self.request.user)
|
||||
return queryset
|
||||
|
||||
|
||||
class LoginConfirmTicketsCreateActionApi(generics.CreateAPIView):
|
||||
permission_classes = (IsValidUser,)
|
||||
serializer_class = serializers.LoginConfirmTicketActionSerializer
|
||||
|
||||
def get_ticket(self):
|
||||
ticket_id = self.kwargs.get('pk')
|
||||
queryset = LoginConfirmTicket.objects.all()\
|
||||
.filter(assignees=self.request.user)
|
||||
ticket = get_object_or_404(queryset, id=ticket_id)
|
||||
return ticket
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
ticket = self.get_ticket()
|
||||
context['ticket'] = ticket
|
||||
return context
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TicketsConfig(AppConfig):
|
||||
name = 'tickets'
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 2.2.5 on 2019-10-31 10:23
|
||||
# Generated by Django 2.2.5 on 2019-11-07 08:02
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LoginConfirmOrder',
|
||||
name='Ticket',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
|
@ -27,18 +27,28 @@ class Migration(migrations.Migration):
|
|||
('body', models.TextField(verbose_name='Body')),
|
||||
('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(choices=[('login_confirm', 'Login confirm')], max_length=16, verbose_name='Type')),
|
||||
('status', models.CharField(choices=[('accepted', 'Accepted'), ('rejected', 'Rejected'), ('pending', 'Pending')], default='pending', max_length=16)),
|
||||
('ip', models.GenericIPAddressField(blank=True, null=True)),
|
||||
('city', models.CharField(blank=True, default='', max_length=16)),
|
||||
('assignee', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loginconfirmorder_handled', to=settings.AUTH_USER_MODEL, verbose_name='Assignee')),
|
||||
('assignees', models.ManyToManyField(related_name='loginconfirmorder_assigned', to=settings.AUTH_USER_MODEL, verbose_name='Assignees')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='loginconfirmorder_requested', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
('type', models.CharField(default='general', max_length=16, verbose_name='Type')),
|
||||
('status', models.CharField(choices=[('open', 'Open'), ('closed', 'Closed')], default='open', 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')),
|
||||
],
|
||||
options={
|
||||
'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',
|
||||
|
@ -47,9 +57,9 @@ class Migration(migrations.Migration):
|
|||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('order_id', models.UUIDField()),
|
||||
('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')),
|
||||
('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={
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .base import *
|
||||
from .login_confirm import *
|
|
@ -1,33 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.mixins.models import CommonModelMixin
|
||||
|
||||
__all__ = ['LoginConfirmOrder', 'Comment']
|
||||
__all__ = ['Ticket', 'Comment']
|
||||
|
||||
|
||||
class Comment(CommonModelMixin):
|
||||
order_id = models.UUIDField()
|
||||
user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_("User"), related_name='comments')
|
||||
user_display = models.CharField(max_length=128, verbose_name=_("User display name"))
|
||||
body = models.TextField(verbose_name=_("Body"))
|
||||
|
||||
class Meta:
|
||||
ordering = ('date_created', )
|
||||
|
||||
|
||||
class BaseOrder(CommonModelMixin):
|
||||
STATUS_ACCEPTED = 'accepted'
|
||||
STATUS_REJECTED = 'rejected'
|
||||
STATUS_PENDING = 'pending'
|
||||
class Ticket(CommonModelMixin):
|
||||
STATUS_OPEN = 'open'
|
||||
STATUS_CLOSED = 'closed'
|
||||
STATUS_CHOICES = (
|
||||
(STATUS_ACCEPTED, _("Accepted")),
|
||||
(STATUS_REJECTED, _("Rejected")),
|
||||
(STATUS_PENDING, _("Pending"))
|
||||
(STATUS_OPEN, _("Open")),
|
||||
(STATUS_CLOSED, _("Closed"))
|
||||
)
|
||||
TYPE_GENERAL = 'general'
|
||||
TYPE_LOGIN_CONFIRM = 'login_confirm'
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_LOGIN_CONFIRM, 'Login confirm'),
|
||||
(TYPE_GENERAL, _("General")),
|
||||
(TYPE_LOGIN_CONFIRM, _("Login confirm"))
|
||||
)
|
||||
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"))
|
||||
|
@ -38,8 +31,8 @@ class BaseOrder(CommonModelMixin):
|
|||
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(choices=TYPE_CHOICES, max_length=16, verbose_name=_('Type'))
|
||||
status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='pending')
|
||||
type = models.CharField(max_length=16, default='general', verbose_name=_("Type"))
|
||||
status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='open')
|
||||
|
||||
def __str__(self):
|
||||
return '{}: {}'.format(self.user_display, self.title)
|
||||
|
@ -57,10 +50,16 @@ class BaseOrder(CommonModelMixin):
|
|||
return self.get_status_display()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ('-date_created',)
|
||||
|
||||
|
||||
class LoginConfirmOrder(BaseOrder):
|
||||
ip = models.GenericIPAddressField(blank=True, null=True)
|
||||
city = models.CharField(max_length=16, blank=True, default='')
|
||||
class Comment(CommonModelMixin):
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_("User"), related_name='comments')
|
||||
user_display = models.CharField(max_length=128, verbose_name=_("User display name"))
|
||||
body = models.TextField(verbose_name=_("Body"))
|
||||
|
||||
class Meta:
|
||||
ordering = ('date_created', )
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# -*- 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)
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .base import *
|
||||
from .login_confirm import *
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from .. import models
|
||||
|
||||
__all__ = ['TicketSerializer', 'CommentSerializer']
|
||||
|
||||
|
||||
class TicketSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.Ticket
|
||||
fields = [
|
||||
'id', 'user', 'user_display', 'title', 'body',
|
||||
'assignees', 'assignees_display',
|
||||
'status', 'date_created', 'date_updated',
|
||||
]
|
||||
read_only_fields = [
|
||||
'user_display', 'assignees_display',
|
||||
'date_created', 'date_updated',
|
||||
]
|
||||
|
||||
|
||||
class CommentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.Comment
|
||||
fields = [
|
||||
'id', 'ticket', 'body', 'user', 'user_display',
|
||||
'date_created', 'date_updated'
|
||||
]
|
||||
read_only_fields = [
|
||||
'user_display', 'date_created', 'date_updated'
|
||||
]
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from .base import TicketSerializer
|
||||
from ..models import LoginConfirmTicket
|
||||
|
||||
|
||||
__all__ = ['LoginConfirmTicketSerializer', 'LoginConfirmTicketActionSerializer']
|
||||
|
||||
|
||||
class LoginConfirmTicketSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = LoginConfirmTicket
|
||||
fields = TicketSerializer.Meta.fields + [
|
||||
'ip', 'city', 'action'
|
||||
]
|
||||
read_only_fields = TicketSerializer.Meta.read_only_fields
|
||||
|
||||
|
||||
class LoginConfirmTicketActionSerializer(serializers.ModelSerializer):
|
||||
comment = serializers.CharField(allow_blank=True)
|
||||
|
||||
class Meta:
|
||||
model = LoginConfirmTicket
|
||||
fields = ['action', 'comment']
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import m2m_changed, post_save
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import LoginConfirmTicket
|
||||
from .utils import (
|
||||
send_login_confirm_ticket_mail_to_assignees,
|
||||
send_login_confirm_action_mail_to_user
|
||||
)
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=LoginConfirmTicket.assignees.through)
|
||||
def on_login_confirm_ticket_assignees_set(sender, instance=None, action=None,
|
||||
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)
|
||||
|
||||
|
||||
@receiver(post_save, sender=LoginConfirmTicket)
|
||||
def on_login_confirm_ticket_status_change(sender, instance=None, created=False, **kwargs):
|
||||
if created or instance.status == "pending":
|
||||
return
|
||||
logger.debug('Ticket changed, send mail: {}'.format(instance.id))
|
||||
send_login_confirm_action_mail_to_user(instance)
|
|
@ -112,9 +112,9 @@
|
|||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var orderId = "{{ object.id }}";
|
||||
var ticketId = "{{ object.id }}";
|
||||
var status = "{{ object.status }}";
|
||||
var actionCreateUrl = "{% url 'api-orders:login-confirm-order-create-action' pk=object.id %}";
|
||||
var actionCreateUrl = "{% url 'api-tickets:login-confirm-ticket-create-action' pk=object.id %}";
|
||||
$(document).ready(function () {
|
||||
if (status !== "pending") {
|
||||
$('.btn-update').attr('disabled', '1')
|
|
@ -5,7 +5,7 @@
|
|||
{% block custom_head_css_js %}
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<table class="table table-striped table-bordered table-hover " id="login_confirm_order_list_table" >
|
||||
<table class="table table-striped table-bordered table-hover " id="login_confirm_ticket_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
|
@ -27,15 +27,15 @@
|
|||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var orderTable = 0;
|
||||
var ticketTable = 0;
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#login_confirm_order_list_table'),
|
||||
oSearch: {sSearch: "status:pending"},
|
||||
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 "orders:login-confirm-order-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
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) {
|
||||
|
@ -43,12 +43,14 @@ function initTable() {
|
|||
$(td).html(d)
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
if (cellData === "accepted") {
|
||||
if (cellData === "approval") {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else if (cellData === "rejected") {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else if (cellData === "pending") {
|
||||
} 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: 5, createdCell: function (td, cellData) {
|
||||
|
@ -62,25 +64,25 @@ function initTable() {
|
|||
rejectBtn = rejectBtn.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var acceptBtnRef = $(acceptBtn);
|
||||
var rejectBtnRef = $(rejectBtn);
|
||||
if (rowData.status !== "pending") {
|
||||
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-orders:login-confirm-order-list" %}',
|
||||
ajax_url: '{% url "api-tickets:login-confirm-ticket-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "title", className: "text-left"},
|
||||
{data: "id"}, {data: "title"},
|
||||
{data: "user_display"}, {data: "ip"},
|
||||
{data: "status", orderable: false, width: "30px"},
|
||||
{data: "status", ticketable: false},
|
||||
{data: "date_created", width: "120px"},
|
||||
{data: "id", orderable: false, width: "100px"}
|
||||
{data: "id", ticketable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
orderTable = jumpserver.initServerSideDataTable(options);
|
||||
return orderTable
|
||||
ticketTable = jumpserver.initServerSideDataTable(options);
|
||||
return ticketTable
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
|
@ -89,16 +91,19 @@ $(document).ready(function(){
|
|||
{title: "IP", value: "ip"},
|
||||
{title: "{% trans 'Title' %}", value: "title"},
|
||||
{title: "{% trans 'Status' %}", value: "status", submenu: [
|
||||
{title: "{% trans 'Pending' %}", value: "pending"},
|
||||
{title: "{% trans 'Accepted' %}", value: "accepted"},
|
||||
{title: "{% trans 'Rejected' %}", value: "rejected"}
|
||||
]}
|
||||
{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_order_list_table_filter input', menu)
|
||||
initTableFilterDropdown('#login_confirm_ticket_list_table_filter input', menu)
|
||||
}).on('click', '.btn-action', function () {
|
||||
var actionCreateUrl = "{% url 'api-orders:login-confirm-order-create-action' pk=DEFAULT_PK %}";
|
||||
var orderId = $(this).data('uid');
|
||||
actionCreateUrl = actionCreateUrl.replace("{{ DEFAULT_PK }}", orderId);
|
||||
var actionCreateUrl = "{% url 'api-tickets:login-confirm-ticket-create-action' pk=DEFAULT_PK %}";
|
||||
var ticketId = $(this).data('uid');
|
||||
actionCreateUrl = actionCreateUrl.replace("{{ DEFAULT_PK }}", ticketId);
|
||||
var action = $(this).data('action');
|
||||
var comment = '';
|
||||
var data = {
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'tickets'
|
||||
router = DefaultRouter()
|
||||
|
||||
router.register('tickets', api.TicketViewSet, 'ticket')
|
||||
router.register('login-confirm-tickets', api.LoginConfirmTicketViewSet, 'login-confirm-ticket')
|
||||
router.register('tickets/<uuid:ticket_id>/comments/', api.CommentViewSet, 'ticket-comment')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('login-confirm-tickets/<uuid:pk>/actions/',
|
||||
api.LoginConfirmTicketsCreateActionApi.as_view(),
|
||||
name='login-confirm-ticket-create-action'
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
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')
|
||||
]
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_logger, reverse
|
||||
from common.tasks import send_mail_async
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def send_login_confirm_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',
|
||||
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}
|
||||
<br/>
|
||||
<a href={url}>click here to review</a>
|
||||
</div>
|
||||
</div>
|
||||
""").format(ticket=ticket, 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):
|
||||
if not ticket.user:
|
||||
logger.error("Ticket not has user: {}".format(ticket.id))
|
||||
return
|
||||
user = ticket.user
|
||||
recipient_list = [user.email]
|
||||
subject = '{}: {}'.format(_("Ticket has been reply"), ticket.title)
|
||||
message = _("""
|
||||
<div>
|
||||
<p>Your ticket has been replay</p>
|
||||
<div>
|
||||
<b>Title:</b> {ticket.title}
|
||||
<br/>
|
||||
<b>Assignee:</b> {ticket.assignee_display}
|
||||
<br/>
|
||||
<b>Status:</b> {ticket.status_display}
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
""").format(ticket=ticket)
|
||||
send_mail_async.delay(subject, message, recipient_list, html_message=message)
|
|
@ -2,33 +2,33 @@ from django.views.generic import TemplateView, DetailView
|
|||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin
|
||||
from .models import LoginConfirmOrder
|
||||
from .models import LoginConfirmTicket
|
||||
|
||||
|
||||
class LoginConfirmOrderListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'orders/login_confirm_order_list.html'
|
||||
class LoginConfirmTicketListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'tickets/login_confirm_ticket_list.html'
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _("Orders"),
|
||||
'action': _("Login confirm order list")
|
||||
'app': _("Tickets"),
|
||||
'action': _("Login confirm ticket list")
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class LoginConfirmOrderDetailView(PermissionsMixin, DetailView):
|
||||
template_name = 'orders/login_confirm_order_detail.html'
|
||||
class LoginConfirmTicketDetailView(PermissionsMixin, DetailView):
|
||||
template_name = 'tickets/login_confirm_ticket_detail.html'
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_queryset(self):
|
||||
return LoginConfirmOrder.objects.filter(assignees=self.request.user)
|
||||
return LoginConfirmTicket.objects.filter(assignees=self.request.user)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _("Orders"),
|
||||
'action': _("Login confirm order detail")
|
||||
'app': _("Tickets"),
|
||||
'action': _("Login confirm ticket detail")
|
||||
})
|
||||
return context
|
|
@ -26,7 +26,7 @@
|
|||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>
|
||||
<table class="table table-striped table-bordered table-hover " id="user_list_table" >
|
||||
<table class="table table-striped table-bordered table-hover " id="user_list_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
|
@ -125,7 +125,7 @@ function initTable() {
|
|||
{data: "groups_display", orderable: false},
|
||||
{data: "source"},
|
||||
{data: "is_valid", orderable: false},
|
||||
{data: "id", orderable: false, width: "100px"}
|
||||
{data: "id", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue