fix(fsm): Fix StateLog.by capture and cycle validation; add photographer field to photos

## FSM State Machine Fixes

### StateLog.by Field Capture
- Modified TransitionMethodFactory to pass 'user' as 'by' kwarg to enable
  django-fsm-log's @fsm_log_by decorator to correctly capture the user who
  performed the transition
- Applied fix to both escalate_transition and create_transition_method
- Uses exec() to dynamically create transition functions with correct __name__
  before decorators are applied, ensuring django-fsm's method registration works

### Cycle Validation Behavior
- Changed validate_no_cycles() to return ValidationWarning instead of ValidationError
- Cycles are now treated as warnings, not blocking errors, since cycles are often
  intentional in operational status FSMs (e.g., reopening after temporary closure)

### Ride Status Transitions
- Added TEMPORARY_CLOSURE -> OPERATING transition (reopen after temporary closure)
- Added SBNO -> OPERATING transition (revival - ride returns to operation)

## Field Parity

### Photo Models
- Added 'photographer' field to RidePhoto and ParkPhoto models
- Maps to frontend 'photographer_credit' field for full schema parity
- Includes corresponding migrations for both apps

### Serializers
- Added 'photographer' to RidePhotoSerializer and ParkPhotoSerializer read_only_fields
This commit is contained in:
pacnpal
2026-01-09 08:04:44 -05:00
parent fe960e8b62
commit cf54df0416
9 changed files with 157 additions and 32 deletions

View File

@@ -0,0 +1,41 @@
# Generated by Django 5.2.9 on 2026-01-08 18:48
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rides', '0038_company_schema_parity'),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name='ridephoto',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='ridephoto',
name='update_update',
),
migrations.AddField(
model_name='ridephoto',
name='photographer',
field=models.CharField(blank=True, help_text='Photographer credit (maps to frontend photographer_credit)', max_length=200),
),
migrations.AddField(
model_name='ridephotoevent',
name='photographer',
field=models.CharField(blank=True, help_text='Photographer credit (maps to frontend photographer_credit)', max_length=200),
),
pgtrigger.migrations.AddTrigger(
model_name='ridephoto',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_ridephotoevent" ("alt_text", "caption", "created_at", "date_taken", "id", "image_id", "is_approved", "is_primary", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "photo_type", "photographer", "ride_id", "updated_at", "uploaded_by_id") VALUES (NEW."alt_text", NEW."caption", NEW."created_at", NEW."date_taken", NEW."id", NEW."image_id", NEW."is_approved", NEW."is_primary", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."photo_type", NEW."photographer", NEW."ride_id", NEW."updated_at", NEW."uploaded_by_id"); RETURN NULL;', hash='b426eed3a10c63be3db15a5a9477d66388f5dd2f', operation='INSERT', pgid='pgtrigger_insert_insert_0043a', table='rides_ridephoto', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='ridephoto',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "rides_ridephotoevent" ("alt_text", "caption", "created_at", "date_taken", "id", "image_id", "is_approved", "is_primary", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "photo_type", "photographer", "ride_id", "updated_at", "uploaded_by_id") VALUES (NEW."alt_text", NEW."caption", NEW."created_at", NEW."date_taken", NEW."id", NEW."image_id", NEW."is_approved", NEW."is_primary", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."photo_type", NEW."photographer", NEW."ride_id", NEW."updated_at", NEW."uploaded_by_id"); RETURN NULL;', hash='9728ec4736aea41ea171c3494de909aae3f68569', operation='UPDATE', pgid='pgtrigger_update_update_93a7e', table='rides_ridephoto', when='AFTER')),
),
]