feat: Implement MFA authentication, add ride statistics model, and update various services, APIs, and tests across the application.

This commit is contained in:
pacnpal
2025-12-28 17:32:53 -05:00
parent aa56c46c27
commit c95f99ca10
452 changed files with 7948 additions and 6073 deletions

View File

@@ -2,32 +2,34 @@
API viewsets for the ride ranking system.
"""
from typing import TYPE_CHECKING, Any, Type, cast
from typing import TYPE_CHECKING, Any, cast
from django.db.models import Q, QuerySet
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.filters import OrderingFilter
from rest_framework.permissions import IsAuthenticatedOrReadOnly, AllowAny
from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import BaseSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_framework.views import APIView
from rest_framework.viewsets import ReadOnlyModelViewSet
if TYPE_CHECKING:
pass
# Import models inside methods to avoid Django initialization issues
import contextlib
from .serializers_rankings import (
RideRankingSerializer,
RideRankingDetailSerializer,
RankingSnapshotSerializer,
RankingStatsSerializer,
RideRankingDetailSerializer,
RideRankingSerializer,
)
@@ -127,10 +129,8 @@ class RideRankingViewSet(ReadOnlyModelViewSet):
# Filter by minimum mutual riders
min_riders = request.query_params.get("min_riders")
if min_riders:
try:
with contextlib.suppress(ValueError):
queryset = queryset.filter(mutual_riders_count__gte=int(min_riders))
except ValueError:
pass
# Filter by park
park_slug = request.query_params.get("park")
@@ -142,12 +142,12 @@ class RideRankingViewSet(ReadOnlyModelViewSet):
def get_serializer_class(self) -> Any: # type: ignore[override]
"""Use different serializers for list vs detail."""
if self.action == "retrieve":
return cast(Type[BaseSerializer], RideRankingDetailSerializer)
return cast(type[BaseSerializer], RideRankingDetailSerializer)
elif self.action == "history":
return cast(Type[BaseSerializer], RankingSnapshotSerializer)
return cast(type[BaseSerializer], RankingSnapshotSerializer)
elif self.action == "statistics":
return cast(Type[BaseSerializer], RankingStatsSerializer)
return cast(Type[BaseSerializer], RideRankingSerializer)
return cast(type[BaseSerializer], RankingStatsSerializer)
return cast(type[BaseSerializer], RideRankingSerializer)
@action(detail=True, methods=["get"])
def history(self, request, ride_slug=None):
@@ -167,7 +167,7 @@ class RideRankingViewSet(ReadOnlyModelViewSet):
@action(detail=False, methods=["get"])
def statistics(self, request):
"""Get overall ranking system statistics."""
from apps.rides.models import RideRanking, RidePairComparison, RankingSnapshot
from apps.rides.models import RankingSnapshot, RidePairComparison, RideRanking
total_rankings = RideRanking.objects.count()
total_comparisons = RidePairComparison.objects.count()