- 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
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
- EditSubmission - Content edit submission workflow
- ModerationReport - User content/behavior reports
- ModerationQueue - Moderation task queue
- BulkOperation - Bulk administrative operations
- PhotoSubmission - Photo upload moderation workflow
Key Changes
1. Field Type Changes
- Before:
status = RichChoiceField(...) - After:
status = RichFSMField(...)
2. Model Inheritance
- Added
StateMachineMixinto 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
TransitionNotAllowedexception handling - Fallback to direct status assignment for compatibility
5. View Layer Updates
- Added
TransitionNotAllowedexception 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
- ✅ Updated model field definitions (RichChoiceField → RichFSMField)
- ✅ Added StateMachineMixin to all models
- ✅ Refactored transition methods to work with FSM
- ✅ Configured state machine application in apps.py
- ✅ Updated service layer to use FSM transitions
- ✅ Updated view layer with TransitionNotAllowed handling
- ✅ Created Django migration (0007_convert_status_to_richfsmfield.py)
- ✅ Created validation management command
- ✅ Fixed FSM method naming to use transition_to_ pattern
- ✅ 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 torequires_moderator: Whether transition requires moderator permissionsis_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
TransitionNotAllowedraised 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:
- Revert migration:
python manage.py migrate moderation <previous_migration> - Revert code changes in Git
- Remove FSM configuration from apps.py
- 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