From a708a8b151fd9d4fce3aada45cf5043552379592 Mon Sep 17 00:00:00 2001 From: corbz Date: Sun, 14 Jan 2024 01:50:22 +0000 Subject: [PATCH] Working on Ticket API Improved filters and added TicketCount API View. --- apps/api/urls.py | 3 +- apps/api/views.py | 126 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/apps/api/urls.py b/apps/api/urls.py index 6a1a1f3..d54b9bf 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -2,8 +2,9 @@ from django.urls import path -from .views import TicketListApiView +from .views import TicketListApiView, FilterCountApiView urlpatterns = [ path("ticket/", TicketListApiView.as_view(), name="ticket"), + path("filter-counts/", FilterCountApiView.as_view(), name="filter-counts"), ] \ No newline at end of file diff --git a/apps/api/views.py b/apps/api/views.py index cd08ad4..6a624fe 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -1,37 +1,60 @@ # -*- encoding: utf-8 -*- -import json +import logging +from typing import Any -from django.conf import settings +from django.db.models import Q +from django.http import HttpRequest +from django.core.cache import cache +from django.views.decorators.cache import cache_page +from django.utils.decorators import method_decorator +from django.core.exceptions import ValidationError from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status, permissions +from rest_framework.pagination import PageNumberPagination from apps.home.models import Ticket, TicketPriority, TicketTag +from apps.authentication.models import Department from .serializers import ( TicketSerializer, TicketTagSerializer, TicketPrioritySerializer, UserSerializer ) +log = logging.getLogger(__name__) + +def respond_error(error: Exception, status: Any) -> Response: + logging.error(error) + return Response({ + "detail": str(error), + "error": error.__class__.__name__ + }, status=status) + + +class TicketPaginiation(PageNumberPagination): + page_size = 25 + page_size_query_param = "page_size" + max_page_size = 100 + class TicketListApiView(APIView): permission_classes = [permissions.IsAuthenticated] + pagination_class = TicketPaginiation - def get(self, request): - """Return all tickets.""" + ALLOWED_FILTERS = ("uuid__in", "priority__in", "tags__in", "author__department__in") - filters = json.loads(request.GET.get("filters", "{}")) - queryset = Ticket.objects.all() + # @method_decorator(cache_page(60 * 5)) + def get(self, request: HttpRequest) -> Response: + """Returns a response containing tickets matching the given filters.""" - for key, values in filters.items(): - - for value in values: - if value == "all": continue - queryset = queryset.filter(**{key: [value]}) + try: + return self._get_tickets(request) - tickets = queryset.order_by("-create_timestamp") - serializer = TicketSerializer(tickets, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) + except KeyError as error: + return respond_error(error, status.HTTP_400_BAD_REQUEST) + + except ValidationError as error: + return respond_error(error, status.HTTP_400_BAD_REQUEST) def post(self, request): """Create a new ticket""" @@ -40,7 +63,80 @@ class TicketListApiView(APIView): }) if not serializer.is_valid(): - return Response(serlializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) + + + def _get_tickets(self, request: HttpRequest) -> Response: + """Returns a response containing tickets matching the given filters. + + Parameters + ---------- + request : HttpRequest + The request containing the filters. + + Returns + ------- + Response + A response containing the tickets data. + + Raises + ------ + KeyError + If any given filter key isn't in `self.ALLOWED_FILTERS`. + ValidationError + If any ValidationErrors are raised by Django while applying filters. + """ + + queryset = Ticket.objects.all() + + for key in request.GET.keys(): + values = request.GET.getlist(key) if key.endswith("[]") else [request.GET.get(key)] + key = key.removesuffix("[]") + + if key not in self.ALLOWED_FILTERS: + raise KeyError(key) + + for value in values: + if value == "all": continue + queryset = queryset.filter(Q(**{key: [value]})) + + tickets = queryset.order_by("-create_timestamp") + serializer = TicketSerializer(tickets, many=True) + response_data = serializer.data + + log.debug("Query successful showing %s results", tickets.count()) + return Response(response_data, status=status.HTTP_200_OK) + + +class FilterCountApiView(APIView): + permission_classes = [permissions.IsAuthenticated] + + @method_decorator(cache_page(60 * 5)) + def get(self, request): + + priorities = TicketPriority.objects.all() + tags = TicketTag.objects.all() + departments = Department.objects.all() + + tickets = Ticket.objects.all() + + data = { + "priority_counts": {}, + "tag_counts": {}, + "department_counts": {}, + "ticket_count": tickets.count() + } + + for priority in priorities: + data["priority_counts"][str(priority.uuid)] = tickets.filter(priority=priority).count() + + for tag in tags: + data["tag_counts"][str(tag.uuid)] = tickets.filter(tags__in=[tag]).count() + + for department in departments: + data["department_counts"][str(department.uuid)] = tickets.filter(author__department=department).count() + + return Response(data, status=status.HTTP_200_OK) \ No newline at end of file