From 08775551c21633fdc5dff96dc1fa43f8de1dc1ae Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 7 Nov 2019 18:06:58 +0800 Subject: [PATCH] [Update] Rename app --- apps/authentication/api/login_confirm.py | 43 +++++++---- apps/authentication/errors.py | 18 ++--- apps/authentication/mixins.py | 26 +++---- apps/authentication/models.py | 16 ++--- apps/authentication/urls/api_urls.py | 2 +- apps/authentication/views/login.py | 28 ++++---- apps/jumpserver/settings.py | 2 +- apps/jumpserver/urls.py | 4 +- apps/orders/api.py | 39 ---------- apps/orders/apps.py | 9 --- apps/orders/serializers.py | 72 ------------------- apps/orders/signals_handler.py | 31 -------- apps/orders/urls/api_urls.py | 20 ------ apps/orders/urls/views_urls.py | 11 --- apps/orders/utils.py | 62 ---------------- apps/static/css/jumpserver.css | 4 ++ apps/templates/_filter_dropdown.html | 5 ++ apps/templates/_nav.html | 6 +- apps/{orders => tickets}/__init__.py | 0 apps/{orders => tickets}/admin.py | 0 apps/tickets/api/__init__.py | 4 ++ apps/tickets/api/base.py | 21 ++++++ apps/tickets/api/login_confirm.py | 39 ++++++++++ apps/tickets/apps.py | 5 ++ .../migrations/0001_initial.py | 30 +++++--- .../migrations/__init__.py | 0 apps/tickets/models/__init__.py | 4 ++ .../models.py => tickets/models/base.py} | 49 +++++++------ apps/tickets/models/login_confirm.py | 20 ++++++ apps/tickets/serializers/__init__.py | 4 ++ apps/tickets/serializers/base.py | 33 +++++++++ apps/tickets/serializers/login_confirm.py | 32 +++++++++ apps/tickets/signals_handler.py | 31 ++++++++ .../tickets/login_confirm_ticket_detail.html} | 4 +- .../tickets/login_confirm_ticket_list.html} | 49 +++++++------ apps/{orders => tickets}/tests.py | 0 apps/{orders => tickets}/urls/__init__.py | 0 apps/tickets/urls/api_urls.py | 23 ++++++ apps/tickets/urls/views_urls.py | 11 +++ apps/tickets/utils.py | 62 ++++++++++++++++ apps/{orders => tickets}/views.py | 20 +++--- apps/users/templates/users/user_list.html | 4 +- 42 files changed, 463 insertions(+), 380 deletions(-) delete mode 100644 apps/orders/api.py delete mode 100644 apps/orders/apps.py delete mode 100644 apps/orders/serializers.py delete mode 100644 apps/orders/signals_handler.py delete mode 100644 apps/orders/urls/api_urls.py delete mode 100644 apps/orders/urls/views_urls.py delete mode 100644 apps/orders/utils.py rename apps/{orders => tickets}/__init__.py (100%) rename apps/{orders => tickets}/admin.py (100%) create mode 100644 apps/tickets/api/__init__.py create mode 100644 apps/tickets/api/base.py create mode 100644 apps/tickets/api/login_confirm.py create mode 100644 apps/tickets/apps.py rename apps/{orders => tickets}/migrations/0001_initial.py (68%) rename apps/{orders => tickets}/migrations/__init__.py (100%) create mode 100644 apps/tickets/models/__init__.py rename apps/{orders/models.py => tickets/models/base.py} (73%) create mode 100644 apps/tickets/models/login_confirm.py create mode 100644 apps/tickets/serializers/__init__.py create mode 100644 apps/tickets/serializers/base.py create mode 100644 apps/tickets/serializers/login_confirm.py create mode 100644 apps/tickets/signals_handler.py rename apps/{orders/templates/orders/login_confirm_order_detail.html => tickets/templates/tickets/login_confirm_ticket_detail.html} (98%) rename apps/{orders/templates/orders/login_confirm_order_list.html => tickets/templates/tickets/login_confirm_ticket_list.html} (72%) rename apps/{orders => tickets}/tests.py (100%) rename apps/{orders => tickets}/urls/__init__.py (100%) create mode 100644 apps/tickets/urls/api_urls.py create mode 100644 apps/tickets/urls/views_urls.py create mode 100644 apps/tickets/utils.py rename apps/{orders => tickets}/views.py (51%) diff --git a/apps/authentication/api/login_confirm.py b/apps/authentication/api/login_confirm.py index d2555eae4..62350e5c9 100644 --- a/apps/authentication/api/login_confirm.py +++ b/apps/authentication/api/login_confirm.py @@ -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" diff --git a/apps/authentication/errors.py b/apps/authentication/errors.py index 9a5957135..a6a73240f 100644 --- a/apps/authentication/errors.py +++ b/apps/authentication/errors.py @@ -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' diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index ec8965369..b33d0fdae 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -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: diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 4f0e06fb6..913e04f73 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -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) diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index 57e238192..a7e15053b 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -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//', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update') ] diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index d409996c2..2bfa3d894 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -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 {} confirm, You also can copy link to her/him
- 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 diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 6f36f529a..3eea3c78a 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -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', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index b9bdb697f..4d5e96671 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -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.*)', celery_flower_view, name='flower-view'), ] diff --git a/apps/orders/api.py b/apps/orders/api.py deleted file mode 100644 index aec04ad46..000000000 --- a/apps/orders/api.py +++ /dev/null @@ -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 diff --git a/apps/orders/apps.py b/apps/orders/apps.py deleted file mode 100644 index 3e58af6ea..000000000 --- a/apps/orders/apps.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.apps import AppConfig - - -class OrdersConfig(AppConfig): - name = 'orders' - - def ready(self): - from . import signals_handler - return super().ready() diff --git a/apps/orders/serializers.py b/apps/orders/serializers.py deleted file mode 100644 index d74e33208..000000000 --- a/apps/orders/serializers.py +++ /dev/null @@ -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 diff --git a/apps/orders/signals_handler.py b/apps/orders/signals_handler.py deleted file mode 100644 index 9c25907f7..000000000 --- a/apps/orders/signals_handler.py +++ /dev/null @@ -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) diff --git a/apps/orders/urls/api_urls.py b/apps/orders/urls/api_urls.py deleted file mode 100644 index 81828d3fe..000000000 --- a/apps/orders/urls/api_urls.py +++ /dev/null @@ -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//actions/', - api.LoginConfirmOrderCreateActionApi.as_view(), - name='login-confirm-order-create-action' - ), -] - -urlpatterns += router.urls diff --git a/apps/orders/urls/views_urls.py b/apps/orders/urls/views_urls.py deleted file mode 100644 index f4fe0ba05..000000000 --- a/apps/orders/urls/views_urls.py +++ /dev/null @@ -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//', views.LoginConfirmOrderDetailView.as_view(), name='login-confirm-order-detail') -] diff --git a/apps/orders/utils.py b/apps/orders/utils.py deleted file mode 100644 index 6fd3d965d..000000000 --- a/apps/orders/utils.py +++ /dev/null @@ -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 = _(""" -
-

