mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-04-01 01:08:22 -04:00
Based on the git diff provided, here's a concise and descriptive commit message:
feat: add passkey authentication and enhance user preferences - Add passkey login security event type with fingerprint icon - Include request and site context in email confirmation for backend - Add user_id exact match filter to prevent incorrect user lookups - Enable PATCH method for updating user preferences via API - Add moderation_preferences support to user settings - Optimize ticket queries with select_related and prefetch_related This commit introduces passkey authentication tracking, improves user profile filtering accuracy, and extends the preferences API to support updates. Query optimizations reduce database hits for ticket listings.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-12 01:01
|
||||
|
||||
import apps.core.choices.fields
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("rides", "0039_add_photographer_to_photos"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="RideSubType",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"name",
|
||||
models.CharField(help_text="Name of the ride sub-type (e.g., 'Flying Coaster')", max_length=100),
|
||||
),
|
||||
(
|
||||
"category",
|
||||
apps.core.choices.fields.RichChoiceField(
|
||||
allow_deprecated=False,
|
||||
choice_group="categories",
|
||||
choices=[
|
||||
("RC", "Roller Coaster"),
|
||||
("DR", "Dark Ride"),
|
||||
("FR", "Flat Ride"),
|
||||
("WR", "Water Ride"),
|
||||
("TR", "Transport Ride"),
|
||||
("OT", "Other"),
|
||||
],
|
||||
domain="rides",
|
||||
help_text="Ride category this sub-type belongs to",
|
||||
max_length=2,
|
||||
),
|
||||
),
|
||||
("description", models.TextField(blank=True, help_text="Description of this ride sub-type")),
|
||||
(
|
||||
"created_by",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="User who created this sub-type",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="created_ride_sub_types",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Ride Sub-Type",
|
||||
"verbose_name_plural": "Ride Sub-Types",
|
||||
"ordering": ["category", "name"],
|
||||
"abstract": False,
|
||||
"unique_together": {("category", "name")},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -17,12 +17,14 @@ from .rankings import RankingSnapshot, RidePairComparison, RideRanking
|
||||
from .reviews import RideReview
|
||||
from .rides import Ride, RideModel, RollerCoasterStats
|
||||
from .stats import DarkRideStats, FlatRideStats, KiddieRideStats, TransportationStats, WaterRideStats
|
||||
from .sub_types import RideSubType
|
||||
|
||||
__all__ = [
|
||||
# Primary models
|
||||
"Ride",
|
||||
"RideModel",
|
||||
"RideNameHistory",
|
||||
"RideSubType",
|
||||
"RollerCoasterStats",
|
||||
"WaterRideStats",
|
||||
"DarkRideStats",
|
||||
|
||||
67
backend/apps/rides/models/sub_types.py
Normal file
67
backend/apps/rides/models/sub_types.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
RideSubType model for categorizing rides by sub-type within each category.
|
||||
|
||||
This model replaces the legacy Supabase ride_sub_types table,
|
||||
providing a lookup table for ride sub-types (e.g., 'Flying Coaster',
|
||||
'Inverted Coaster' for roller coasters).
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from apps.core.choices import RichChoiceField
|
||||
from apps.core.models import TrackedModel
|
||||
|
||||
|
||||
class RideSubType(TrackedModel):
|
||||
"""
|
||||
Lookup table for ride sub-types categorized by ride category.
|
||||
|
||||
Examples:
|
||||
- Roller Coaster: Flying Coaster, Inverted Coaster, Dive Coaster
|
||||
- Water Ride: Log Flume, River Rapids, Splash Battle
|
||||
- Dark Ride: Trackless, Omnimover, Boat Ride
|
||||
"""
|
||||
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
help_text="Name of the ride sub-type (e.g., 'Flying Coaster')",
|
||||
)
|
||||
category = RichChoiceField(
|
||||
choice_group="categories",
|
||||
domain="rides",
|
||||
max_length=2,
|
||||
help_text="Ride category this sub-type belongs to",
|
||||
)
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
help_text="Description of this ride sub-type",
|
||||
)
|
||||
created_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="created_ride_sub_types",
|
||||
help_text="User who created this sub-type",
|
||||
)
|
||||
|
||||
class Meta(TrackedModel.Meta):
|
||||
verbose_name = "Ride Sub-Type"
|
||||
verbose_name_plural = "Ride Sub-Types"
|
||||
ordering = ["category", "name"]
|
||||
unique_together = [["category", "name"]]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name} ({self.get_category_display()})"
|
||||
|
||||
def get_category_display(self) -> str:
|
||||
"""Get human-readable category label."""
|
||||
from apps.core.choices.registry import get_choice_group
|
||||
|
||||
group = get_choice_group("categories", domain="rides")
|
||||
if group:
|
||||
for choice in group.choices:
|
||||
if choice.value == self.category:
|
||||
return choice.label
|
||||
return self.category
|
||||
@@ -6,7 +6,7 @@ from django.utils import timezone
|
||||
|
||||
from apps.core.utils import capture_and_log
|
||||
|
||||
from .models import Ride
|
||||
from .models import Ride, RideSubType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -286,3 +286,59 @@ def track_ride_name_changes(sender, instance, **kwargs):
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to track name change for ride {instance.pk}: {e}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Auto-Create Ride Sub-Types on Ride Save
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@receiver(post_save, sender=Ride)
|
||||
def auto_create_ride_sub_type(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Automatically create a RideSubType entry when a ride is saved with a new sub-type value.
|
||||
|
||||
This integrates with the submission pipeline - when a ride submission with a new
|
||||
ride_sub_type value is approved, the sub-type automatically gets added to the
|
||||
lookup table for future autocomplete suggestions.
|
||||
|
||||
Args:
|
||||
sender: The Ride model class.
|
||||
instance: The Ride instance that was saved.
|
||||
created: Whether this is a new ride (not used, we check sub-types for all saves).
|
||||
"""
|
||||
# Skip if no ride_sub_type is set
|
||||
if not instance.ride_sub_type or not instance.ride_sub_type.strip():
|
||||
return
|
||||
|
||||
# Skip if no category is set (can't categorize the sub-type)
|
||||
if not instance.category:
|
||||
return
|
||||
|
||||
ride_sub_type_value = instance.ride_sub_type.strip()
|
||||
|
||||
try:
|
||||
# Check if this sub-type already exists for this category
|
||||
existing = RideSubType.objects.filter(
|
||||
name__iexact=ride_sub_type_value,
|
||||
category=instance.category
|
||||
).exists()
|
||||
|
||||
if not existing:
|
||||
# Create the new sub-type entry
|
||||
RideSubType.objects.create(
|
||||
name=ride_sub_type_value,
|
||||
category=instance.category,
|
||||
description=f"Auto-created from ride: {instance.name}",
|
||||
created_by=getattr(instance, 'created_by', None),
|
||||
)
|
||||
logger.info(
|
||||
f"Auto-created RideSubType '{ride_sub_type_value}' for category "
|
||||
f"'{instance.category}' from ride '{instance.name}'"
|
||||
)
|
||||
except Exception as e:
|
||||
# Non-critical error - log but don't fail the ride save
|
||||
logger.warning(
|
||||
f"Failed to auto-create RideSubType for ride {instance.pk}: {e}"
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user