Commit 08775551 authored by ibuler's avatar ibuler

[Update] Rename app

parent edce831e
......@@ -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'),
]
......
from django.apps import AppConfig
class OrdersConfig(AppConfig):
name = 'orders'
def ready(self):
from . import signals_handler
return super().ready()
# -*- 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
# -*- 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')
]
......@@ -554,3 +554,7 @@ span.select2-selection__placeholder {
height: 22px;
max-width: inherit;
}
table.table-striped.table-bordered {
width: 100% !important;
}
<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 %}
......
# -*- coding: utf-8 -*-
#
from .base import *
from .login_confirm import *
# -*- 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
......@@ -5,35 +5,35 @@ 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
from .. import serializers
from ..models import LoginConfirmTicket
class LoginConfirmOrderViewSet(CommonApiMixin, viewsets.ModelViewSet):
serializer_class = serializers.LoginConfirmOrderSerializer
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 = LoginConfirmOrder.objects.all()\
queryset = LoginConfirmTicket.objects.all()\
.filter(assignees=self.request.user)
return queryset
class LoginConfirmOrderCreateActionApi(generics.CreateAPIView):
class LoginConfirmTicketsCreateActionApi(generics.CreateAPIView):
permission_classes = (IsValidUser,)
serializer_class = serializers.LoginConfirmOrderActionSerializer
serializer_class = serializers.LoginConfirmTicketActionSerializer
def get_order(self):
order_id = self.kwargs.get('pk')
queryset = LoginConfirmOrder.objects.all()\
def get_ticket(self):
ticket_id = self.kwargs.get('pk')
queryset = LoginConfirmTicket.objects.all()\
.filter(assignees=self.request.user)
order = get_object_or_404(queryset, id=order_id)
return order
ticket = get_object_or_404(queryset, id=ticket_id)
return ticket
def get_serializer_context(self):
context = super().get_serializer_context()
order = self.get_order()
context['order'] = order
ticket = self.get_ticket()
context['ticket'] = ticket
return context
from django.apps import AppConfig
class TicketsConfig(AppConfig):
name = 'tickets'
# 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)),
('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)),
('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')),
('action', models.CharField(blank=True, choices=[('approve', 'Approve'), ('reject', 'Reject')], default='', max_length=16)),
],
options={
'ordering': ('-date_created',),
'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={
......
# -*- coding: utf-8 -*-
#
from .base import *
from .login_confirm import *
# -*- 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']
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', )
__all__ = ['Ticket', 'Comment']
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', )
# -*- 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)
# -*- coding: utf-8 -*-
#
from .base import *
from .login_confirm import *
# -*- 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'
]
# -*- 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
......@@ -4,9 +4,9 @@ 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 .models import LoginConfirmTicket
from .utils import (
send_login_confirm_order_mail_to_assignees,
send_login_confirm_ticket_mail_to_assignees,
send_login_confirm_action_mail_to_user
)
......@@ -14,18 +14,18 @@ from .utils import (
logger = get_logger(__name__)
@receiver(m2m_changed, sender=LoginConfirmOrder.assignees.through)
def on_login_confirm_order_assignees_set(sender, instance=None, action=None,
@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 order create, send mail: {}'.format(instance.id))
logger.debug('New ticket create, send mail: {}'.format(instance.id))
assignees = model.objects.filter(pk__in=pk_set)
send_login_confirm_order_mail_to_assignees(instance, assignees)
send_login_confirm_ticket_mail_to_assignees(instance, assignees)
@receiver(post_save, sender=LoginConfirmOrder)
def on_login_confirm_order_status_change(sender, instance=None, created=False, **kwargs):
@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('Order changed, send mail: {}'.format(instance.id))
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 = {
......
......@@ -5,16 +5,19 @@ from rest_framework.routers import DefaultRouter
from .. import api
app_name = 'orders'
app_name = 'tickets'
router = DefaultRouter()
router.register('login-confirm-orders', api.LoginConfirmOrderViewSet, 'login-confirm-order')
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-order/<uuid:pk>/actions/',
api.LoginConfirmOrderCreateActionApi.as_view(),
name='login-confirm-order-create-action'
),
path('login-confirm-tickets/<uuid:pk>/actions/',
api.LoginConfirmTicketsCreateActionApi.as_view(),
name='login-confirm-ticket-create-action'
),
]
urlpatterns += router.urls
# -*- 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')
]
......@@ -9,54 +9,54 @@ from common.tasks import send_mail_async
logger = get_logger(__name__)
def send_login_confirm_order_mail_to_assignees(order, assignees):
def send_login_confirm_ticket_mail_to_assignees(ticket, assignees):
recipient_list = [user.email for user in assignees]
user = order.user
user = ticket.user
if not recipient_list:
logger.error("Order not has assignees: {}".format(order.id))
logger.error("Ticket not has assignees: {}".format(ticket.id))
return
subject = '{}: {}'.format(_("New order"), order.title)
detail_url = reverse('orders:login-confirm-order-detail',
kwargs={'pk': order.id}, external=True)
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 order</p>
<p>Your has a new ticket</p>
<div>
<b>Title:</b> {order.title}
<b>Title:</b> {ticket.title}
<br/>
<b>User:</b> {user}
<br/>
<b>Assignees:</b> {order.assignees_display}
<b>Assignees:</b> {ticket.assignees_display}
<br/>
<b>City:</b> {order.city}
<b>City:</b> {ticket.city}
<br/>
<b>IP:</b> {order.ip}
<b>IP:</b> {ticket.ip}
<br/>
<a href={url}>click here to review</a>
</div>
</div>
""").format(order=order, user=user, url=detail_url)
""").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(order):
if not order.user:
logger.error("Order not has user: {}".format(order.id))
def send_login_confirm_action_mail_to_user(ticket):
if not ticket.user:
logger.error("Ticket not has user: {}".format(ticket.id))
return
user = order.user
user = ticket.user
recipient_list = [user.email]
subject = '{}: {}'.format(_("Order has been reply"), order.title)
subject = '{}: {}'.format(_("Ticket has been reply"), ticket.title)
message = _("""
<div>
<p>Your order has been replay</p>
<p>Your ticket has been replay</p>
<div>
<b>Title:</b> {order.title}
<b>Title:</b> {ticket.title}
<br/>
<b>Assignee:</b> {order.assignee_display}
<b>Assignee:</b> {ticket.assignee_display}
<br/>
<b>Status:</b> {order.status_display}
<b>Status:</b> {ticket.status_display}
<br/>
</div>
</div>
""").format(order=order)
""").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()
};
......
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