Your has a new order

-
- Title: {order.title} -
- User: {user} -
- Assignees: {order.assignees_display} -
- City: {order.city} -
- IP: {order.ip} -
- click here to review -
-
- """).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 = _(""" -
-

Your order has been replay

-
- Title: {order.title} -
- Assignee: {order.assignee_display} -
- Status: {order.status_display} -
-
-
- """).format(order=order) - send_mail_async.delay(subject, message, recipient_list, html_message=message) diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index ffcb87d33..02b59aae6 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -554,3 +554,7 @@ span.select2-selection__placeholder { height: 22px; max-width: inherit; } + +table.table-striped.table-bordered { + width: 100% !important; +} diff --git a/apps/templates/_filter_dropdown.html b/apps/templates/_filter_dropdown.html index e99ca79ef..9237df0d5 100644 --- a/apps/templates/_filter_dropdown.html +++ b/apps/templates/_filter_dropdown.html @@ -1,3 +1,8 @@ + diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index d23f549ca..2aecd2fd6 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -122,12 +122,12 @@ {% endif %} {% if request.user.can_admin_current_org and LOGIN_CONFIRM_ENABLE %} -
  • +
  • - {% trans 'Orders' %} + {% trans 'Tickets' %}
  • {% endif %} diff --git a/apps/orders/__init__.py b/apps/tickets/__init__.py similarity index 100% rename from apps/orders/__init__.py rename to apps/tickets/__init__.py diff --git a/apps/orders/admin.py b/apps/tickets/admin.py similarity index 100% rename from apps/orders/admin.py rename to apps/tickets/admin.py diff --git a/apps/tickets/api/__init__.py b/apps/tickets/api/__init__.py new file mode 100644 index 000000000..99396f9f3 --- /dev/null +++ b/apps/tickets/api/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# +from .base import * +from .login_confirm import * diff --git a/apps/tickets/api/base.py b/apps/tickets/api/base.py new file mode 100644 index 000000000..a029d8fab --- /dev/null +++ b/apps/tickets/api/base.py @@ -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 diff --git a/apps/tickets/api/login_confirm.py b/apps/tickets/api/login_confirm.py new file mode 100644 index 000000000..c990ee339 --- /dev/null +++ b/apps/tickets/api/login_confirm.py @@ -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 diff --git a/apps/tickets/apps.py b/apps/tickets/apps.py new file mode 100644 index 000000000..3ea742ac3 --- /dev/null +++ b/apps/tickets/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TicketsConfig(AppConfig): + name = 'tickets' diff --git a/apps/orders/migrations/0001_initial.py b/apps/tickets/migrations/0001_initial.py similarity index 68% rename from apps/orders/migrations/0001_initial.py rename to apps/tickets/migrations/0001_initial.py index 9b1099965..e86fc44ad 100644 --- a/apps/orders/migrations/0001_initial.py +++ b/apps/tickets/migrations/0001_initial.py @@ -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={ diff --git a/apps/orders/migrations/__init__.py b/apps/tickets/migrations/__init__.py similarity index 100% rename from apps/orders/migrations/__init__.py rename to apps/tickets/migrations/__init__.py diff --git a/apps/tickets/models/__init__.py b/apps/tickets/models/__init__.py new file mode 100644 index 000000000..99396f9f3 --- /dev/null +++ b/apps/tickets/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# +from .base import * +from .login_confirm import * diff --git a/apps/orders/models.py b/apps/tickets/models/base.py similarity index 73% rename from apps/orders/models.py rename to apps/tickets/models/base.py index 3ed12c8c8..3fcb702cf 100644 --- a/apps/orders/models.py +++ b/apps/tickets/models/base.py @@ -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', ) + + diff --git a/apps/tickets/models/login_confirm.py b/apps/tickets/models/login_confirm.py new file mode 100644 index 000000000..0781d20be --- /dev/null +++ b/apps/tickets/models/login_confirm.py @@ -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) diff --git a/apps/tickets/serializers/__init__.py b/apps/tickets/serializers/__init__.py new file mode 100644 index 000000000..99396f9f3 --- /dev/null +++ b/apps/tickets/serializers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# +from .base import * +from .login_confirm import * diff --git a/apps/tickets/serializers/base.py b/apps/tickets/serializers/base.py new file mode 100644 index 000000000..eb4040f59 --- /dev/null +++ b/apps/tickets/serializers/base.py @@ -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' + ] diff --git a/apps/tickets/serializers/login_confirm.py b/apps/tickets/serializers/login_confirm.py new file mode 100644 index 000000000..4649294ec --- /dev/null +++ b/apps/tickets/serializers/login_confirm.py @@ -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 diff --git a/apps/tickets/signals_handler.py b/apps/tickets/signals_handler.py new file mode 100644 index 000000000..a1b9dbbec --- /dev/null +++ b/apps/tickets/signals_handler.py @@ -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) diff --git a/apps/orders/templates/orders/login_confirm_order_detail.html b/apps/tickets/templates/tickets/login_confirm_ticket_detail.html similarity index 98% rename from apps/orders/templates/orders/login_confirm_order_detail.html rename to apps/tickets/templates/tickets/login_confirm_ticket_detail.html index 55a86e009..2b43da756 100644 --- a/apps/orders/templates/orders/login_confirm_order_detail.html +++ b/apps/tickets/templates/tickets/login_confirm_ticket_detail.html @@ -112,9 +112,9 @@ {% endblock %} {% block custom_foot_js %}