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

View File

@@ -13,12 +13,23 @@ class ParkArea(TrackedModel):
objects = ParkAreaManager()
id: int # Type hint for Django's automatic id field
park = models.ForeignKey(Park, on_delete=models.CASCADE, related_name="areas")
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
description = models.TextField(blank=True)
opening_date = models.DateField(null=True, blank=True)
closing_date = models.DateField(null=True, blank=True)
park = models.ForeignKey(
Park,
on_delete=models.CASCADE,
related_name="areas",
help_text="Park this area belongs to",
)
name = models.CharField(max_length=255, help_text="Name of the park area")
slug = models.SlugField(
max_length=255, help_text="URL-friendly identifier (unique within park)"
)
description = models.TextField(blank=True, help_text="Detailed description of the area")
opening_date = models.DateField(
null=True, blank=True, help_text="Date this area opened"
)
closing_date = models.DateField(
null=True, blank=True, help_text="Date this area closed (if applicable)"
)
def save(self, *args, **kwargs):
if not self.slug:
@@ -28,5 +39,8 @@ class ParkArea(TrackedModel):
def __str__(self):
return self.name
class Meta:
class Meta(TrackedModel.Meta):
verbose_name = "Park Area"
verbose_name_plural = "Park Areas"
ordering = ["park", "name"]
unique_together = ("park", "slug")

View File

@@ -13,20 +13,27 @@ class Company(TrackedModel):
objects = CompanyManager()
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="parks", max_length=20),
default=list,
blank=True,
help_text="Company roles (operator, manufacturer, 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")
# Operator-specific fields
founded_year = models.PositiveIntegerField(blank=True, null=True)
parks_count = models.IntegerField(default=0)
rides_count = models.IntegerField(default=0)
founded_year = models.PositiveIntegerField(
blank=True, null=True, help_text="Year the company was founded"
)
parks_count = models.IntegerField(
default=0, help_text="Number of parks operated (auto-calculated)"
)
rides_count = models.IntegerField(
default=0, help_text="Number of rides manufactured (auto-calculated)"
)
def save(self, *args, **kwargs):
if not self.slug:
@@ -38,8 +45,9 @@ class Company(TrackedModel):
class Meta(TrackedModel.Meta):
app_label = "parks"
ordering = ["name"]
verbose_name = "Company"
verbose_name_plural = "Companies"
ordering = ["name"]
@pghistory.track()
@@ -51,7 +59,10 @@ class CompanyHeadquarters(models.Model):
# Relationships
company = models.OneToOneField(
"Company", on_delete=models.CASCADE, related_name="headquarters"
"Company",
on_delete=models.CASCADE,
related_name="headquarters",
help_text="Company this headquarters belongs to",
)
# Address Fields (No coordinates needed)

View File

