Add secret management guide, client-side performance monitoring, and search accessibility enhancements

- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
pacnpal
2025-12-23 16:41:42 -05:00
parent ae31e889d7
commit edcd8f2076
155 changed files with 22046 additions and 4645 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -12,22 +12,29 @@ from apps.core.choices.fields import RichChoiceField
@pghistory.track()
class Company(TrackedModel):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
name = models.CharField(max_length=255, help_text="Company name")
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
roles = ArrayField(
RichChoiceField(choice_group="company_roles", domain="rides", max_length=20),
default=list,
blank=True,
help_text="Company roles (manufacturer, designer, etc.)",
)
description = models.TextField(blank=True)
website = models.URLField(blank=True)
description = models.TextField(blank=True, help_text="Detailed company description")
website = models.URLField(blank=True, help_text="Company website URL")
# General company info
founded_date = models.DateField(null=True, blank=True)
founded_date = models.DateField(
null=True, blank=True, help_text="Date the company was founded"
)
# Manufacturer-specific fields
rides_count = models.IntegerField(default=0)
coasters_count = models.IntegerField(default=0)
rides_count = models.IntegerField(
default=0, help_text="Number of rides manufactured (auto-calculated)"
)
coasters_count = models.IntegerField(
default=0, help_text="Number of coasters manufactured (auto-calculated)"
)
# Frontend URL
url = models.URLField(blank=True, help_text="Frontend URL for this company")
@@ -92,5 +99,6 @@ class Company(TrackedModel):
class Meta(TrackedModel.Meta):
app_label = "rides"
ordering = ["name"]
verbose_name = "Company"
verbose_name_plural = "Companies"
ordering = ["name"]

View File

