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

@@ -1,41 +1,41 @@
from .querysets import get_base_park_queryset
from apps.core.mixins import HTMXFilterableMixin
from .models.location import ParkLocation
from .models.media import ParkPhoto
from apps.moderation.mixins import (
EditSubmissionMixin,
PhotoSubmissionMixin,
HistoryMixin,
)
from apps.core.views.views import SlugRedirectMixin
from .filters import ParkFilter
from .forms import ParkForm
from .models import Park, ParkArea, ParkReview as Review
from .services import ParkFilterService, ParkService
from django.http import (
HttpResponseRedirect,
HttpResponse,
HttpRequest,
JsonResponse,
)
from django.core.exceptions import ObjectDoesNotExist
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import QuerySet
from django.urls import reverse
from django.shortcuts import get_object_or_404, render
from decimal import InvalidOperation
from django.views.generic import DetailView, ListView, CreateView, UpdateView
import requests
from decimal import Decimal, ROUND_DOWN
from typing import Any, Optional, cast, Literal, Dict
from django.views.decorators.http import require_POST
from django.template.loader import render_to_string
import contextlib
import json
import logging
from decimal import ROUND_DOWN, Decimal, InvalidOperation
from typing import Any, Literal, cast
from apps.core.logging import log_exception, log_business_event
import requests
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import QuerySet
from django.http import (
HttpRequest,
HttpResponse,
HttpResponseRedirect,
JsonResponse,
)
from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
from django.urls import reverse
from django.views.decorators.http import require_POST
from django.views.generic import CreateView, DetailView, ListView, UpdateView
from apps.core.logging import log_business_event, log_exception
from apps.core.mixins import HTMXFilterableMixin
from apps.core.views.views import SlugRedirectMixin
from apps.moderation.mixins import (
EditSubmissionMixin,
HistoryMixin,
PhotoSubmissionMixin,
)
from .filters import ParkFilter
from .forms import ParkForm
from .models import Park, ParkArea
from .models import ParkReview as Review
from .querysets import get_base_park_queryset
from .services import ParkFilterService, ParkService
logger = logging.getLogger(__name__)
@@ -364,7 +364,7 @@ class ParkListView(HTMXFilterableMixin, ListView):
"search_query": self.request.GET.get("search", ""),
}
def _get_clean_filter_params(self) -> Dict[str, Any]:
def _get_clean_filter_params(self) -> dict[str, Any]:
"""Extract and clean filter parameters from request."""
filter_params = {}
@@ -391,7 +391,7 @@ class ParkListView(HTMXFilterableMixin, ListView):
return {k: v for k, v in filter_params.items() if v is not None}
def _clean_filter_value(self, param: str, value: str) -> Optional[Any]:
def _clean_filter_value(self, param: str, value: str) -> Any | None:
"""Clean and validate a single filter value."""
if param in ("has_coasters", "big_parks_only"):
# Boolean filters
@@ -413,7 +413,7 @@ class ParkListView(HTMXFilterableMixin, ListView):
# String filters
return value.strip()
def _build_filter_query_string(self, filter_params: Dict[str, Any]) -> str:
def _build_filter_query_string(self, filter_params: dict[str, Any]) -> str:
"""Build query string from filter parameters."""
from urllib.parse import urlencode
@@ -428,8 +428,8 @@ class ParkListView(HTMXFilterableMixin, ListView):
return urlencode(url_params)
def _get_pagination_urls(
self, page_obj, filter_params: Dict[str, Any]
) -> Dict[str, str]:
self, page_obj, filter_params: dict[str, Any]
) -> dict[str, str]:
"""Generate pagination URLs that preserve filter state."""
base_query = self._build_filter_query_string(filter_params)
@@ -841,10 +841,8 @@ def htmx_save_trip(request: HttpRequest) -> HttpResponse:
trip = Trip.objects.create(owner=request.user, name=name)
# attempt to associate parks if the Trip model supports it
try:
with contextlib.suppress(Exception):
trip.parks.set([p.id for p in parks])
except Exception:
pass
trips = list(
Trip.objects.filter(owner=request.user).order_by("-created_at")[:10]
)
@@ -1133,7 +1131,7 @@ class ParkDetailView(
template_name = "parks/park_detail.html"
context_object_name = "park"
def get_object(self, queryset: Optional[QuerySet[Park]] = None) -> Park:
def get_object(self, queryset: QuerySet[Park] | None = None) -> Park:
if queryset is None:
queryset = self.get_queryset()
slug = self.kwargs.get(self.slug_url_kwarg)
@@ -1184,7 +1182,7 @@ class ParkAreaDetailView(
context_object_name = "area"
slug_url_kwarg = "area_slug"
def get_object(self, queryset: Optional[QuerySet[ParkArea]] = None) -> ParkArea:
def get_object(self, queryset: QuerySet[ParkArea] | None = None) -> ParkArea:
if queryset is None:
queryset = self.get_queryset()
park_slug = self.kwargs.get("park_slug")
@@ -1217,9 +1215,10 @@ class OperatorListView(ListView):
def get_queryset(self):
"""Get companies that are operators with optimized query"""
from .models.companies import Company
from django.db.models import Count
from .models.companies import Company
return (
Company.objects.filter(roles__contains=["OPERATOR"])
.annotate(park_count=Count("operated_parks"))