mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:11:08 -05:00
feat: Add PrimeProgress, PrimeSelect, and PrimeSkeleton components with customizable styles and props
- Implemented PrimeProgress component with support for labels, helper text, and various styles (size, variant, color). - Created PrimeSelect component with dropdown functionality, custom templates, and validation states. - Developed PrimeSkeleton component for loading placeholders with different shapes and animations. - Updated index.ts to export new components for easy import. - Enhanced PrimeVueTest.vue to include tests for new components and their functionalities. - Introduced a custom ThrillWiki theme for PrimeVue with tailored color schemes and component styles. - Added ambient type declarations for various components to improve TypeScript support.
This commit is contained in:
@@ -5,7 +5,10 @@ This module contains all authentication-related API endpoints including
|
||||
login, signup, logout, password management, and social authentication.
|
||||
"""
|
||||
|
||||
from django.contrib.auth import authenticate, login, logout, get_user_model
|
||||
# type: ignore[misc,attr-defined,arg-type,call-arg,index,assignment]
|
||||
|
||||
from typing import TYPE_CHECKING, Type, Any
|
||||
from django.contrib.auth import login, logout, get_user_model
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.exceptions import ValidationError
|
||||
from rest_framework import status
|
||||
@@ -15,56 +18,21 @@ from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
# Import serializers inside methods to avoid Django initialization issues
|
||||
|
||||
|
||||
# Placeholder classes for schema decorators
|
||||
class LoginInputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class LoginOutputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class SignupInputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class SignupOutputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class LogoutOutputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class UserOutputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class PasswordResetInputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class PasswordResetOutputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class PasswordChangeInputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class PasswordChangeOutputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class SocialProviderOutputSerializer:
|
||||
pass
|
||||
|
||||
|
||||
class AuthStatusOutputSerializer:
|
||||
pass
|
||||
# Import serializers from the auth serializers module
|
||||
from ..serializers.auth import (
|
||||
LoginInputSerializer,
|
||||
LoginOutputSerializer,
|
||||
SignupInputSerializer,
|
||||
SignupOutputSerializer,
|
||||
LogoutOutputSerializer,
|
||||
UserOutputSerializer,
|
||||
PasswordResetInputSerializer,
|
||||
PasswordResetOutputSerializer,
|
||||
PasswordChangeInputSerializer,
|
||||
PasswordChangeOutputSerializer,
|
||||
SocialProviderOutputSerializer,
|
||||
AuthStatusOutputSerializer,
|
||||
)
|
||||
|
||||
|
||||
# Handle optional dependencies with fallback classes
|
||||
@@ -73,7 +41,8 @@ class AuthStatusOutputSerializer:
|
||||
class FallbackTurnstileMixin:
|
||||
"""Fallback mixin if TurnstileMixin is not available."""
|
||||
|
||||
def validate_turnstile(self, request):
|
||||
def validate_turnstile(self, request: Any) -> None:
|
||||
"""Fallback validation method that does nothing."""
|
||||
pass
|
||||
|
||||
|
||||
@@ -83,6 +52,14 @@ try:
|
||||
except ImportError:
|
||||
TurnstileMixin = FallbackTurnstileMixin
|
||||
|
||||
# Type hint for the mixin
|
||||
if TYPE_CHECKING:
|
||||
from typing import Union
|
||||
|
||||
TurnstileMixinType = Union[Type[FallbackTurnstileMixin], Any]
|
||||
else:
|
||||
TurnstileMixinType = TurnstileMixin
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
|
||||
@@ -98,7 +75,7 @@ UserModel = get_user_model()
|
||||
tags=["Authentication"],
|
||||
),
|
||||
)
|
||||
class LoginAPIView(TurnstileMixin, APIView):
|
||||
class LoginAPIView(TurnstileMixin, APIView): # type: ignore[misc]
|
||||
"""API endpoint for user login."""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
@@ -106,90 +83,33 @@ class LoginAPIView(TurnstileMixin, APIView):
|
||||
serializer_class = LoginInputSerializer
|
||||
|
||||
def post(self, request: Request) -> Response:
|
||||
from ..serializers import LoginInputSerializer, LoginOutputSerializer
|
||||
|
||||
try:
|
||||
# Validate Turnstile if configured
|
||||
self.validate_turnstile(request)
|
||||
except ValidationError as e:
|
||||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
serializer = LoginInputSerializer(data=request.data)
|
||||
serializer = LoginInputSerializer(
|
||||
data=request.data, context={"request": request}
|
||||
)
|
||||
if serializer.is_valid():
|
||||
# type: ignore[index]
|
||||
email_or_username = serializer.validated_data["username"]
|
||||
password = serializer.validated_data["password"] # type: ignore[index]
|
||||
# The serializer handles authentication validation
|
||||
user = serializer.validated_data["user"] # type: ignore[index]
|
||||
|
||||
# Optimized user lookup: single query using Q objects
|
||||
from django.db.models import Q
|
||||
from django.contrib.auth import get_user_model
|
||||
login(request._request, user) # type: ignore[attr-defined]
|
||||
# Optimized token creation - get_or_create is atomic
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
User = get_user_model()
|
||||
user = None
|
||||
token, created = Token.objects.get_or_create(user=user)
|
||||
|
||||
# Single query to find user by email OR username
|
||||
try:
|
||||
if "@" in email_or_username:
|
||||
# Email-like input: try email first, then username as fallback
|
||||
user_obj = (
|
||||
User.objects.select_related()
|
||||
.filter(
|
||||
Q(email=email_or_username) | Q(username=email_or_username)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
else:
|
||||
# Username-like input: try username first, then email as fallback
|
||||
user_obj = (
|
||||
User.objects.select_related()
|
||||
.filter(
|
||||
Q(username=email_or_username) | Q(email=email_or_username)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if user_obj:
|
||||
user = authenticate(
|
||||
# type: ignore[attr-defined]
|
||||
request._request,
|
||||
username=user_obj.username,
|
||||
password=password,
|
||||
)
|
||||
except Exception:
|
||||
# Fallback to original behavior
|
||||
user = authenticate(
|
||||
# type: ignore[attr-defined]
|
||||
request._request,
|
||||
username=email_or_username,
|
||||
password=password,
|
||||
)
|
||||
|
||||
if user:
|
||||
if user.is_active:
|
||||
login(request._request, user) # type: ignore[attr-defined]
|
||||
# Optimized token creation - get_or_create is atomic
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
token, created = Token.objects.get_or_create(user=user)
|
||||
|
||||
response_serializer = LoginOutputSerializer(
|
||||
{
|
||||
"token": token.key,
|
||||
"user": user,
|
||||
"message": "Login successful",
|
||||
}
|
||||
)
|
||||
return Response(response_serializer.data)
|
||||
else:
|
||||
return Response(
|
||||
{"error": "Account is disabled"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
{"error": "Invalid credentials"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
response_serializer = LoginOutputSerializer(
|
||||
{
|
||||
"token": token.key,
|
||||
"user": user,
|
||||
"message": "Login successful",
|
||||
}
|
||||
)
|
||||
return Response(response_serializer.data)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -206,7 +126,7 @@ class LoginAPIView(TurnstileMixin, APIView):
|
||||
tags=["Authentication"],
|
||||
),
|
||||
)
|
||||
class SignupAPIView(TurnstileMixin, APIView):
|
||||
class SignupAPIView(TurnstileMixin, APIView): # type: ignore[misc]
|
||||
"""API endpoint for user registration."""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
@@ -385,9 +305,9 @@ class SocialProvidersAPIView(APIView):
|
||||
site = get_current_site(request._request) # type: ignore[attr-defined]
|
||||
|
||||
# Cache key based on site and request host
|
||||
cache_key = (
|
||||
f"social_providers:{getattr(site, 'id', site.pk)}:{request.get_host()}"
|
||||
)
|
||||
# Use pk for Site objects, domain for RequestSite objects
|
||||
site_identifier = getattr(site, "pk", site.domain)
|
||||
cache_key = f"social_providers:{site_identifier}:{request.get_host()}"
|
||||
|
||||
# Try to get from cache first (cache for 15 minutes)
|
||||
cached_providers = cache.get(cache_key)
|
||||
|
||||
@@ -135,13 +135,14 @@ class HealthCheckAPIView(APIView):
|
||||
serializer = HealthCheckOutputSerializer(health_data)
|
||||
return Response(serializer.data, status=status_code)
|
||||
|
||||
def _get_database_metrics(self):
|
||||
def _get_database_metrics(self) -> dict:
|
||||
"""Get database performance metrics."""
|
||||
try:
|
||||
from django.db import connection
|
||||
from typing import Any
|
||||
|
||||
# Get basic connection info
|
||||
metrics = {
|
||||
metrics: dict[str, Any] = {
|
||||
"vendor": connection.vendor,
|
||||
"connection_status": "connected",
|
||||
}
|
||||
@@ -193,9 +194,11 @@ class HealthCheckAPIView(APIView):
|
||||
except Exception as e:
|
||||
return {"connection_status": "error", "error": str(e)}
|
||||
|
||||
def _get_system_metrics(self):
|
||||
def _get_system_metrics(self) -> dict:
|
||||
"""Get system performance metrics."""
|
||||
metrics = {
|
||||
from typing import Any
|
||||
|
||||
metrics: dict[str, Any] = {
|
||||
"debug_mode": settings.DEBUG,
|
||||
"allowed_hosts": (settings.ALLOWED_HOSTS if settings.DEBUG else ["hidden"]),
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ from rest_framework.views import APIView
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
|
||||
@@ -19,24 +19,23 @@ from drf_spectacular.types import OpenApiTypes
|
||||
summary="Get trending content",
|
||||
description="Retrieve trending parks and rides based on view counts, ratings, and recency.",
|
||||
parameters=[
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Number of trending items to return (default: 20, max: 100)",
|
||||
"required": False,
|
||||
"schema": {"type": "integer", "default": 20, "maximum": 100},
|
||||
},
|
||||
{
|
||||
"name": "timeframe",
|
||||
"in": "query",
|
||||
"description": "Timeframe for trending calculation (day, week, month) - default: week",
|
||||
"required": False,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["day", "week", "month"],
|
||||
"default": "week",
|
||||
},
|
||||
},
|
||||
OpenApiParameter(
|
||||
name="limit",
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Number of trending items to return (default: 20, max: 100)",
|
||||
required=False,
|
||||
type=OpenApiTypes.INT,
|
||||
default=20,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="timeframe",
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Timeframe for trending calculation (day, week, month) - default: week",
|
||||
required=False,
|
||||
type=OpenApiTypes.STR,
|
||||
enum=["day", "week", "month"],
|
||||
default="week",
|
||||
),
|
||||
],
|
||||
responses={200: OpenApiTypes.OBJECT},
|
||||
tags=["Trending"],
|
||||
@@ -183,20 +182,22 @@ class TrendingAPIView(APIView):
|
||||
summary="Get new content",
|
||||
description="Retrieve recently added parks and rides.",
|
||||
parameters=[
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Number of new items to return (default: 20, max: 100)",
|
||||
"required": False,
|
||||
"schema": {"type": "integer", "default": 20, "maximum": 100},
|
||||
},
|
||||
{
|
||||
"name": "days",
|
||||
"in": "query",
|
||||
"description": "Number of days to look back for new content (default: 30, max: 365)",
|
||||
"required": False,
|
||||
"schema": {"type": "integer", "default": 30, "maximum": 365},
|
||||
},
|
||||
OpenApiParameter(
|
||||
name="limit",
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Number of new items to return (default: 20, max: 100)",
|
||||
required=False,
|
||||
type=OpenApiTypes.INT,
|
||||
default=20,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="days",
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Number of days to look back for new content (default: 30, max: 365)",
|
||||
required=False,
|
||||
type=OpenApiTypes.INT,
|
||||
default=30,
|
||||
),
|
||||
],
|
||||
responses={200: OpenApiTypes.OBJECT},
|
||||
tags=["Trending"],
|
||||
|
||||
Reference in New Issue
Block a user