mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 15:31:09 -05:00
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:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]),
|
||||
|
||||
@@ -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 (1–10)"
|
||||
)
|
||||
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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user