@@ -30,7 +30,10 @@ class ParkPhoto(TrackedModel):
"""Photo model specific to parks."""
park = models.ForeignKey(
"parks.Park", on_delete=models.CASCADE, related_name="photos"
"parks.Park",
on_delete=models.CASCADE,
related_name="photos",
help_text="Park this photo belongs to",
)
image = models.ForeignKey(
@@ -39,10 +42,18 @@ class ParkPhoto(TrackedModel):
help_text="Park photo stored on Cloudflare Images"
)
caption = models.CharField(max_length=255, blank=True)
alt_text = models.CharField(max_length=255, blank=True)
is_primary = models.BooleanField(default=False)
is_approved = models.BooleanField(default=False)
caption = models.CharField(
max_length=255, blank=True, help_text="Photo caption or description"
)
alt_text = models.CharField(
max_length=255, blank=True, help_text="Alternative text for accessibility"
)
is_primary = models.BooleanField(
default=False, help_text="Whether this is the primary photo for the park"
)
is_approved = models.BooleanField(
default=False, help_text="Whether this photo has been approved by moderators"
)
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
@@ -55,10 +66,13 @@ class ParkPhoto(TrackedModel):
on_delete=models.SET_NULL,
null=True,
related_name="uploaded_park_photos",
help_text="User who uploaded this photo",
)
class Meta(TrackedModel.Meta):
app_label = "parks"
verbose_name = "Park Photo"
verbose_name_plural = "Park Photos"
ordering = ["-is_primary", "-created_at"]
indexes = [
models.Index(fields=["park", "is_primary"]),

View File

@@ -24,9 +24,9 @@ class Park(StateMachineMixin, TrackedModel):
objects = ParkManager()
id: int # Type hint for Django's automatic id field
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
description = models.TextField(blank=True)
name = models.CharField(max_length=255, help_text="Park name")
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
description = models.TextField(blank=True, help_text="Park description")
state_field_name = "status"
status = RichFSMField(
@@ -50,20 +50,20 @@ class Park(StateMachineMixin, TrackedModel):
# ParkLocation
# Details
opening_date = models.DateField(null=True, blank=True)
closing_date = models.DateField(null=True, blank=True)
operating_season = models.CharField(max_length=255, blank=True)
opening_date = models.DateField(null=True, blank=True, help_text="Opening date")
closing_date = models.DateField(null=True, blank=True, help_text="Closing date")
operating_season = models.CharField(max_length=255, blank=True, help_text="Operating season")
size_acres = models.DecimalField(
max_digits=10, decimal_places=2, null=True, blank=True
max_digits=10, decimal_places=2, null=True, blank=True, help_text="Park size in acres"
)
website = models.URLField(blank=True)
website = models.URLField(blank=True, help_text="Official website URL")
# Statistics
average_rating = models.DecimalField(
max_digits=3, decimal_places=2, null=True, blank=True
max_digits=3, decimal_places=2, null=True, blank=True, help_text="Average user rating (110)"
)
ride_count = models.IntegerField(null=True, blank=True)
coaster_count = models.IntegerField(null=True, blank=True)
ride_count = models.IntegerField(null=True, blank=True, help_text="Total ride count")
coaster_count = models.IntegerField(null=True, blank=True, help_text="Total coaster count")
# Image settings - references to existing photos
banner_image = models.ForeignKey(
@@ -133,6 +133,8 @@ class Park(StateMachineMixin, TrackedModel):
)
class Meta:
verbose_name = "Park"
verbose_name_plural = "Parks"
ordering = ["name"]
constraints = [
# Business rule: Closing date must be after opening date

View File

@@ -15,35 +15,51 @@ class ParkReview(TrackedModel):
A review of a park.
"""
park = models.ForeignKey(
"parks.Park", on_delete=models.CASCADE, related_name="reviews"
"parks.Park",
on_delete=models.CASCADE,
related_name="reviews",
help_text="Park being reviewed",
)
user = models.ForeignKey(
"accounts.User", on_delete=models.CASCADE, related_name="park_reviews"
"accounts.User",
on_delete=models.CASCADE,
related_name="park_reviews",
help_text="User who wrote the review",
)
rating = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(10)]
validators=[MinValueValidator(1), MaxValueValidator(10)],
help_text="Rating from 1-10",
)
title = models.CharField(max_length=200)
content = models.TextField()
visit_date = models.DateField()
title = models.CharField(max_length=200, help_text="Review title")
content = models.TextField(help_text="Review content")
visit_date = models.DateField(help_text="Date the user visited the park")
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# Moderation
is_published = models.BooleanField(default=True)
moderation_notes = models.TextField(blank=True)
is_published = models.BooleanField(
default=True, help_text="Whether this review is publicly visible"
)
moderation_notes = models.TextField(
blank=True, help_text="Internal notes from moderators"
)
moderated_by = models.ForeignKey(
"accounts.User",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="moderated_park_reviews",
help_text="Moderator who reviewed this",
)
moderated_at = models.DateTimeField(
null=True, blank=True, help_text="When this review was moderated"
)
moderated_at = models.DateTimeField(null=True, blank=True)
class Meta:
class Meta(TrackedModel.Meta):
verbose_name = "Park Review"
verbose_name_plural = "Park Reviews"
ordering = ["-created_at"]
unique_together = ["park", "user"]
constraints = [