mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 13:51: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:
File diff suppressed because it is too large
Load Diff
@@ -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"]
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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}"
|
||||
|
||||
212
backend/apps/rides/tests/test_admin.py
Normal file
212
backend/apps/rides/tests/test_admin.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user