API Tokens and Filters
This commit is contained in:
parent
88942a84ba
commit
307ed35ee9
@ -1,12 +1,14 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
from rest_framework.authtoken.views import obtain_auth_token
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
|
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
|
||||||
|
path("api-token-auth/", obtain_auth_token),
|
||||||
|
|
||||||
path("tickets/", views.TicketListApiView.as_view(), name="tickets"),
|
path("tickets/", views.TicketListApiView.as_view(), name="tickets"),
|
||||||
path("filter-counts/", views.FilterCountApiView.as_view(), name="filter-counts"),
|
path("filter-counts/", views.FilterCountApiView.as_view(), name="filter-counts"),
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from operator import or_, and_
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from django.db.models import Q
|
from django_filters import rest_framework as rest_filters
|
||||||
|
from django.db.models import Q, Count
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
@ -11,11 +14,12 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status, permissions
|
from rest_framework import status, permissions, filters, generics
|
||||||
from rest_framework.pagination import PageNumberPagination
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
|
||||||
|
|
||||||
from apps.home.models import Ticket, TicketPriority, TicketTag
|
from apps.home.models import Ticket, TicketPriority, TicketTag
|
||||||
from apps.authentication.models import Department
|
from apps.authentication.models import Department, User
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
TicketSerializer, TicketTagSerializer, TicketPrioritySerializer,
|
TicketSerializer, TicketTagSerializer, TicketPrioritySerializer,
|
||||||
UserSerializer
|
UserSerializer
|
||||||
@ -37,101 +41,162 @@ class TicketPaginiation(PageNumberPagination):
|
|||||||
max_page_size = 100
|
max_page_size = 100
|
||||||
|
|
||||||
|
|
||||||
class TicketListApiView(APIView):
|
class TicketListApiView(generics.ListAPIView):
|
||||||
|
authentication_classes = [SessionAuthentication, TokenAuthentication]
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
pagination_class = TicketPaginiation
|
|
||||||
|
|
||||||
ALLOWED_FILTERS = (
|
pagination_class = TicketPaginiation
|
||||||
"uuid__in", "priority", "tags__in", "author__department", "search"
|
serializer_class = TicketSerializer
|
||||||
)
|
|
||||||
MATCHER_MAP = {
|
queryset = Ticket.objects.all()
|
||||||
"contains": lambda k, v: {k: v[0]},
|
|
||||||
"in": lambda k, v: {k: v}
|
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
|
||||||
}
|
filterset_fields = ["priority", "tags", "author"]
|
||||||
|
search_fields = ["author__forename", "author__surname", "title", "description"]
|
||||||
|
ordering_fields = ["create_timestamp", "edit_timestamp"]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
tag_uuids = self.request.query_params.getlist("tags", [])
|
||||||
|
queryset = self.queryset
|
||||||
|
|
||||||
|
log.debug("tag uuids %s", tag_uuids)
|
||||||
|
|
||||||
|
for uuid in tag_uuids:
|
||||||
|
if not uuid: continue
|
||||||
|
log.debug("uuid %s", uuid)
|
||||||
|
queryset = queryset.filter(tags__uuid__in=[uuid])
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
# ALLOWED_FILTERS = (
|
||||||
|
# "uuid__in", "priority", "tags__in", "author__department", "search"
|
||||||
|
# )
|
||||||
|
# MATCHER_MAP = {
|
||||||
|
# "contains": lambda k, v: {k: v[0]},
|
||||||
|
# "in": lambda k, v: {k: v}
|
||||||
|
# }
|
||||||
|
|
||||||
# @method_decorator(cache_page(60 * 5))
|
# @method_decorator(cache_page(60 * 5))
|
||||||
def get(self, request: HttpRequest) -> Response:
|
# def get(self, request: HttpRequest) -> Response:
|
||||||
"""Returns a response containing tickets matching the given filters."""
|
# """Returns a response containing tickets matching the given filters."""
|
||||||
|
|
||||||
try:
|
# try:
|
||||||
return self._get_tickets(request)
|
|
||||||
|
|
||||||
except KeyError as error:
|
# kwargs = self._parse_querystring(request)
|
||||||
return respond_error(error, status.HTTP_400_BAD_REQUEST)
|
# data = self._new_get_tickets(**kwargs)
|
||||||
|
# return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
except ValidationError as error:
|
# except KeyError as error:
|
||||||
return respond_error(error, status.HTTP_400_BAD_REQUEST)
|
# return respond_error(error, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def post(self, request):
|
# except ValidationError as error:
|
||||||
"""Create a new ticket"""
|
# return respond_error(error, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
serializer = TicketSerializer(data={
|
# def post(self, request):
|
||||||
|
# """Create a new ticket"""
|
||||||
|
|
||||||
})
|
# serializer = TicketSerializer(data={
|
||||||
if not serializer.is_valid():
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
serializer.save()
|
# })
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
# if not serializer.is_valid():
|
||||||
|
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def _get_tickets(self, request: HttpRequest) -> Response:
|
# serializer.save()
|
||||||
"""Returns a response containing tickets matching the given filters.
|
# return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
Parameters
|
# def _parse_querystring(self, request: HttpRequest) -> dict:
|
||||||
----------
|
# """"""
|
||||||
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, values in request.GET.lists():
|
# def _new_get_tickets(self, **filters) -> dict:
|
||||||
key = key.removesuffix("[]")
|
# """"""
|
||||||
|
|
||||||
if key not in self.ALLOWED_FILTERS:
|
|
||||||
raise KeyError(key)
|
|
||||||
|
|
||||||
if not key.endswith("__in"):
|
|
||||||
values = values[0]
|
|
||||||
|
|
||||||
if key == "search":
|
# def _get_tickets(self, request: HttpRequest) -> dict:
|
||||||
queryset = queryset.filter(
|
# """Returns a response containing tickets matching the given filters.
|
||||||
Q(**{"title__contains": values}) |
|
|
||||||
Q(**{"description__contains": values}) |
|
|
||||||
Q(**{"author__forename__contains": values}) |
|
|
||||||
Q(**{"author__surname__contains": values})
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if "all" in values:
|
# Parameters
|
||||||
continue
|
# ----------
|
||||||
|
# request : HttpRequest
|
||||||
|
# The request containing the filters.
|
||||||
|
|
||||||
if not key.endswith("__in"):
|
# Returns
|
||||||
queryset = queryset.filter(Q(**{key: values}))
|
# -------
|
||||||
continue
|
# Response
|
||||||
|
# A response containing the tickets data.
|
||||||
|
|
||||||
for value in values:
|
# Raises
|
||||||
filter_kwargs = {key: [value]}
|
# ------
|
||||||
queryset = queryset.filter(Q(**filter_kwargs))
|
# KeyError
|
||||||
|
# If any given filter key isn't in `self.ALLOWED_FILTERS`.
|
||||||
|
# ValidationError
|
||||||
|
# If any ValidationErrors are raised by Django while applying filters.
|
||||||
|
# """
|
||||||
|
|
||||||
tickets = queryset.order_by("-edit_timestamp")
|
# queryset = Ticket.objects.all()
|
||||||
serializer = TicketSerializer(tickets, many=True)
|
|
||||||
response_data = serializer.data
|
|
||||||
|
|
||||||
log.debug("Query successful showing %s results", tickets.count())
|
# for key, values in request.GET.lists():
|
||||||
return Response(response_data, status=status.HTTP_200_OK)
|
# key = key.removesuffix("[]")
|
||||||
|
|
||||||
|
# # format is not intended for this function
|
||||||
|
# if key == "format":
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# if key not in self.ALLOWED_FILTERS:
|
||||||
|
# raise KeyError(key)
|
||||||
|
|
||||||
|
# if not key.endswith("__in"):
|
||||||
|
# values = values[0]
|
||||||
|
|
||||||
|
# # search filter is a user text input
|
||||||
|
# if key == "search":
|
||||||
|
|
||||||
|
# clean_value = values.split()
|
||||||
|
# log.debug("clean values %s", clean_value)
|
||||||
|
# or_filters = [
|
||||||
|
# Q(**{"title__contains": clean_value}),
|
||||||
|
# Q(**{"description__contains": clean_value}),
|
||||||
|
# Q(**{"author__forename__contains": clean_value}),
|
||||||
|
# Q(**{"author__surname__contains": clean_value})
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# name_values = values.split(" ")
|
||||||
|
# if len(name_values) == 2:
|
||||||
|
|
||||||
|
# log.debug("looking for author %s %s", *name_values)
|
||||||
|
# users = User.objects.filter(Q(forename__contains=name_values[0]) | Q(surname__contains=name_values[1]))
|
||||||
|
# log.debug("found %s authors under that name", users.count())
|
||||||
|
# for user in users:
|
||||||
|
# log.debug("%s %s", user.fullname.lower(), values.lower())
|
||||||
|
# if user.fullname.lower() == values.lower():
|
||||||
|
# log.debug("adding author to filter")
|
||||||
|
# or_filters.append(Q(**{"author": user}))
|
||||||
|
|
||||||
|
# queryset = queryset.filter(reduce(or_, or_filters))
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# if "all" in values:
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# # if it doesn't end with "__in", assume we are looking for an exact match
|
||||||
|
# if not key.endswith("__in"):
|
||||||
|
# queryset = queryset.filter(Q(**{key: values}))
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# # Chain the filters to create an AND query (Q() & Q() doesnt work?)
|
||||||
|
# for value in values:
|
||||||
|
# filter_kwargs = {key: [value]}
|
||||||
|
# queryset = queryset.filter(Q(**filter_kwargs))
|
||||||
|
|
||||||
|
# tickets = queryset.order_by("-edit_timestamp")
|
||||||
|
# serializer = TicketSerializer(tickets, many=True)
|
||||||
|
|
||||||
|
# log.debug("Query successful showing %s results", tickets.count())
|
||||||
|
# return serializer.data
|
||||||
|
|
||||||
|
|
||||||
class FilterCountApiView(APIView):
|
class FilterCountApiView(APIView):
|
||||||
|
@ -15,6 +15,7 @@ env = environ.Env(
|
|||||||
# BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
# BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
BASE_DIR = Path(__file__).parent.parent
|
BASE_DIR = Path(__file__).parent.parent
|
||||||
CORE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
CORE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
print(BASE_DIR, CORE_DIR)
|
||||||
|
|
||||||
# Take environment variables from .env file
|
# Take environment variables from .env file
|
||||||
environ.Env.read_env(BASE_DIR / ".env")
|
environ.Env.read_env(BASE_DIR / ".env")
|
||||||
@ -42,6 +43,8 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
"rest_framework.authtoken",
|
||||||
|
"django_filters",
|
||||||
'apps.api',
|
'apps.api',
|
||||||
'apps.home',
|
'apps.home',
|
||||||
'apps.authentication'
|
'apps.authentication'
|
||||||
|
@ -4,6 +4,8 @@ bleach==6.1.0
|
|||||||
dj-database-url==0.5.0
|
dj-database-url==0.5.0
|
||||||
Django==3.2.16
|
Django==3.2.16
|
||||||
django-environ==0.8.1
|
django-environ==0.8.1
|
||||||
|
django-filter==23.5
|
||||||
|
django-rest-framework==0.1.0
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
pillow==10.2.0
|
pillow==10.2.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user