feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -43,24 +43,15 @@ class TestModerationAdminSite(TestCase):
assert moderation_site.has_permission(request) is False
# Regular user
request.user = type("obj", (object,), {
"is_authenticated": True,
"role": "USER"
})()
request.user = type("obj", (object,), {"is_authenticated": True, "role": "USER"})()
assert moderation_site.has_permission(request) is False
# Moderator
request.user = type("obj", (object,), {
"is_authenticated": True,
"role": "MODERATOR"
})()
request.user = type("obj", (object,), {"is_authenticated": True, "role": "MODERATOR"})()
assert moderation_site.has_permission(request) is True
# Admin
request.user = type("obj", (object,), {
"is_authenticated": True,
"role": "ADMIN"
})()
request.user = type("obj", (object,), {"is_authenticated": True, "role": "ADMIN"})()
assert moderation_site.has_permission(request) is True
@@ -146,6 +137,7 @@ class TestStateLogAdmin(TestCase):
self.site = AdminSite()
# Note: StateLog is from django_fsm_log
from django_fsm_log.models import StateLog
self.admin = StateLogAdmin(model=StateLog, admin_site=self.site)
def test_readonly_permissions(self):
@@ -215,4 +207,5 @@ class TestRegisteredModels(TestCase):
def test_state_log_registered(self):
"""Verify StateLog is registered with moderation site."""
from django_fsm_log.models import StateLog
assert StateLog in moderation_site._registry

View File

