""" 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