Files
thrilltrack-explorer/django/apps/entities/signals.py
pacnpal d6ff4cc3a3 Add email templates for user notifications and account management
- Created a base email template (base.html) for consistent styling across all emails.
- Added moderation approval email template (moderation_approved.html) to notify users of approved submissions.
- Added moderation rejection email template (moderation_rejected.html) to inform users of required changes for their submissions.
- Created password reset email template (password_reset.html) for users requesting to reset their passwords.
- Developed a welcome email template (welcome.html) to greet new users and provide account details and tips for using ThrillWiki.
2025-11-08 15:34:04 -05:00

253 lines
9.0 KiB
Python

"""
Signal handlers for automatic search vector updates.
These signals ensure search vectors stay synchronized with model changes,
eliminating the need for manual re-indexing.
Signal handlers are only active when using PostgreSQL with PostGIS backend.
"""
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.conf import settings
from django.contrib.postgres.search import SearchVector
from apps.entities.models import Company, RideModel, Park, Ride
# Only register signals if using PostgreSQL with PostGIS
_using_postgis = 'postgis' in settings.DATABASES['default']['ENGINE']
if _using_postgis:
# ==========================================
# Company Signals
# ==========================================
@receiver(post_save, sender=Company)
def update_company_search_vector(sender, instance, created, **kwargs):
"""
Update search vector when company is created or updated.
Search vector includes:
- name (weight A)
- description (weight B)
"""
# Update the company's own search vector
Company.objects.filter(pk=instance.pk).update(
search_vector=(
SearchVector('name', weight='A', config='english') +
SearchVector('description', weight='B', config='english')
)
)
@receiver(pre_save, sender=Company)
def check_company_name_change(sender, instance, **kwargs):
"""
Track if company name is changing to trigger cascading updates.
Stores the old name on the instance for use in post_save signal.
"""
if instance.pk:
try:
old_instance = Company.objects.get(pk=instance.pk)
instance._old_name = old_instance.name
except Company.DoesNotExist:
instance._old_name = None
else:
instance._old_name = None
@receiver(post_save, sender=Company)
def cascade_company_name_updates(sender, instance, created, **kwargs):
"""
When company name changes, update search vectors for related objects.
Updates:
- All RideModels from this manufacturer
- All Rides from this manufacturer
"""
# Skip if this is a new company or name hasn't changed
if created or not hasattr(instance, '_old_name'):
return
old_name = getattr(instance, '_old_name', None)
if old_name == instance.name:
return
# Update all RideModels from this manufacturer
ride_models = RideModel.objects.filter(manufacturer=instance)
for ride_model in ride_models:
RideModel.objects.filter(pk=ride_model.pk).update(
search_vector=(
SearchVector('name', weight='A', config='english') +
SearchVector('manufacturer__name', weight='A', config='english') +
SearchVector('description', weight='B', config='english')
)
)
# Update all Rides from this manufacturer
rides = Ride.objects.filter(manufacturer=instance)
for ride in rides:
Ride.objects.filter(pk=ride.pk).update(
search_vector=(
SearchVector('name', weight='A', config='english') +
SearchVector('park__name', weight='A', config='english') +
SearchVector('manufacturer__name', weight='B', config='english') +
SearchVector('description', weight='B', config='english')
)
)
# ==========================================
# Park Signals
# ==========================================
@receiver(post_save, sender=Park)
def update_park_search_vector(sender, instance, created, **kwargs):
"""
Update search vector when park is created or updated.
Search vector includes:
- name (weight A)
- description (weight B)
"""
# Update the park's own search vector
Park.objects.filter(pk=instance.pk).update(
search_vector=(
SearchVector('name', weight='A', config='english') +
SearchVector('description', weight='B', config='english')
)
)
@receiver(pre_save, sender=Park)
def check_park_name_change(sender, instance, **kwargs):
"""
Track if park name is changing to trigger cascading updates.
Stores the old name on the instance for use in post_save signal.
"""
if instance.pk:
try:
old_instance = Park.objects.get(pk=instance.pk)
instance._old_name = old_instance.name
except Park.DoesNotExist:
instance._old_name = None
else:
instance._old_name = None
@receiver(post_save, sender=Park)
def cascade_park_name_updates(sender, instance, created, **kwargs):
"""
When park name changes, update search vectors for related rides.
Updates:
- All Rides in this park
"""
# Skip if this is a new park or name hasn't changed
if created or not hasattr(instance, '_old_name'):
return
old_name = getattr(instance, '_old_name', None)
if old_name == instance.name:
return
# Update all Rides in this park
rides = Ride.objects.filter(park=instance)
for ride in rides:
Ride.objects.filter(pk=ride.pk).update(
search_vector=(
SearchVector('name', weight='A', config='english') +
SearchVector('park__name', weight='A', config='english') +
SearchVector('manufacturer__name', weight='B', config='english') +
SearchVector('description', weight='B', config='english')
)
)
# ==========================================
# RideModel Signals
# ==========================================
@receiver(post_save, sender=RideModel)
def update_ride_model_search_vector(sender, instance, created, **kwargs):
"""
Update search vector when ride model is created or updated.
Search vector includes:
- name (weight A)
- manufacturer__name (weight A)
- description (weight B)
"""
RideModel.objects.filter(pk=instance.pk).update(
search_vector=(
SearchVector('name', weight='A', config='english') +
SearchVector('manufacturer__name', weight='A', config='english') +
SearchVector('description', weight='B', config='english')
)
)
@receiver(pre_save, sender=RideModel)
def check_ride_model_manufacturer_change(sender, instance, **kwargs):
"""
Track if ride model manufacturer is changing.
Stores the old manufacturer on the instance for use in post_save signal.
"""
if instance.pk:
try:
old_instance = RideModel.objects.get(pk=instance.pk)
instance._old_manufacturer = old_instance.manufacturer
except RideModel.DoesNotExist:
instance._old_manufacturer = None
else:
instance._old_manufacturer = None
# ==========================================
# Ride Signals
# ==========================================
@receiver(post_save, sender=Ride)
def update_ride_search_vector(sender, instance, created, **kwargs):
"""
Update search vector when ride is created or updated.
Search vector includes:
- name (weight A)
- park__name (weight A)
- manufacturer__name (weight B)
- description (weight B)
"""
Ride.objects.filter(pk=instance.pk).update(
search_vector=(
SearchVector('name', weight='A', config='english') +
SearchVector('park__name', weight='A', config='english') +
SearchVector('manufacturer__name', weight='B', config='english') +
SearchVector('description', weight='B', config='english')
)
)
@receiver(pre_save, sender=Ride)
def check_ride_relationships_change(sender, instance, **kwargs):
"""
Track if ride park or manufacturer are changing.
Stores old values on the instance for use in post_save signal.
"""
if instance.pk:
try:
old_instance = Ride.objects.get(pk=instance.pk)
instance._old_park = old_instance.park
instance._old_manufacturer = old_instance.manufacturer
except Ride.DoesNotExist:
instance._old_park = None
instance._old_manufacturer = None
else:
instance._old_park = None
instance._old_manufacturer = None