@@ -22,7 +22,8 @@ class RideRanking(models.Model):
"""
ride = models.OneToOneField(
"rides.Ride", on_delete=models.CASCADE, related_name="ranking"
"rides.Ride", on_delete=models.CASCADE, related_name="ranking",
help_text="Ride this ranking entry describes"
)
# Core ranking metrics
@@ -73,6 +74,8 @@ class RideRanking(models.Model):
)
class Meta:
verbose_name = "Ride Ranking"
verbose_name_plural = "Ride Rankings"
ordering = ["rank"]
indexes = [
models.Index(fields=["rank"]),
@@ -155,6 +158,9 @@ class RidePairComparison(models.Model):
)
class Meta:
verbose_name = "Ride Pair Comparison"
verbose_name_plural = "Ride Pair Comparisons"
ordering = ["ride_a", "ride_b"]
unique_together = [["ride_a", "ride_b"]]
indexes = [
models.Index(fields=["ride_a", "ride_b"]),
@@ -201,6 +207,8 @@ class RankingSnapshot(models.Model):
)
class Meta:
verbose_name = "Ranking Snapshot"
verbose_name_plural = "Ranking Snapshots"
unique_together = [["ride", "snapshot_date"]]
ordering = ["-snapshot_date", "rank"]
indexes = [

View File

@@ -165,6 +165,8 @@ class RideModel(TrackedModel):
url = models.URLField(blank=True, help_text="Frontend URL for this ride model")
class Meta(TrackedModel.Meta):
verbose_name = "Ride Model"
verbose_name_plural = "Ride Models"
ordering = ["manufacturer__name", "name"]
constraints = [
# Unique constraints (replacing unique_together for better error messages)
@@ -330,7 +332,10 @@ class RideModelVariant(TrackedModel):
"""
ride_model = models.ForeignKey(
RideModel, on_delete=models.CASCADE, related_name="variants"
RideModel,
on_delete=models.CASCADE,
related_name="variants",
help_text="Base ride model this variant belongs to",
)
name = models.CharField(max_length=255, help_text="Name of this variant")
description = models.TextField(
@@ -339,16 +344,32 @@ class RideModelVariant(TrackedModel):
# Variant-specific specifications
min_height_ft = models.DecimalField(
max_digits=6, decimal_places=2, null=True, blank=True
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Minimum height for this variant",
)
max_height_ft = models.DecimalField(
max_digits=6, decimal_places=2, null=True, blank=True
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum height for this variant",
)
min_speed_mph = models.DecimalField(
max_digits=5, decimal_places=2, null=True, blank=True
max_digits=5,
decimal_places=2,
null=True,
blank=True,
help_text="Minimum speed for this variant",
)
max_speed_mph = models.DecimalField(
max_digits=5, decimal_places=2, null=True, blank=True
max_digits=5,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum speed for this variant",
)
# Distinguishing features
@@ -357,6 +378,8 @@ class RideModelVariant(TrackedModel):
)
class Meta(TrackedModel.Meta):
verbose_name = "Ride Model Variant"
verbose_name_plural = "Ride Model Variants"
ordering = ["ride_model", "name"]
unique_together = ["ride_model", "name"]
@@ -369,15 +392,22 @@ class RideModelPhoto(TrackedModel):
"""Photos associated with ride models for catalog/promotional purposes."""
ride_model = models.ForeignKey(
RideModel, on_delete=models.CASCADE, related_name="photos"
RideModel,
on_delete=models.CASCADE,
related_name="photos",
help_text="Ride model this photo belongs to",
)
image = models.ForeignKey(
'django_cloudflareimages_toolkit.CloudflareImage',
on_delete=models.CASCADE,
help_text="Photo of the ride model stored on Cloudflare Images"
)
caption = models.CharField(max_length=500, blank=True)
alt_text = models.CharField(max_length=255, blank=True)
caption = models.CharField(
max_length=500, blank=True, help_text="Photo caption or description"
)
alt_text = models.CharField(
max_length=255, blank=True, help_text="Alternative text for accessibility"
)
# Photo metadata
photo_type = RichChoiceField(
@@ -393,11 +423,17 @@ class RideModelPhoto(TrackedModel):
)
# Attribution
photographer = models.CharField(max_length=255, blank=True)
source = models.CharField(max_length=255, blank=True)
copyright_info = models.CharField(max_length=255, blank=True)
photographer = models.CharField(
max_length=255, blank=True, help_text="Name of the photographer"
)
source = models.CharField(max_length=255, blank=True, help_text="Source of the photo")
copyright_info = models.CharField(
max_length=255, blank=True, help_text="Copyright information"
)
class Meta(TrackedModel.Meta):
verbose_name = "Ride Model Photo"
verbose_name_plural = "Ride Model Photos"
ordering = ["-is_primary", "-created_at"]
def __str__(self) -> str:
@@ -420,7 +456,10 @@ class RideModelTechnicalSpec(TrackedModel):
"""
ride_model = models.ForeignKey(
RideModel, on_delete=models.CASCADE, related_name="technical_specs"
RideModel,
on_delete=models.CASCADE,
related_name="technical_specs",
help_text="Ride model this specification belongs to",
)
spec_category = RichChoiceField(
@@ -442,6 +481,8 @@ class RideModelTechnicalSpec(TrackedModel):
)
class Meta(TrackedModel.Meta):
verbose_name = "Ride Model Technical Specification"
verbose_name_plural = "Ride Model Technical Specifications"
ordering = ["spec_category", "spec_name"]
unique_together = ["ride_model", "spec_category", "spec_name"]
@@ -563,6 +604,8 @@ class Ride(StateMachineMixin, TrackedModel):
)
class Meta(TrackedModel.Meta):
verbose_name = "Ride"
verbose_name_plural = "Rides"
ordering = ["name"]
unique_together = ["park", "slug"]
constraints = [
@@ -949,20 +992,41 @@ class RollerCoasterStats(models.Model):
ride = models.OneToOneField(
Ride, on_delete=models.CASCADE, related_name="coaster_stats"
Ride,
on_delete=models.CASCADE,
related_name="coaster_stats",
help_text="Ride these statistics belong to",
)
height_ft = models.DecimalField(
max_digits=6, decimal_places=2, null=True, blank=True
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum height in feet",
)
length_ft = models.DecimalField(
max_digits=7, decimal_places=2, null=True, blank=True
max_digits=7,
decimal_places=2,
null=True,
blank=True,
help_text="Track length in feet",
)
speed_mph = models.DecimalField(
max_digits=5, decimal_places=2, null=True, blank=True
max_digits=5,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum speed in mph",
)
inversions = models.PositiveIntegerField(
default=0, help_text="Number of inversions"
)
ride_time_seconds = models.PositiveIntegerField(
null=True, blank=True, help_text="Duration of the ride in seconds"
)
track_type = models.CharField(
max_length=255, blank=True, help_text="Type of track (e.g., tubular steel, wooden)"
)
inversions = models.PositiveIntegerField(default=0)
ride_time_seconds = models.PositiveIntegerField(null=True, blank=True)
track_type = models.CharField(max_length=255, blank=True)
track_material = RichChoiceField(
choice_group="track_materials",
domain="rides",
@@ -980,7 +1044,11 @@ class RollerCoasterStats(models.Model):
help_text="Roller coaster type classification"
)
max_drop_height_ft = models.DecimalField(
max_digits=6, decimal_places=2, null=True, blank=True
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum drop height in feet",
)
propulsion_system = RichChoiceField(
choice_group="propulsion_systems",
@@ -989,14 +1057,23 @@ class RollerCoasterStats(models.Model):
default="CHAIN",
help_text="Propulsion or lift system type"
)
train_style = models.CharField(max_length=255, blank=True)
trains_count = models.PositiveIntegerField(null=True, blank=True)
cars_per_train = models.PositiveIntegerField(null=True, blank=True)
seats_per_car = models.PositiveIntegerField(null=True, blank=True)
train_style = models.CharField(
max_length=255, blank=True, help_text="Style of train (e.g., floorless, inverted)"
)
trains_count = models.PositiveIntegerField(
null=True, blank=True, help_text="Number of trains"
)
cars_per_train = models.PositiveIntegerField(
null=True, blank=True, help_text="Number of cars per train"
)
seats_per_car = models.PositiveIntegerField(
null=True, blank=True, help_text="Number of seats per car"
)
class Meta:
verbose_name = "Roller Coaster Statistics"
verbose_name_plural = "Roller Coaster Statistics"
ordering = ["ride"]
def __str__(self) -> str:
return f"Stats for {self.ride.name}"

View File

@@ -0,0 +1,212 @@
"""
Tests for rides admin interfaces.
These tests verify the functionality of ride, model, stats, company,
review, and ranking admin classes including query optimization and custom actions.
"""
import pytest
from django.contrib.admin.sites import AdminSite
from django.contrib.auth import get_user_model
from django.test import RequestFactory, TestCase
from apps.rides.admin import (
CompanyAdmin,
RankingSnapshotAdmin,
RideAdmin,
RideLocationAdmin,
RideModelAdmin,
RidePairComparisonAdmin,
RideRankingAdmin,
RideReviewAdmin,
RollerCoasterStatsAdmin,
)
from apps.rides.models.company import Company
from apps.rides.models.location import RideLocation
from apps.rides.models.rankings import RankingSnapshot, RidePairComparison, RideRanking
from apps.rides.models.reviews import RideReview
from apps.rides.models.rides import Ride, RideModel, RollerCoasterStats
User = get_user_model()
class TestRideAdmin(TestCase):
"""Tests for RideAdmin class."""
def setUp(self):
self.factory = RequestFactory()
self.site = AdminSite()
self.admin = RideAdmin(model=Ride, admin_site=self.site)
def test_list_display_fields(self):
"""Verify all required fields are in list_display."""
required_fields = [
"name",
"park_link",
"category_badge",
"manufacturer_link",
"status_badge",
]
for field in required_fields:
assert field in self.admin.list_display
def test_list_select_related(self):
"""Verify select_related is configured for ForeignKeys."""
assert "park" in self.admin.list_select_related
assert "manufacturer" in self.admin.list_select_related
assert "designer" in self.admin.list_select_related
assert "ride_model" in self.admin.list_select_related
def test_list_prefetch_related(self):
"""Verify prefetch_related is configured for reverse relations."""
assert "reviews" in self.admin.list_prefetch_related
def test_export_fields_configured(self):
"""Verify export fields are configured."""
assert hasattr(self.admin, "export_fields")
assert "id" in self.admin.export_fields
assert "name" in self.admin.export_fields
assert "category" in self.admin.export_fields
def test_status_actions_registered(self):
"""Verify status change actions are registered."""
request = self.factory.get("/admin/")
request.user = User(is_superuser=True)
actions = self.admin.get_actions(request)
assert "bulk_set_operating" in actions
assert "bulk_set_closed" in actions
assert "bulk_set_sbno" in actions
class TestRideModelAdmin(TestCase):
"""Tests for RideModelAdmin class."""
def setUp(self):
self.factory = RequestFactory()
self.site = AdminSite()
self.admin = RideModelAdmin(model=RideModel, admin_site=self.site)
def test_list_select_related(self):
"""Verify select_related for manufacturer."""
assert "manufacturer" in self.admin.list_select_related
def test_list_prefetch_related(self):
"""Verify prefetch_related for rides."""
assert "rides" in self.admin.list_prefetch_related
class TestRollerCoasterStatsAdmin(TestCase):
"""Tests for RollerCoasterStatsAdmin class."""
def setUp(self):
self.factory = RequestFactory()
self.site = AdminSite()
self.admin = RollerCoasterStatsAdmin(model=RollerCoasterStats, admin_site=self.site)
def test_list_select_related(self):
"""Verify select_related for ride and park."""
assert "ride" in self.admin.list_select_related
assert "ride__park" in self.admin.list_select_related
assert "ride__manufacturer" in self.admin.list_select_related
class TestRideReviewAdmin(TestCase):
"""Tests for RideReviewAdmin class."""
def setUp(self):
self.factory = RequestFactory()
self.site = AdminSite()
self.admin = RideReviewAdmin(model=RideReview, admin_site=self.site)
def test_list_select_related(self):
"""Verify select_related for ride, park, and user."""
assert "ride" in self.admin.list_select_related
assert "ride__park" in self.admin.list_select_related
assert "user" in self.admin.list_select_related
assert "moderated_by" in self.admin.list_select_related
def test_moderation_actions_registered(self):
"""Verify moderation actions are registered."""
request = self.factory.get("/admin/")
request.user = User(is_superuser=True)
actions = self.admin.get_actions(request)
assert "bulk_approve" in actions
assert "bulk_reject" in actions
assert "flag_for_review" in actions
class TestRideRankingAdmin(TestCase):
"""Tests for RideRankingAdmin class."""
def setUp(self):
self.factory = RequestFactory()
self.site = AdminSite()
self.admin = RideRankingAdmin(model=RideRanking, admin_site=self.site)
def test_readonly_permissions(self):
"""Verify read-only permissions are set."""
request = self.factory.get("/admin/")
request.user = User(is_superuser=False)
assert self.admin.has_add_permission(request) is False
assert self.admin.has_change_permission(request) is False
def test_list_select_related(self):
"""Verify select_related for ride and park."""
assert "ride" in self.admin.list_select_related
assert "ride__park" in self.admin.list_select_related
class TestRidePairComparisonAdmin(TestCase):
"""Tests for RidePairComparisonAdmin class."""
def setUp(self):
self.factory = RequestFactory()
self.site = AdminSite()
self.admin = RidePairComparisonAdmin(model=RidePairComparison, admin_site=self.site)
def test_readonly_permissions(self):
"""Verify read-only permissions are set."""
request = self.factory.get("/admin/")
request.user = User(is_superuser=False)
assert self.admin.has_add_permission(request) is False
assert self.admin.has_change_permission(request) is False
def test_list_select_related(self):
"""Verify select_related for both rides."""
assert "ride_a" in self.admin.list_select_related
assert "ride_b" in self.admin.list_select_related
class TestRankingSnapshotAdmin(TestCase):
"""Tests for RankingSnapshotAdmin class."""
def setUp(self):
self.factory = RequestFactory()
self.site = AdminSite()
self.admin = RankingSnapshotAdmin(model=RankingSnapshot, admin_site=self.site)
def test_readonly_permissions(self):
"""Verify read-only permissions are set."""
request = self.factory.get("/admin/")
request.user = User(is_superuser=False)
assert self.admin.has_add_permission(request) is False
assert self.admin.has_change_permission(request) is False
class TestCompanyAdmin(TestCase):
"""Tests for rides CompanyAdmin class."""
def setUp(self):
self.factory = RequestFactory()
self.site = AdminSite()
self.admin = CompanyAdmin(model=Company, admin_site=self.site)
def test_list_prefetch_related(self):
"""Verify prefetch_related for manufactured rides."""
assert "manufactured_rides" in self.admin.list_prefetch_related
assert "designed_rides" in self.admin.list_prefetch_related

View File

@@ -56,6 +56,12 @@ from .models.rankings import RankingSnapshot, RideRanking
from .models.rides import Ride, RideModel
from .services.ranking_service import RideRankingService
import logging
from apps.core.logging import log_exception, log_business_event
logger = logging.getLogger(__name__)
class ParkContextRequired:
"""
@@ -244,7 +250,20 @@ class RideCreateView(
def form_valid(self, form):
"""Handle form submission using RideFormMixin for entity suggestions."""
self.handle_entity_suggestions(form)
return super().form_valid(form)
response = super().form_valid(form)
log_business_event(
logger,
event_type="ride_created",
message=f"Ride created: {self.object.name}",
context={
"ride_id": self.object.id,
"ride_name": self.object.name,
"park_id": self.park.id,
"park_name": self.park.name,
},
request=self.request,
)
return response
class RideUpdateView(
@@ -300,7 +319,20 @@ class RideUpdateView(
def form_valid(self, form):
"""Handle form submission using RideFormMixin for entity suggestions."""
self.handle_entity_suggestions(form)
return super().form_valid(form)
response = super().form_valid(form)
log_business_event(
logger,
event_type="ride_updated",
message=f"Ride updated: {self.object.name}",
context={
"ride_id": self.object.id,
"ride_name": self.object.name,
"park_id": self.park.id,
"park_name": self.park.name,
},
request=self.request,
)
return response
class RideListView(ListView):
@@ -547,6 +579,7 @@ class RideSearchView(ListView):
# Process search form
form = RideSearchForm(self.request.GET)
search_term = self.request.GET.get("ride", "").strip()
if form.is_valid():
ride = form.cleaned_data.get("ride")
if ride:
@@ -554,10 +587,17 @@ class RideSearchView(ListView):
queryset = queryset.filter(id=ride.id)
else:
# If no specific ride, filter by search term
search_term = self.request.GET.get("ride", "").strip()
if search_term:
queryset = queryset.filter(name__icontains=search_term)
result_count = queryset.count()
logger.info(
"Ride search executed",
extra={
"query": search_term,
"result_count": result_count,
},
)
return queryset
def get_template_names(self):
@@ -596,10 +636,18 @@ class RideRankingsView(ListView):
min_riders = self.request.GET.get("min_riders")
if min_riders:
try:
min_riders = int(min_riders)
queryset = queryset.filter(mutual_riders_count__gte=min_riders)
except ValueError:
pass
min_riders_int = int(min_riders)
queryset = queryset.filter(mutual_riders_count__gte=min_riders_int)
except (ValueError, TypeError) as e:
log_exception(
logger,
e,
context={
"operation": "ride_rankings_min_riders",
"min_riders": min_riders,
},
request=self.request,
)
return queryset