mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 15:11:13 -05:00
Add email templates for user notifications and account management
- Created a base email template (base.html) for consistent styling across all emails. - Added moderation approval email template (moderation_approved.html) to notify users of approved submissions. - Added moderation rejection email template (moderation_rejected.html) to inform users of required changes for their submissions. - Created password reset email template (password_reset.html) for users requesting to reset their passwords. - Developed a welcome email template (welcome.html) to greet new users and provide account details and tips for using ThrillWiki.
This commit is contained in:
360
django/api/v1/endpoints/rides.py
Normal file
360
django/api/v1/endpoints/rides.py
Normal file
@@ -0,0 +1,360 @@
|
||||
"""
|
||||
Ride endpoints for API v1.
|
||||
|
||||
Provides CRUD operations for Ride entities with filtering and search.
|
||||
"""
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Q
|
||||
from ninja import Router, Query
|
||||
from ninja.pagination import paginate, PageNumberPagination
|
||||
|
||||
from apps.entities.models import Ride, Park, Company, RideModel
|
||||
from ..schemas import (
|
||||
RideCreate,
|
||||
RideUpdate,
|
||||
RideOut,
|
||||
RideListOut,
|
||||
ErrorResponse
|
||||
)
|
||||
|
||||
|
||||
router = Router(tags=["Rides"])
|
||||
|
||||
|
||||
class RidePagination(PageNumberPagination):
|
||||
"""Custom pagination for rides."""
|
||||
page_size = 50
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response={200: List[RideOut]},
|
||||
summary="List rides",
|
||||
description="Get a paginated list of rides with optional filtering"
|
||||
)
|
||||
@paginate(RidePagination)
|
||||
def list_rides(
|
||||
request,
|
||||
search: Optional[str] = Query(None, description="Search by ride name"),
|
||||
park_id: Optional[UUID] = Query(None, description="Filter by park"),
|
||||
ride_category: Optional[str] = Query(None, description="Filter by ride category"),
|
||||
status: Optional[str] = Query(None, description="Filter by status"),
|
||||
is_coaster: Optional[bool] = Query(None, description="Filter for roller coasters only"),
|
||||
manufacturer_id: Optional[UUID] = Query(None, description="Filter by manufacturer"),
|
||||
ordering: Optional[str] = Query("-created", description="Sort by field (prefix with - for descending)")
|
||||
):
|
||||
"""
|
||||
List all rides with optional filters.
|
||||
|
||||
**Filters:**
|
||||
- search: Search ride names (case-insensitive partial match)
|
||||
- park_id: Filter by park
|
||||
- ride_category: Filter by ride category
|
||||
- status: Filter by operational status
|
||||
- is_coaster: Filter for roller coasters (true/false)
|
||||
- manufacturer_id: Filter by manufacturer
|
||||
- ordering: Sort results (default: -created)
|
||||
|
||||
**Returns:** Paginated list of rides
|
||||
"""
|
||||
queryset = Ride.objects.select_related('park', 'manufacturer', 'model').all()
|
||||
|
||||
# Apply search filter
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(name__icontains=search) | Q(description__icontains=search)
|
||||
)
|
||||
|
||||
# Apply park filter
|
||||
if park_id:
|
||||
queryset = queryset.filter(park_id=park_id)
|
||||
|
||||
# Apply ride category filter
|
||||
if ride_category:
|
||||
queryset = queryset.filter(ride_category=ride_category)
|
||||
|
||||
# Apply status filter
|
||||
if status:
|
||||
queryset = queryset.filter(status=status)
|
||||
|
||||
# Apply coaster filter
|
||||
if is_coaster is not None:
|
||||
queryset = queryset.filter(is_coaster=is_coaster)
|
||||
|
||||
# Apply manufacturer filter
|
||||
if manufacturer_id:
|
||||
queryset = queryset.filter(manufacturer_id=manufacturer_id)
|
||||
|
||||
# Apply ordering
|
||||
valid_order_fields = ['name', 'created', 'modified', 'opening_date', 'height', 'speed', 'length']
|
||||
order_field = ordering.lstrip('-')
|
||||
if order_field in valid_order_fields:
|
||||
queryset = queryset.order_by(ordering)
|
||||
else:
|
||||
queryset = queryset.order_by('-created')
|
||||
|
||||
# Annotate with related names
|
||||
for ride in queryset:
|
||||
ride.park_name = ride.park.name if ride.park else None
|
||||
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
|
||||
ride.model_name = ride.model.name if ride.model else None
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{ride_id}",
|
||||
response={200: RideOut, 404: ErrorResponse},
|
||||
summary="Get ride",
|
||||
description="Retrieve a single ride by ID"
|
||||
)
|
||||
def get_ride(request, ride_id: UUID):
|
||||
"""
|
||||
Get a ride by ID.
|
||||
|
||||
**Parameters:**
|
||||
- ride_id: UUID of the ride
|
||||
|
||||
**Returns:** Ride details
|
||||
"""
|
||||
ride = get_object_or_404(
|
||||
Ride.objects.select_related('park', 'manufacturer', 'model'),
|
||||
id=ride_id
|
||||
)
|
||||
ride.park_name = ride.park.name if ride.park else None
|
||||
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
|
||||
ride.model_name = ride.model.name if ride.model else None
|
||||
return ride
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
response={201: RideOut, 400: ErrorResponse, 404: ErrorResponse},
|
||||
summary="Create ride",
|
||||
description="Create a new ride (requires authentication)"
|
||||
)
|
||||
def create_ride(request, payload: RideCreate):
|
||||
"""
|
||||
Create a new ride.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Parameters:**
|
||||
- payload: Ride data
|
||||
|
||||
**Returns:** Created ride
|
||||
"""
|
||||
# TODO: Add authentication check
|
||||
# if not request.auth:
|
||||
# return 401, {"detail": "Authentication required"}
|
||||
|
||||
# Verify park exists
|
||||
park = get_object_or_404(Park, id=payload.park_id)
|
||||
|
||||
# Verify manufacturer if provided
|
||||
if payload.manufacturer_id:
|
||||
get_object_or_404(Company, id=payload.manufacturer_id)
|
||||
|
||||
# Verify model if provided
|
||||
if payload.model_id:
|
||||
get_object_or_404(RideModel, id=payload.model_id)
|
||||
|
||||
ride = Ride.objects.create(**payload.dict())
|
||||
|
||||
# Reload with related objects
|
||||
ride = Ride.objects.select_related('park', 'manufacturer', 'model').get(id=ride.id)
|
||||
ride.park_name = ride.park.name if ride.park else None
|
||||
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
|
||||
ride.model_name = ride.model.name if ride.model else None
|
||||
|
||||
return 201, ride
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{ride_id}",
|
||||
response={200: RideOut, 404: ErrorResponse, 400: ErrorResponse},
|
||||
summary="Update ride",
|
||||
description="Update an existing ride (requires authentication)"
|
||||
)
|
||||
def update_ride(request, ride_id: UUID, payload: RideUpdate):
|
||||
"""
|
||||
Update a ride.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Parameters:**
|
||||
- ride_id: UUID of the ride
|
||||
- payload: Updated ride data
|
||||
|
||||
**Returns:** Updated ride
|
||||
"""
|
||||
# TODO: Add authentication check
|
||||
# if not request.auth:
|
||||
# return 401, {"detail": "Authentication required"}
|
||||
|
||||
ride = get_object_or_404(
|
||||
Ride.objects.select_related('park', 'manufacturer', 'model'),
|
||||
id=ride_id
|
||||
)
|
||||
|
||||
# Update only provided fields
|
||||
for key, value in payload.dict(exclude_unset=True).items():
|
||||
setattr(ride, key, value)
|
||||
|
||||
ride.save()
|
||||
|
||||
# Reload to get updated relationships
|
||||
ride = Ride.objects.select_related('park', 'manufacturer', 'model').get(id=ride.id)
|
||||
ride.park_name = ride.park.name if ride.park else None
|
||||
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
|
||||
ride.model_name = ride.model.name if ride.model else None
|
||||
|
||||
return ride
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{ride_id}",
|
||||
response={200: RideOut, 404: ErrorResponse, 400: ErrorResponse},
|
||||
summary="Partial update ride",
|
||||
description="Partially update an existing ride (requires authentication)"
|
||||
)
|
||||
def partial_update_ride(request, ride_id: UUID, payload: RideUpdate):
|
||||
"""
|
||||
Partially update a ride.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Parameters:**
|
||||
- ride_id: UUID of the ride
|
||||
- payload: Fields to update
|
||||
|
||||
**Returns:** Updated ride
|
||||
"""
|
||||
# TODO: Add authentication check
|
||||
# if not request.auth:
|
||||
# return 401, {"detail": "Authentication required"}
|
||||
|
||||
ride = get_object_or_404(
|
||||
Ride.objects.select_related('park', 'manufacturer', 'model'),
|
||||
id=ride_id
|
||||
)
|
||||
|
||||
# Update only provided fields
|
||||
for key, value in payload.dict(exclude_unset=True).items():
|
||||
setattr(ride, key, value)
|
||||
|
||||
ride.save()
|
||||
|
||||
# Reload to get updated relationships
|
||||
ride = Ride.objects.select_related('park', 'manufacturer', 'model').get(id=ride.id)
|
||||
ride.park_name = ride.park.name if ride.park else None
|
||||
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
|
||||
ride.model_name = ride.model.name if ride.model else None
|
||||
|
||||
return ride
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{ride_id}",
|
||||
response={204: None, 404: ErrorResponse},
|
||||
summary="Delete ride",
|
||||
description="Delete a ride (requires authentication)"
|
||||
)
|
||||
def delete_ride(request, ride_id: UUID):
|
||||
"""
|
||||
Delete a ride.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Parameters:**
|
||||
- ride_id: UUID of the ride
|
||||
|
||||
**Returns:** No content (204)
|
||||
"""
|
||||
# TODO: Add authentication check
|
||||
# if not request.auth:
|
||||
# return 401, {"detail": "Authentication required"}
|
||||
|
||||
ride = get_object_or_404(Ride, id=ride_id)
|
||||
ride.delete()
|
||||
return 204, None
|
||||
|
||||
|
||||
@router.get(
|
||||
"/coasters/",
|
||||
response={200: List[RideOut]},
|
||||
summary="List roller coasters",
|
||||
description="Get a paginated list of roller coasters only"
|
||||
)
|
||||
@paginate(RidePagination)
|
||||
def list_coasters(
|
||||
request,
|
||||
search: Optional[str] = Query(None, description="Search by ride name"),
|
||||
park_id: Optional[UUID] = Query(None, description="Filter by park"),
|
||||
status: Optional[str] = Query(None, description="Filter by status"),
|
||||
manufacturer_id: Optional[UUID] = Query(None, description="Filter by manufacturer"),
|
||||
min_height: Optional[float] = Query(None, description="Minimum height in feet"),
|
||||
min_speed: Optional[float] = Query(None, description="Minimum speed in mph"),
|
||||
ordering: Optional[str] = Query("-height", description="Sort by field (prefix with - for descending)")
|
||||
):
|
||||
"""
|
||||
List only roller coasters with optional filters.
|
||||
|
||||
**Filters:**
|
||||
- search: Search coaster names
|
||||
- park_id: Filter by park
|
||||
- status: Filter by operational status
|
||||
- manufacturer_id: Filter by manufacturer
|
||||
- min_height: Minimum height filter
|
||||
- min_speed: Minimum speed filter
|
||||
- ordering: Sort results (default: -height)
|
||||
|
||||
**Returns:** Paginated list of roller coasters
|
||||
"""
|
||||
queryset = Ride.objects.filter(is_coaster=True).select_related(
|
||||
'park', 'manufacturer', 'model'
|
||||
)
|
||||
|
||||
# Apply search filter
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(name__icontains=search) | Q(description__icontains=search)
|
||||
)
|
||||
|
||||
# Apply park filter
|
||||
if park_id:
|
||||
queryset = queryset.filter(park_id=park_id)
|
||||
|
||||
# Apply status filter
|
||||
if status:
|
||||
queryset = queryset.filter(status=status)
|
||||
|
||||
# Apply manufacturer filter
|
||||
if manufacturer_id:
|
||||
queryset = queryset.filter(manufacturer_id=manufacturer_id)
|
||||
|
||||
# Apply height filter
|
||||
if min_height is not None:
|
||||
queryset = queryset.filter(height__gte=min_height)
|
||||
|
||||
# Apply speed filter
|
||||
if min_speed is not None:
|
||||
queryset = queryset.filter(speed__gte=min_speed)
|
||||
|
||||
# Apply ordering
|
||||
valid_order_fields = ['name', 'height', 'speed', 'length', 'opening_date', 'inversions']
|
||||
order_field = ordering.lstrip('-')
|
||||
if order_field in valid_order_fields:
|
||||
queryset = queryset.order_by(ordering)
|
||||
else:
|
||||
queryset = queryset.order_by('-height')
|
||||
|
||||
# Annotate with related names
|
||||
for ride in queryset:
|
||||
ride.park_name = ride.park.name if ride.park else None
|
||||
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
|
||||
ride.model_name = ride.model.name if ride.model else None
|
||||
|
||||
return queryset
|
||||
Reference in New Issue
Block a user