Files
thrillwiki_django_no_react/backend/apps/moderation/FSM_MIGRATION.md
pacnpal 7ba0004c93 chore: fix pghistory migration deps and improve htmx utilities
- Update pghistory dependency from 0007 to 0006 in account migrations
- Add docstrings and remove unused imports in htmx_forms.py
- Add DJANGO_SETTINGS_MODULE bash commands to Claude settings
- Add state transition definitions for ride statuses
2025-12-21 17:33:24 -05:00

9.6 KiB

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_<state>(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

# 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

# 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

# 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

# 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

# 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:

# 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:

# 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:

# 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

python manage.py migrate moderation

3. Validate State Machines

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:

{
    '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
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:

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:

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