# Moderation Models FSM Migration Documentation ## Overview This document describes the migration of moderation models from manual `RichChoiceField` status management to automated FSM-based state transitions using `django-fsm`. ## Migration Summary ### Models Migrated 1. **EditSubmission** - Content edit submission workflow 2. **ModerationReport** - User content/behavior reports 3. **ModerationQueue** - Moderation task queue 4. **BulkOperation** - Bulk administrative operations 5. **PhotoSubmission** - Photo upload moderation workflow ### Key Changes #### 1. Field Type Changes - **Before**: `status = RichChoiceField(...)` - **After**: `status = RichFSMField(...)` #### 2. Model Inheritance - Added `StateMachineMixin` to all models - Set `state_field_name = "status"` on each model #### 3. Transition Methods Models now have auto-generated FSM transition methods based on RichChoice metadata: - `transition_to_(user=None)` - FSM transition methods - Original business logic preserved in existing methods (approve, reject, escalate) #### 4. Service Layer Updates - Updated to use FSM transition methods where appropriate - Added `TransitionNotAllowed` exception handling - Fallback to direct status assignment for compatibility #### 5. View Layer Updates - Added `TransitionNotAllowed` exception handling - Graceful fallback for missing FSM transitions ## FSM Transition Methods ### EditSubmission ```python # Auto-generated based on edit_submission_statuses metadata submission.transition_to_approved(user=moderator) submission.transition_to_rejected(user=moderator) submission.transition_to_escalated(user=moderator) # Business logic preserved in wrapper methods submission.approve(moderator) # Creates/updates Park or Ride objects submission.reject(moderator, reason="...") submission.escalate(moderator, reason="...") ``` ### ModerationReport ```python # Auto-generated based on moderation_report_statuses metadata report.transition_to_under_review(user=moderator) report.transition_to_resolved(user=moderator) report.transition_to_closed(user=moderator) ``` ### ModerationQueue ```python # Auto-generated based on moderation_queue_statuses metadata queue_item.transition_to_in_progress(user=moderator) queue_item.transition_to_completed(user=moderator) queue_item.transition_to_pending(user=moderator) ``` ### BulkOperation ```python # Auto-generated based on bulk_operation_statuses metadata operation.transition_to_running(user=admin) operation.transition_to_completed(user=admin) operation.transition_to_failed(user=admin) operation.transition_to_cancelled(user=admin) operation.transition_to_pending(user=admin) ``` ### PhotoSubmission ```python # Auto-generated based on photo_submission_statuses metadata submission.transition_to_approved(user=moderator) submission.transition_to_rejected(user=moderator) submission.transition_to_escalated(user=moderator) # Business logic preserved in wrapper methods submission.approve(moderator, notes="...") # Creates ParkPhoto or RidePhoto submission.reject(moderator, notes="...") submission.escalate(moderator, notes="...") ``` ## StateMachineMixin Helper Methods All models now have access to these helper methods: ```python # Check if transition is possible submission.can_transition_to('APPROVED') # Returns bool # Get available transitions from current state submission.get_available_transitions() # Returns list of state values # Get available transition method names submission.get_available_transition_methods() # Returns list of method names # Check if state is final (no transitions out) submission.is_final_state() # Returns bool # Get state display with metadata submission.get_state_display_rich() # Returns RichChoice with metadata ``` ## Configuration (apps.py) State machines are auto-configured during Django initialization: ```python # apps/moderation/apps.py class ModerationConfig(AppConfig): def ready(self): from apps.core.state_machine import apply_state_machine from .models import ( EditSubmission, ModerationReport, ModerationQueue, BulkOperation, PhotoSubmission ) apply_state_machine( EditSubmission, field_name="status", choice_group="edit_submission_statuses", domain="moderation" ) # ... similar for other models ``` ## Validation Command Validate all state machine configurations: ```bash # Validate all models python manage.py validate_state_machines # Validate specific model python manage.py validate_state_machines --model editsubmission # Verbose output with transition graphs python manage.py validate_state_machines --verbose ``` ## Migration Steps Applied 1. ✅ Updated model field definitions (RichChoiceField → RichFSMField) 2. ✅ Added StateMachineMixin to all models 3. ✅ Refactored transition methods to work with FSM 4. ✅ Configured state machine application in apps.py 5. ✅ Updated service layer to use FSM transitions 6. ✅ Updated view layer with TransitionNotAllowed handling 7. ✅ Created Django migration (0007_convert_status_to_richfsmfield.py) 8. ✅ Created validation management command 9. ✅ Fixed FSM method naming to use transition_to_ pattern 10. ✅ Updated business logic methods to call FSM transitions ## Next Steps ### 1. Review Generated Migration ✅ COMPLETED Migration file created: `apps/moderation/migrations/0007_convert_status_to_richfsmfield.py` - Converts status fields from RichChoiceField to RichFSMField - All 5 models included: EditSubmission, ModerationReport, ModerationQueue, BulkOperation, PhotoSubmission - No data loss - field type change is compatible - Default values preserved ### 2. Apply Migration ```bash python manage.py migrate moderation ``` ### 3. Validate State Machines ```bash python manage.py validate_state_machines --verbose ``` ### 4. Test Transitions - Test approve/reject/escalate workflows for EditSubmission - Test photo approval workflows for PhotoSubmission - Test queue item lifecycle for ModerationQueue - Test report resolution for ModerationReport - Test bulk operation status changes for BulkOperation ## RichChoice Metadata Requirements All choice groups must have this metadata structure: ```python { 'PENDING': { 'can_transition_to': ['APPROVED', 'REJECTED', 'ESCALATED'], 'requires_moderator': False, 'is_final': False }, 'APPROVED': { 'can_transition_to': [], 'requires_moderator': True, 'is_final': True }, # ... } ``` Required metadata keys: - `can_transition_to`: List of states this state can transition to - `requires_moderator`: Whether transition requires moderator permissions - `is_final`: Whether this is a terminal state ## Permission Guards FSM transitions automatically enforce permissions based on metadata: - `requires_moderator=True`: Requires MODERATOR, ADMIN, or SUPERUSER role - Permission checks happen before transition execution - `TransitionNotAllowed` raised if permissions insufficient ## Error Handling ### TransitionNotAllowed Exception Raised when: - Invalid state transition attempted - Permission requirements not met - Current state doesn't allow transition ```python from django_fsm import TransitionNotAllowed try: submission.transition_to_approved(user=user) except TransitionNotAllowed: # Handle invalid transition pass ``` ### Service Layer Fallbacks Services include fallback logic for compatibility: ```python try: queue_item.transition_to_completed(user=moderator) except (TransitionNotAllowed, AttributeError): # Fallback to direct assignment if FSM unavailable queue_item.status = 'COMPLETED' ``` ## Testing Recommendations ### Unit Tests - Test each transition method individually - Verify permission requirements - Test invalid transitions raise TransitionNotAllowed - Test business logic in wrapper methods ### Integration Tests - Test complete approval workflows - Test queue item lifecycle - Test bulk operation status progression - Test service layer integration ### Manual Testing - Use Django admin to trigger transitions - Test API endpoints for status changes - Verify fsm_log records created correctly ## FSM Logging All transitions are automatically logged via `django-fsm-log`: ```python from django_fsm_log.models import StateLog # Get transition history for a model logs = StateLog.objects.for_(submission) # Each log contains: # - timestamp # - state (new state) # - by (user who triggered transition) # - transition (method name) # - source_state (previous state) ``` ## Rollback Plan If issues arise, rollback steps: 1. Revert migration: `python manage.py migrate moderation ` 2. Revert code changes in Git 3. Remove FSM configuration from apps.py 4. Restore original RichChoiceField definitions ## Performance Considerations - FSM transitions add minimal overhead - State validation happens in-memory - Permission guards use cached user data - No additional database queries for transitions - FSM logging adds one INSERT per transition (async option available) ## Compatibility Notes - Maintains backward compatibility with existing status queries - RichFSMField is drop-in replacement for RichChoiceField - All existing filters and lookups continue to work - No changes needed to serializers or templates - API responses unchanged (status values remain the same) ## Support Resources - FSM Infrastructure: `backend/apps/core/state_machine/` - State Machine README: `backend/apps/core/state_machine/README.md` - Metadata Specification: `backend/apps/core/state_machine/METADATA_SPEC.md` - django-fsm docs: https://github.com/viewflow/django-fsm - django-fsm-log docs: https://github.com/jazzband/django-fsm-log