mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 10:11:09 -05:00
748 lines
24 KiB
Python
748 lines
24 KiB
Python
"""
|
|
Moderation API Serializers
|
|
|
|
This module contains DRF serializers for the moderation system, including:
|
|
- ModerationReport serializers for content reporting
|
|
- ModerationQueue serializers for moderation workflow
|
|
- ModerationAction serializers for tracking moderation actions
|
|
- BulkOperation serializers for administrative bulk operations
|
|
|
|
All serializers include comprehensive validation and nested relationships.
|
|
"""
|
|
|
|
from rest_framework import serializers
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
|
|
from .models import (
|
|
ModerationReport,
|
|
ModerationQueue,
|
|
ModerationAction,
|
|
BulkOperation,
|
|
)
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
# ============================================================================
|
|
# Base Serializers
|
|
# ============================================================================
|
|
|
|
|
|
class UserBasicSerializer(serializers.ModelSerializer):
|
|
"""Basic user information for moderation contexts."""
|
|
|
|
display_name = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = User
|
|
fields = ["id", "username", "display_name", "email", "role"]
|
|
read_only_fields = ["id", "username", "display_name", "email", "role"]
|
|
|
|
def get_display_name(self, obj):
|
|
"""Get the user's display name."""
|
|
return obj.get_display_name()
|
|
|
|
|
|
class ContentTypeSerializer(serializers.ModelSerializer):
|
|
"""Content type information for generic foreign keys."""
|
|
|
|
class Meta:
|
|
model = ContentType
|
|
fields = ["id", "app_label", "model"]
|
|
read_only_fields = ["id", "app_label", "model"]
|
|
|
|
|
|
# ============================================================================
|
|
# Moderation Report Serializers
|
|
# ============================================================================
|
|
|
|
|
|
class ModerationReportSerializer(serializers.ModelSerializer):
|
|
"""Full moderation report serializer with all details."""
|
|
|
|
reported_by = UserBasicSerializer(read_only=True)
|
|
assigned_moderator = UserBasicSerializer(read_only=True)
|
|
content_type = ContentTypeSerializer(read_only=True)
|
|
|
|
# Computed fields
|
|
is_overdue = serializers.SerializerMethodField()
|
|
time_since_created = serializers.SerializerMethodField()
|
|
priority_display = serializers.CharField(
|
|
source="get_priority_display", read_only=True
|
|
)
|
|
status_display = serializers.CharField(source="get_status_display", read_only=True)
|
|
report_type_display = serializers.CharField(
|
|
source="get_report_type_display", read_only=True
|
|
)
|
|
|
|
class Meta:
|
|
model = ModerationReport
|
|
fields = [
|
|
"id",
|
|
"report_type",
|
|
"report_type_display",
|
|
"status",
|
|
"status_display",
|
|
"priority",
|
|
"priority_display",
|
|
"reported_entity_type",
|
|
"reported_entity_id",
|
|
"reason",
|
|
"description",
|
|
"evidence_urls",
|
|
"resolved_at",
|
|
"resolution_notes",
|
|
"resolution_action",
|
|
"created_at",
|
|
"updated_at",
|
|
"reported_by",
|
|
"assigned_moderator",
|
|
"content_type",
|
|
"is_overdue",
|
|
"time_since_created",
|
|
]
|
|
read_only_fields = [
|
|
"id",
|
|
"created_at",
|
|
"updated_at",
|
|
"reported_by",
|
|
"content_type",
|
|
"is_overdue",
|
|
"time_since_created",
|
|
"report_type_display",
|
|
"status_display",
|
|
"priority_display",
|
|
]
|
|
|
|
def get_is_overdue(self, obj) -> bool:
|
|
"""Check if report is overdue based on priority."""
|
|
if obj.status in ["RESOLVED", "DISMISSED"]:
|
|
return False
|
|
|
|
now = timezone.now()
|
|
hours_since_created = (now - obj.created_at).total_seconds() / 3600
|
|
|
|
# Define SLA hours by priority
|
|
sla_hours = {"URGENT": 2, "HIGH": 8, "MEDIUM": 24, "LOW": 72}
|
|
|
|
if obj.priority in sla_hours:
|
|
threshold = sla_hours[obj.priority]
|
|
else:
|
|
raise ValueError(f"Unknown priority level: {obj.priority}")
|
|
|
|
return hours_since_created > threshold
|
|
|
|
def get_time_since_created(self, obj) -> str:
|
|
"""Human-readable time since creation."""
|
|
now = timezone.now()
|
|
diff = now - obj.created_at
|
|
|
|
if diff.days > 0:
|
|
return f"{diff.days} days ago"
|
|
elif diff.seconds > 3600:
|
|
hours = diff.seconds // 3600
|
|
return f"{hours} hours ago"
|
|
else:
|
|
minutes = diff.seconds // 60
|
|
return f"{minutes} minutes ago"
|
|
|
|
|
|
class CreateModerationReportSerializer(serializers.ModelSerializer):
|
|
"""Serializer for creating new moderation reports."""
|
|
|
|
class Meta:
|
|
model = ModerationReport
|
|
fields = [
|
|
"report_type",
|
|
"reported_entity_type",
|
|
"reported_entity_id",
|
|
"reason",
|
|
"description",
|
|
"evidence_urls",
|
|
]
|
|
|
|
def validate(self, attrs):
|
|
"""Validate the report data."""
|
|
# Validate entity type
|
|
valid_entity_types = ["park", "ride", "review", "photo", "user", "comment"]
|
|
if attrs["reported_entity_type"] not in valid_entity_types:
|
|
raise serializers.ValidationError(
|
|
{
|
|
"reported_entity_type": f'Must be one of: {", ".join(valid_entity_types)}'
|
|
}
|
|
)
|
|
|
|
# Validate evidence URLs
|
|
evidence_urls = attrs.get("evidence_urls", [])
|
|
if not isinstance(evidence_urls, list):
|
|
raise serializers.ValidationError(
|
|
{"evidence_urls": "Must be a list of URLs"}
|
|
)
|
|
|
|
return attrs
|
|
|
|
def create(self, validated_data):
|
|
"""Create a new moderation report."""
|
|
validated_data["reported_by"] = self.context["request"].user
|
|
validated_data["status"] = "PENDING"
|
|
validated_data["priority"] = "MEDIUM" # Default priority
|
|
|
|
# Set content type based on entity type
|
|
entity_type = validated_data["reported_entity_type"]
|
|
app_label_map = {
|
|
"park": "parks",
|
|
"ride": "rides",
|
|
"review": "rides", # Assuming ride reviews
|
|
"photo": "media",
|
|
"user": "accounts",
|
|
"comment": "core",
|
|
}
|
|
|
|
if entity_type in app_label_map:
|
|
try:
|
|
content_type = ContentType.objects.get(
|
|
app_label=app_label_map[entity_type], model=entity_type
|
|
)
|
|
validated_data["content_type"] = content_type
|
|
except ContentType.DoesNotExist:
|
|
pass
|
|
|
|
return super().create(validated_data)
|
|
|
|
|
|
class UpdateModerationReportSerializer(serializers.ModelSerializer):
|
|
"""Serializer for updating moderation reports."""
|
|
|
|
class Meta:
|
|
model = ModerationReport
|
|
fields = [
|
|
"status",
|
|
"priority",
|
|
"assigned_moderator",
|
|
"resolution_notes",
|
|
"resolution_action",
|
|
]
|
|
|
|
def validate_status(self, value):
|
|
"""Validate status transitions."""
|
|
if self.instance and self.instance.status == "RESOLVED":
|
|
if value != "RESOLVED":
|
|
raise serializers.ValidationError(
|
|
"Cannot change status of resolved report"
|
|
)
|
|
return value
|
|
|
|
def update(self, instance, validated_data):
|
|
"""Update moderation report with automatic timestamps."""
|
|
if "status" in validated_data and validated_data["status"] == "RESOLVED":
|
|
validated_data["resolved_at"] = timezone.now()
|
|
|
|
return super().update(instance, validated_data)
|
|
|
|
|
|
# ============================================================================
|
|
# Moderation Queue Serializers
|
|
# ============================================================================
|
|
|
|
|
|
class ModerationQueueSerializer(serializers.ModelSerializer):
|
|
"""Full moderation queue item serializer."""
|
|
|
|
assigned_to = UserBasicSerializer(read_only=True)
|
|
related_report = ModerationReportSerializer(read_only=True)
|
|
content_type = ContentTypeSerializer(read_only=True)
|
|
|
|
# Computed fields
|
|
is_overdue = serializers.SerializerMethodField()
|
|
time_in_queue = serializers.SerializerMethodField()
|
|
estimated_completion = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = ModerationQueue
|
|
fields = [
|
|
"id",
|
|
"item_type",
|
|
"status",
|
|
"priority",
|
|
"title",
|
|
"description",
|
|
"entity_type",
|
|
"entity_id",
|
|
"entity_preview",
|
|
"flagged_by",
|
|
"assigned_at",
|
|
"estimated_review_time",
|
|
"created_at",
|
|
"updated_at",
|
|
"tags",
|
|
"assigned_to",
|
|
"related_report",
|
|
"content_type",
|
|
"is_overdue",
|
|
"time_in_queue",
|
|
"estimated_completion",
|
|
]
|
|
read_only_fields = [
|
|
"id",
|
|
"created_at",
|
|
"updated_at",
|
|
"content_type",
|
|
"is_overdue",
|
|
"time_in_queue",
|
|
"estimated_completion",
|
|
]
|
|
|
|
def get_is_overdue(self, obj) -> bool:
|
|
"""Check if queue item is overdue."""
|
|
if obj.status == "COMPLETED":
|
|
return False
|
|
|
|
if obj.assigned_at:
|
|
time_assigned = (timezone.now() - obj.assigned_at).total_seconds() / 60
|
|
return time_assigned > obj.estimated_review_time
|
|
|
|
# If not assigned, check time in queue
|
|
time_in_queue = (timezone.now() - obj.created_at).total_seconds() / 60
|
|
return time_in_queue > (obj.estimated_review_time * 2)
|
|
|
|
def get_time_in_queue(self, obj) -> int:
|
|
"""Minutes since item was created."""
|
|
return int((timezone.now() - obj.created_at).total_seconds() / 60)
|
|
|
|
def get_estimated_completion(self, obj) -> str:
|
|
"""Estimated completion time."""
|
|
if obj.assigned_at:
|
|
completion_time = obj.assigned_at + timedelta(
|
|
minutes=obj.estimated_review_time
|
|
)
|
|
else:
|
|
completion_time = timezone.now() + timedelta(
|
|
minutes=obj.estimated_review_time
|
|
)
|
|
|
|
return completion_time.isoformat()
|
|
|
|
|
|
class AssignQueueItemSerializer(serializers.Serializer):
|
|
"""Serializer for assigning queue items to moderators."""
|
|
|
|
moderator_id = serializers.IntegerField()
|
|
|
|
def validate_moderator_id(self, value):
|
|
"""Validate that the moderator exists and has appropriate permissions."""
|
|
try:
|
|
user = User.objects.get(id=value)
|
|
user_role = getattr(user, "role", "USER")
|
|
if user_role not in ["MODERATOR", "ADMIN", "SUPERUSER"]:
|
|
raise serializers.ValidationError(
|
|
"User must be a moderator, admin, or superuser"
|
|
)
|
|
return value
|
|
except User.DoesNotExist:
|
|
raise serializers.ValidationError("Moderator not found")
|
|
|
|
|
|
class CompleteQueueItemSerializer(serializers.Serializer):
|
|
"""Serializer for completing queue items."""
|
|
|
|
action = serializers.ChoiceField(
|
|
choices=[
|
|
("NO_ACTION", "No Action Required"),
|
|
("CONTENT_REMOVED", "Content Removed"),
|
|
("CONTENT_EDITED", "Content Edited"),
|
|
("USER_WARNING", "User Warning Issued"),
|
|
("USER_SUSPENDED", "User Suspended"),
|
|
("USER_BANNED", "User Banned"),
|
|
]
|
|
)
|
|
notes = serializers.CharField(required=False, allow_blank=True)
|
|
|
|
def validate(self, attrs):
|
|
"""Validate completion data."""
|
|
action = attrs["action"]
|
|
notes = attrs.get("notes", "")
|
|
|
|
# Require notes for certain actions
|
|
if action in ["USER_WARNING", "USER_SUSPENDED", "USER_BANNED"] and not notes:
|
|
raise serializers.ValidationError(
|
|
{"notes": f"Notes are required for action: {action}"}
|
|
)
|
|
|
|
return attrs
|
|
|
|
|
|
# ============================================================================
|
|
# Moderation Action Serializers
|
|
# ============================================================================
|
|
|
|
|
|
class ModerationActionSerializer(serializers.ModelSerializer):
|
|
"""Full moderation action serializer."""
|
|
|
|
moderator = UserBasicSerializer(read_only=True)
|
|
target_user = UserBasicSerializer(read_only=True)
|
|
related_report = ModerationReportSerializer(read_only=True)
|
|
|
|
# Computed fields
|
|
is_expired = serializers.SerializerMethodField()
|
|
time_remaining = serializers.SerializerMethodField()
|
|
action_type_display = serializers.CharField(
|
|
source="get_action_type_display", read_only=True
|
|
)
|
|
|
|
class Meta:
|
|
model = ModerationAction
|
|
fields = [
|
|
"id",
|
|
"action_type",
|
|
"action_type_display",
|
|
"reason",
|
|
"details",
|
|
"duration_hours",
|
|
"created_at",
|
|
"expires_at",
|
|
"is_active",
|
|
"moderator",
|
|
"target_user",
|
|
"related_report",
|
|
"updated_at",
|
|
"is_expired",
|
|
"time_remaining",
|
|
]
|
|
read_only_fields = [
|
|
"id",
|
|
"created_at",
|
|
"updated_at",
|
|
"moderator",
|
|
"target_user",
|
|
"related_report",
|
|
"is_expired",
|
|
"time_remaining",
|
|
"action_type_display",
|
|
]
|
|
|
|
def get_is_expired(self, obj) -> bool:
|
|
"""Check if action has expired."""
|
|
if not obj.expires_at:
|
|
return False
|
|
return timezone.now() > obj.expires_at
|
|
|
|
def get_time_remaining(self, obj) -> str | None:
|
|
"""Time remaining until expiration."""
|
|
if not obj.expires_at or not obj.is_active:
|
|
return None
|
|
|
|
now = timezone.now()
|
|
if now >= obj.expires_at:
|
|
return "Expired"
|
|
|
|
diff = obj.expires_at - now
|
|
if diff.days > 0:
|
|
return f"{diff.days} days"
|
|
elif diff.seconds > 3600:
|
|
hours = diff.seconds // 3600
|
|
return f"{hours} hours"
|
|
else:
|
|
minutes = diff.seconds // 60
|
|
return f"{minutes} minutes"
|
|
|
|
|
|
class CreateModerationActionSerializer(serializers.ModelSerializer):
|
|
"""Serializer for creating moderation actions."""
|
|
|
|
target_user_id = serializers.IntegerField()
|
|
related_report_id = serializers.IntegerField(required=False)
|
|
|
|
class Meta:
|
|
model = ModerationAction
|
|
fields = [
|
|
"action_type",
|
|
"reason",
|
|
"details",
|
|
"duration_hours",
|
|
"target_user_id",
|
|
"related_report_id",
|
|
]
|
|
|
|
def validate_target_user_id(self, value):
|
|
"""Validate target user exists."""
|
|
try:
|
|
User.objects.get(id=value)
|
|
return value
|
|
except User.DoesNotExist:
|
|
raise serializers.ValidationError("Target user not found")
|
|
|
|
def validate_related_report_id(self, value):
|
|
"""Validate related report exists."""
|
|
if value:
|
|
try:
|
|
ModerationReport.objects.get(id=value)
|
|
return value
|
|
except ModerationReport.DoesNotExist:
|
|
raise serializers.ValidationError("Related report not found")
|
|
return value
|
|
|
|
def validate(self, attrs):
|
|
"""Validate action data."""
|
|
action_type = attrs["action_type"]
|
|
duration_hours = attrs.get("duration_hours")
|
|
|
|
# Validate duration for temporary actions
|
|
temporary_actions = ["USER_SUSPENSION", "CONTENT_RESTRICTION"]
|
|
if action_type in temporary_actions and not duration_hours:
|
|
raise serializers.ValidationError(
|
|
{"duration_hours": f"Duration is required for {action_type}"}
|
|
)
|
|
|
|
# Validate duration range
|
|
if duration_hours and (
|
|
duration_hours < 1 or duration_hours > 8760
|
|
): # 1 hour to 1 year
|
|
raise serializers.ValidationError(
|
|
{"duration_hours": "Duration must be between 1 and 8760 hours (1 year)"}
|
|
)
|
|
|
|
return attrs
|
|
|
|
def create(self, validated_data):
|
|
"""Create moderation action with automatic fields."""
|
|
target_user_id = validated_data.pop("target_user_id")
|
|
related_report_id = validated_data.pop("related_report_id", None)
|
|
|
|
validated_data["moderator"] = self.context["request"].user
|
|
validated_data["target_user_id"] = target_user_id
|
|
validated_data["is_active"] = True
|
|
|
|
if related_report_id:
|
|
validated_data["related_report_id"] = related_report_id
|
|
|
|
# Set expiration time for temporary actions
|
|
if validated_data.get("duration_hours"):
|
|
validated_data["expires_at"] = timezone.now() + timedelta(
|
|
hours=validated_data["duration_hours"]
|
|
)
|
|
|
|
return super().create(validated_data)
|
|
|
|
|
|
# ============================================================================
|
|
# Bulk Operation Serializers
|
|
# ============================================================================
|
|
|
|
|
|
class BulkOperationSerializer(serializers.ModelSerializer):
|
|
"""Full bulk operation serializer."""
|
|
|
|
created_by = UserBasicSerializer(read_only=True)
|
|
|
|
# Computed fields
|
|
progress_percentage = serializers.SerializerMethodField()
|
|
estimated_completion = serializers.SerializerMethodField()
|
|
operation_type_display = serializers.CharField(
|
|
source="get_operation_type_display", read_only=True
|
|
)
|
|
status_display = serializers.CharField(source="get_status_display", read_only=True)
|
|
|
|
class Meta:
|
|
model = BulkOperation
|
|
fields = [
|
|
"id",
|
|
"operation_type",
|
|
"operation_type_display",
|
|
"status",
|
|
"status_display",
|
|
"priority",
|
|
"parameters",
|
|
"results",
|
|
"total_items",
|
|
"processed_items",
|
|
"failed_items",
|
|
"created_at",
|
|
"started_at",
|
|
"completed_at",
|
|
"estimated_duration_minutes",
|
|
"can_cancel",
|
|
"description",
|
|
"schedule_for",
|
|
"created_by",
|
|
"updated_at",
|
|
"progress_percentage",
|
|
"estimated_completion",
|
|
]
|
|
read_only_fields = [
|
|
"id",
|
|
"created_at",
|
|
"updated_at",
|
|
"created_by",
|
|
"progress_percentage",
|
|
"estimated_completion",
|
|
"operation_type_display",
|
|
"status_display",
|
|
]
|
|
|
|
def get_progress_percentage(self, obj) -> float:
|
|
"""Calculate progress percentage."""
|
|
if obj.total_items == 0:
|
|
return 0.0
|
|
return round((obj.processed_items / obj.total_items) * 100, 2)
|
|
|
|
def get_estimated_completion(self, obj) -> str | None:
|
|
"""Estimate completion time."""
|
|
if obj.status == "COMPLETED":
|
|
return obj.completed_at.isoformat() if obj.completed_at else None
|
|
|
|
if obj.status == "RUNNING" and obj.started_at:
|
|
# Calculate based on current progress
|
|
if obj.processed_items > 0:
|
|
elapsed_minutes = (timezone.now() - obj.started_at).total_seconds() / 60
|
|
rate = obj.processed_items / elapsed_minutes
|
|
remaining_items = obj.total_items - obj.processed_items
|
|
remaining_minutes = (
|
|
remaining_items / rate
|
|
if rate > 0
|
|
else obj.estimated_duration_minutes
|
|
)
|
|
completion_time = timezone.now() + timedelta(minutes=remaining_minutes)
|
|
return completion_time.isoformat()
|
|
|
|
# Use scheduled time or estimated duration
|
|
if obj.schedule_for:
|
|
return obj.schedule_for.isoformat()
|
|
elif obj.estimated_duration_minutes:
|
|
completion_time = timezone.now() + timedelta(
|
|
minutes=obj.estimated_duration_minutes
|
|
)
|
|
return completion_time.isoformat()
|
|
|
|
return None
|
|
|
|
|
|
class CreateBulkOperationSerializer(serializers.ModelSerializer):
|
|
"""Serializer for creating bulk operations."""
|
|
|
|
class Meta:
|
|
model = BulkOperation
|
|
fields = [
|
|
"operation_type",
|
|
"priority",
|
|
"parameters",
|
|
"description",
|
|
"schedule_for",
|
|
"estimated_duration_minutes",
|
|
]
|
|
|
|
def validate_parameters(self, value):
|
|
"""Validate operation parameters."""
|
|
if not isinstance(value, dict):
|
|
raise serializers.ValidationError("Parameters must be a JSON object")
|
|
|
|
operation_type = getattr(self, "initial_data", {}).get("operation_type")
|
|
|
|
# Validate required parameters by operation type
|
|
required_params = {
|
|
"UPDATE_PARKS": ["park_ids", "updates"],
|
|
"UPDATE_RIDES": ["ride_ids", "updates"],
|
|
"IMPORT_DATA": ["data_type", "source"],
|
|
"EXPORT_DATA": ["data_type", "format"],
|
|
"MODERATE_CONTENT": ["content_type", "action"],
|
|
"USER_ACTIONS": ["user_ids", "action"],
|
|
}
|
|
|
|
if operation_type in required_params:
|
|
for param in required_params[operation_type]:
|
|
if param not in value:
|
|
raise serializers.ValidationError(
|
|
f'Parameter "{param}" is required for {operation_type}'
|
|
)
|
|
|
|
return value
|
|
|
|
def create(self, validated_data):
|
|
"""Create bulk operation with automatic fields."""
|
|
validated_data["created_by"] = self.context["request"].user
|
|
validated_data["status"] = "PENDING"
|
|
validated_data["total_items"] = 0
|
|
validated_data["processed_items"] = 0
|
|
validated_data["failed_items"] = 0
|
|
validated_data["can_cancel"] = True
|
|
|
|
# Generate unique ID
|
|
import uuid
|
|
|
|
validated_data["id"] = str(uuid.uuid4())[:50]
|
|
|
|
return super().create(validated_data)
|
|
|
|
|
|
# ============================================================================
|
|
# Statistics and Summary Serializers
|
|
# ============================================================================
|
|
|
|
|
|
class ModerationStatsSerializer(serializers.Serializer):
|
|
"""Serializer for moderation statistics."""
|
|
|
|
# Report stats
|
|
total_reports = serializers.IntegerField()
|
|
pending_reports = serializers.IntegerField()
|
|
resolved_reports = serializers.IntegerField()
|
|
overdue_reports = serializers.IntegerField()
|
|
|
|
# Queue stats
|
|
queue_size = serializers.IntegerField()
|
|
assigned_items = serializers.IntegerField()
|
|
unassigned_items = serializers.IntegerField()
|
|
|
|
# Action stats
|
|
total_actions = serializers.IntegerField()
|
|
active_actions = serializers.IntegerField()
|
|
expired_actions = serializers.IntegerField()
|
|
|
|
# Bulk operation stats
|
|
running_operations = serializers.IntegerField()
|
|
completed_operations = serializers.IntegerField()
|
|
failed_operations = serializers.IntegerField()
|
|
|
|
# Performance metrics
|
|
average_resolution_time_hours = serializers.FloatField()
|
|
reports_by_priority = serializers.DictField()
|
|
reports_by_type = serializers.DictField()
|
|
|
|
|
|
class UserModerationProfileSerializer(serializers.Serializer):
|
|
"""Serializer for user moderation profile."""
|
|
|
|
user = UserBasicSerializer()
|
|
|
|
# Report history
|
|
reports_made = serializers.IntegerField()
|
|
reports_against = serializers.IntegerField()
|
|
|
|
# Action history
|
|
warnings_received = serializers.IntegerField()
|
|
suspensions_received = serializers.IntegerField()
|
|
active_restrictions = serializers.IntegerField()
|
|
|
|
# Risk assessment
|
|
risk_level = serializers.ChoiceField(
|
|
choices=[
|
|
("LOW", "Low Risk"),
|
|
("MEDIUM", "Medium Risk"),
|
|
("HIGH", "High Risk"),
|
|
("CRITICAL", "Critical Risk"),
|
|
]
|
|
)
|
|
risk_factors = serializers.ListField(child=serializers.CharField())
|
|
|
|
# Recent activity
|
|
recent_reports = ModerationReportSerializer(many=True)
|
|
recent_actions = ModerationActionSerializer(many=True)
|
|
|
|
# Account status
|
|
account_status = serializers.CharField()
|
|
last_violation_date = serializers.DateTimeField(allow_null=True)
|
|
next_review_date = serializers.DateTimeField(allow_null=True)
|