@@ -9,7 +9,6 @@ This module tests end-to-end moderation workflows including:
- Bulk operation workflow
"""
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
@@ -25,22 +24,13 @@ class SubmissionApprovalWorkflowTests(TestCase):
def setUpTestData(cls):
"""Set up test data for all tests."""
cls.regular_user = User.objects.create_user(
username='regular_user',
email='user@example.com',
password='testpass123',
role='USER'
username="regular_user", email="user@example.com", password="testpass123", role="USER"
)
cls.moderator = User.objects.create_user(
username='moderator',
email='mod@example.com',
password='testpass123',
role='MODERATOR'
username="moderator", email="mod@example.com", password="testpass123", role="MODERATOR"
)
cls.admin = User.objects.create_user(
username='admin',
email='admin@example.com',
password='testpass123',
role='ADMIN'
username="admin", email="admin@example.com", password="testpass123", role="ADMIN"
)
def test_edit_submission_approval_workflow(self):
@@ -53,10 +43,7 @@ class SubmissionApprovalWorkflowTests(TestCase):
from apps.parks.models import Company
# Create target object
company = Company.objects.create(
name='Test Company',
description='Original description'
)
company = Company.objects.create(name="Test Company", description="Original description")
# User submits an edit
content_type = ContentType.objects.get_for_model(company)
@@ -64,13 +51,13 @@ class SubmissionApprovalWorkflowTests(TestCase):
user=self.regular_user,
content_type=content_type,
object_id=company.id,
submission_type='EDIT',
changes={'description': 'Updated description'},
status='PENDING',
reason='Fixing typo'
submission_type="EDIT",
changes={"description": "Updated description"},
status="PENDING",
reason="Fixing typo",
)
self.assertEqual(submission.status, 'PENDING')
self.assertEqual(submission.status, "PENDING")
self.assertIsNone(submission.handled_by)
self.assertIsNone(submission.handled_at)
@@ -81,7 +68,7 @@ class SubmissionApprovalWorkflowTests(TestCase):
submission.save()
submission.refresh_from_db()
self.assertEqual(submission.status, 'APPROVED')
self.assertEqual(submission.status, "APPROVED")
self.assertEqual(submission.handled_by, self.moderator)
self.assertIsNotNone(submission.handled_at)
@@ -95,16 +82,9 @@ class SubmissionApprovalWorkflowTests(TestCase):
from apps.parks.models import Company, Park
# Create target park
operator = Company.objects.create(
name='Test Operator',
roles=['OPERATOR']
)
operator = Company.objects.create(name="Test Operator", roles=["OPERATOR"])
park = Park.objects.create(
name='Test Park',
slug='test-park',
operator=operator,
status='OPERATING',
timezone='America/New_York'
name="Test Park", slug="test-park", operator=operator, status="OPERATING", timezone="America/New_York"
)
# User submits a photo
@@ -113,12 +93,12 @@ class SubmissionApprovalWorkflowTests(TestCase):
user=self.regular_user,
content_type=content_type,
object_id=park.id,
status='PENDING',
photo_type='GENERAL',
description='Beautiful park entrance'
status="PENDING",
photo_type="GENERAL",
description="Beautiful park entrance",
)
self.assertEqual(submission.status, 'PENDING')
self.assertEqual(submission.status, "PENDING")
# Moderator approves
submission.transition_to_approved(user=self.moderator)
@@ -127,7 +107,7 @@ class SubmissionApprovalWorkflowTests(TestCase):
submission.save()
submission.refresh_from_db()
self.assertEqual(submission.status, 'APPROVED')
self.assertEqual(submission.status, "APPROVED")
class SubmissionRejectionWorkflowTests(TestCase):
@@ -136,16 +116,10 @@ class SubmissionRejectionWorkflowTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.regular_user = User.objects.create_user(
username='user_rej',
email='user_rej@example.com',
password='testpass123',
role='USER'
username="user_rej", email="user_rej@example.com", password="testpass123", role="USER"
)
cls.moderator = User.objects.create_user(
username='mod_rej',
email='mod_rej@example.com',
password='testpass123',
role='MODERATOR'
username="mod_rej", email="mod_rej@example.com", password="testpass123", role="MODERATOR"
)
def test_edit_submission_rejection_with_reason(self):
@@ -157,32 +131,29 @@ class SubmissionRejectionWorkflowTests(TestCase):
from apps.moderation.models import EditSubmission
from apps.parks.models import Company
company = Company.objects.create(
name='Test Company',
description='Original'
)
company = Company.objects.create(name="Test Company", description="Original")
content_type = ContentType.objects.get_for_model(company)
submission = EditSubmission.objects.create(
user=self.regular_user,
content_type=content_type,
object_id=company.id,
submission_type='EDIT',
changes={'name': 'Spam Content'},
status='PENDING',
reason='Name change request'
submission_type="EDIT",
changes={"name": "Spam Content"},
status="PENDING",
reason="Name change request",
)
# Moderator rejects
submission.transition_to_rejected(user=self.moderator)
submission.handled_by = self.moderator
submission.handled_at = timezone.now()
submission.notes = 'Rejected: Content appears to be spam'
submission.notes = "Rejected: Content appears to be spam"
submission.save()
submission.refresh_from_db()
self.assertEqual(submission.status, 'REJECTED')
self.assertIn('spam', submission.notes.lower())
self.assertEqual(submission.status, "REJECTED")
self.assertIn("spam", submission.notes.lower())
class SubmissionEscalationWorkflowTests(TestCase):
@@ -191,22 +162,13 @@ class SubmissionEscalationWorkflowTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.regular_user = User.objects.create_user(
username='user_esc',
email='user_esc@example.com',
password='testpass123',
role='USER'
username="user_esc", email="user_esc@example.com", password="testpass123", role="USER"
)
cls.moderator = User.objects.create_user(
username='mod_esc',
email='mod_esc@example.com',
password='testpass123',
role='MODERATOR'
username="mod_esc", email="mod_esc@example.com", password="testpass123", role="MODERATOR"
)
cls.admin = User.objects.create_user(
username='admin_esc',
email='admin_esc@example.com',
password='testpass123',
role='ADMIN'
username="admin_esc", email="admin_esc@example.com", password="testpass123", role="ADMIN"
)
def test_escalation_workflow(self):
@@ -218,28 +180,25 @@ class SubmissionEscalationWorkflowTests(TestCase):
from apps.moderation.models import EditSubmission
from apps.parks.models import Company
company = Company.objects.create(
name='Sensitive Company',
description='Original'
)
company = Company.objects.create(name="Sensitive Company", description="Original")
content_type = ContentType.objects.get_for_model(company)
submission = EditSubmission.objects.create(
user=self.regular_user,
content_type=content_type,
object_id=company.id,
submission_type='EDIT',
changes={'name': 'New Sensitive Name'},
status='PENDING',
reason='Major name change'
submission_type="EDIT",
changes={"name": "New Sensitive Name"},
status="PENDING",
reason="Major name change",
)
# Moderator escalates
submission.transition_to_escalated(user=self.moderator)
submission.notes = 'Escalated: Major change needs admin review'
submission.notes = "Escalated: Major change needs admin review"
submission.save()
self.assertEqual(submission.status, 'ESCALATED')
self.assertEqual(submission.status, "ESCALATED")
# Admin approves
submission.transition_to_approved(user=self.admin)
@@ -248,7 +207,7 @@ class SubmissionEscalationWorkflowTests(TestCase):
submission.save()
submission.refresh_from_db()
self.assertEqual(submission.status, 'APPROVED')
self.assertEqual(submission.status, "APPROVED")
self.assertEqual(submission.handled_by, self.admin)
@@ -258,16 +217,10 @@ class ReportHandlingWorkflowTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.reporter = User.objects.create_user(
username='reporter',
email='reporter@example.com',
password='testpass123',
role='USER'
username="reporter", email="reporter@example.com", password="testpass123", role="USER"
)
cls.moderator = User.objects.create_user(
username='mod_report',
email='mod_report@example.com',
password='testpass123',
role='MODERATOR'
username="mod_report", email="mod_report@example.com", password="testpass123", role="MODERATOR"
)
def test_report_resolution_workflow(self):
@@ -279,45 +232,42 @@ class ReportHandlingWorkflowTests(TestCase):
from apps.moderation.models import ModerationReport
from apps.parks.models import Company
reported_company = Company.objects.create(
name='Problematic Company',
description='Some inappropriate content'
)
reported_company = Company.objects.create(name="Problematic Company", description="Some inappropriate content")
content_type = ContentType.objects.get_for_model(reported_company)
# User reports content
report = ModerationReport.objects.create(
report_type='CONTENT',
status='PENDING',
priority='HIGH',
reported_entity_type='company',
report_type="CONTENT",
status="PENDING",
priority="HIGH",
reported_entity_type="company",
reported_entity_id=reported_company.id,
content_type=content_type,
reason='INAPPROPRIATE',
description='This content is inappropriate',
reported_by=self.reporter
reason="INAPPROPRIATE",
description="This content is inappropriate",
reported_by=self.reporter,
)
self.assertEqual(report.status, 'PENDING')
self.assertEqual(report.status, "PENDING")
# Moderator claims and starts review
report.transition_to_under_review(user=self.moderator)
report.assigned_moderator = self.moderator
report.save()
self.assertEqual(report.status, 'UNDER_REVIEW')
self.assertEqual(report.status, "UNDER_REVIEW")
self.assertEqual(report.assigned_moderator, self.moderator)
# Moderator resolves
report.transition_to_resolved(user=self.moderator)
report.resolution_action = 'CONTENT_REMOVED'
report.resolution_notes = 'Content was removed'
report.resolution_action = "CONTENT_REMOVED"
report.resolution_notes = "Content was removed"
report.resolved_at = timezone.now()
report.save()
report.refresh_from_db()
self.assertEqual(report.status, 'RESOLVED')
self.assertEqual(report.status, "RESOLVED")
self.assertIsNotNone(report.resolved_at)
def test_report_dismissal_workflow(self):
@@ -329,23 +279,20 @@ class ReportHandlingWorkflowTests(TestCase):
from apps.moderation.models import ModerationReport
from apps.parks.models import Company
company = Company.objects.create(
name='Valid Company',
description='Normal content'
)
company = Company.objects.create(name="Valid Company", description="Normal content")
content_type = ContentType.objects.get_for_model(company)
report = ModerationReport.objects.create(
report_type='CONTENT',
status='PENDING',
priority='LOW',
reported_entity_type='company',
report_type="CONTENT",
status="PENDING",
priority="LOW",
reported_entity_type="company",
reported_entity_id=company.id,
content_type=content_type,
reason='OTHER',
description='I just do not like this',
reported_by=self.reporter
reason="OTHER",
description="I just do not like this",
reported_by=self.reporter,
)
# Moderator claims
@@ -355,12 +302,12 @@ class ReportHandlingWorkflowTests(TestCase):
# Moderator dismisses as invalid
report.transition_to_dismissed(user=self.moderator)
report.resolution_notes = 'Report does not violate any guidelines'
report.resolution_notes = "Report does not violate any guidelines"
report.resolved_at = timezone.now()
report.save()
report.refresh_from_db()
self.assertEqual(report.status, 'DISMISSED')
self.assertEqual(report.status, "DISMISSED")
class BulkOperationWorkflowTests(TestCase):
@@ -369,10 +316,7 @@ class BulkOperationWorkflowTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.admin = User.objects.create_user(
username='admin_bulk',
email='admin_bulk@example.com',
password='testpass123',
role='ADMIN'
username="admin_bulk", email="admin_bulk@example.com", password="testpass123", role="ADMIN"
)
def test_bulk_operation_success_workflow(self):
@@ -384,22 +328,22 @@ class BulkOperationWorkflowTests(TestCase):
from apps.moderation.models import BulkOperation
operation = BulkOperation.objects.create(
operation_type='APPROVE_SUBMISSIONS',
status='PENDING',
operation_type="APPROVE_SUBMISSIONS",
status="PENDING",
total_items=10,
processed_items=0,
created_by=self.admin,
parameters={'submission_ids': list(range(1, 11))}
parameters={"submission_ids": list(range(1, 11))},
)
self.assertEqual(operation.status, 'PENDING')
self.assertEqual(operation.status, "PENDING")
# Start operation
operation.transition_to_running(user=self.admin)
operation.started_at = timezone.now()
operation.save()
self.assertEqual(operation.status, 'RUNNING')
self.assertEqual(operation.status, "RUNNING")
# Simulate progress
for i in range(1, 11):
@@ -409,11 +353,11 @@ class BulkOperationWorkflowTests(TestCase):
# Complete operation
operation.transition_to_completed(user=self.admin)
operation.completed_at = timezone.now()
operation.results = {'approved': 10, 'failed': 0}
operation.results = {"approved": 10, "failed": 0}
operation.save()
operation.refresh_from_db()
self.assertEqual(operation.status, 'COMPLETED')
self.assertEqual(operation.status, "COMPLETED")
self.assertEqual(operation.processed_items, 10)
def test_bulk_operation_failure_workflow(self):
@@ -425,12 +369,12 @@ class BulkOperationWorkflowTests(TestCase):
from apps.moderation.models import BulkOperation
operation = BulkOperation.objects.create(
operation_type='DELETE_CONTENT',
status='PENDING',
operation_type="DELETE_CONTENT",
status="PENDING",
total_items=5,
processed_items=0,
created_by=self.admin,
parameters={'content_ids': list(range(1, 6))}
parameters={"content_ids": list(range(1, 6))},
)
operation.transition_to_running(user=self.admin)
@@ -442,11 +386,11 @@ class BulkOperationWorkflowTests(TestCase):
operation.failed_items = 3
operation.transition_to_failed(user=self.admin)
operation.completed_at = timezone.now()
operation.results = {'error': 'Database connection lost', 'processed': 2}
operation.results = {"error": "Database connection lost", "processed": 2}
operation.save()
operation.refresh_from_db()
self.assertEqual(operation.status, 'FAILED')
self.assertEqual(operation.status, "FAILED")
self.assertEqual(operation.failed_items, 3)
def test_bulk_operation_cancellation_workflow(self):
@@ -458,13 +402,13 @@ class BulkOperationWorkflowTests(TestCase):
from apps.moderation.models import BulkOperation
operation = BulkOperation.objects.create(
operation_type='BATCH_UPDATE',
status='PENDING',
operation_type="BATCH_UPDATE",
status="PENDING",
total_items=100,
processed_items=0,
created_by=self.admin,
parameters={'update_field': 'status'},
can_cancel=True
parameters={"update_field": "status"},
can_cancel=True,
)
operation.transition_to_running(user=self.admin)
@@ -477,11 +421,11 @@ class BulkOperationWorkflowTests(TestCase):
# Admin cancels
operation.transition_to_cancelled(user=self.admin)
operation.completed_at = timezone.now()
operation.results = {'cancelled_at': 30, 'reason': 'User requested cancellation'}
operation.results = {"cancelled_at": 30, "reason": "User requested cancellation"}
operation.save()
operation.refresh_from_db()
self.assertEqual(operation.status, 'CANCELLED')
self.assertEqual(operation.status, "CANCELLED")
self.assertEqual(operation.processed_items, 30)
@@ -491,10 +435,7 @@ class ModerationQueueWorkflowTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.moderator = User.objects.create_user(
username='mod_queue',
email='mod_queue@example.com',
password='testpass123',
role='MODERATOR'
username="mod_queue", email="mod_queue@example.com", password="testpass123", role="MODERATOR"
)
def test_queue_completion_workflow(self):
@@ -506,14 +447,14 @@ class ModerationQueueWorkflowTests(TestCase):
from apps.moderation.models import ModerationQueue
queue_item = ModerationQueue.objects.create(
queue_type='SUBMISSION_REVIEW',
status='PENDING',
priority='MEDIUM',
item_type='edit_submission',
item_id=123
queue_type="SUBMISSION_REVIEW",
status="PENDING",
priority="MEDIUM",
item_type="edit_submission",
item_id=123,
)
self.assertEqual(queue_item.status, 'PENDING')
self.assertEqual(queue_item.status, "PENDING")
# Moderator claims
queue_item.transition_to_in_progress(user=self.moderator)
@@ -521,7 +462,7 @@ class ModerationQueueWorkflowTests(TestCase):
queue_item.assigned_at = timezone.now()
queue_item.save()
self.assertEqual(queue_item.status, 'IN_PROGRESS')
self.assertEqual(queue_item.status, "IN_PROGRESS")
# Work completed
queue_item.transition_to_completed(user=self.moderator)
@@ -529,4 +470,4 @@ class ModerationQueueWorkflowTests(TestCase):
queue_item.save()
queue_item.refresh_from_db()
self.assertEqual(queue_item.status, 'COMPLETED')
self.assertEqual(queue_item.status, "COMPLETED")