mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-01-02 01:27:03 -05:00
feat: Implement initial schema and add various API, service, and management command enhancements across the application.
This commit is contained in:
@@ -68,9 +68,7 @@ class EditSubmissionSerializer(serializers.ModelSerializer):
|
||||
|
||||
submitted_by = UserBasicSerializer(source="user", read_only=True)
|
||||
claimed_by = UserBasicSerializer(read_only=True)
|
||||
content_type_name = serializers.CharField(
|
||||
source="content_type.model", read_only=True
|
||||
)
|
||||
content_type_name = serializers.CharField(source="content_type.model", read_only=True)
|
||||
|
||||
# UI Metadata fields for Nuxt rendering
|
||||
status_color = serializers.SerializerMethodField()
|
||||
@@ -117,10 +115,10 @@ class EditSubmissionSerializer(serializers.ModelSerializer):
|
||||
def get_status_color(self, obj) -> str:
|
||||
"""Return hex color based on status for UI badges."""
|
||||
colors = {
|
||||
"PENDING": "#f59e0b", # Amber
|
||||
"CLAIMED": "#3b82f6", # Blue
|
||||
"APPROVED": "#10b981", # Emerald
|
||||
"REJECTED": "#ef4444", # Red
|
||||
"PENDING": "#f59e0b", # Amber
|
||||
"CLAIMED": "#3b82f6", # Blue
|
||||
"APPROVED": "#10b981", # Emerald
|
||||
"REJECTED": "#ef4444", # Red
|
||||
"ESCALATED": "#8b5cf6", # Violet
|
||||
}
|
||||
return colors.get(obj.status, "#6b7280") # Default gray
|
||||
@@ -154,15 +152,9 @@ class EditSubmissionSerializer(serializers.ModelSerializer):
|
||||
class EditSubmissionListSerializer(serializers.ModelSerializer):
|
||||
"""Optimized serializer for EditSubmission lists."""
|
||||
|
||||
submitted_by_username = serializers.CharField(
|
||||
source="user.username", read_only=True
|
||||
)
|
||||
claimed_by_username = serializers.CharField(
|
||||
source="claimed_by.username", read_only=True, allow_null=True
|
||||
)
|
||||
content_type_name = serializers.CharField(
|
||||
source="content_type.model", read_only=True
|
||||
)
|
||||
submitted_by_username = serializers.CharField(source="user.username", read_only=True)
|
||||
claimed_by_username = serializers.CharField(source="claimed_by.username", read_only=True, allow_null=True)
|
||||
content_type_name = serializers.CharField(source="content_type.model", read_only=True)
|
||||
status_color = serializers.SerializerMethodField()
|
||||
status_icon = serializers.SerializerMethodField()
|
||||
|
||||
@@ -218,13 +210,9 @@ class ModerationReportSerializer(serializers.ModelSerializer):
|
||||
# Computed fields
|
||||
is_overdue = serializers.SerializerMethodField()
|
||||
time_since_created = serializers.SerializerMethodField()
|
||||
priority_display = serializers.CharField(
|
||||
source="get_priority_display", read_only=True
|
||||
)
|
||||
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
|
||||
)
|
||||
report_type_display = serializers.CharField(source="get_report_type_display", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ModerationReport
|
||||
@@ -318,17 +306,13 @@ class CreateModerationReportSerializer(serializers.ModelSerializer):
|
||||
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)}'
|
||||
}
|
||||
{"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"}
|
||||
)
|
||||
raise serializers.ValidationError({"evidence_urls": "Must be a list of URLs"})
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -351,9 +335,7 @@ class CreateModerationReportSerializer(serializers.ModelSerializer):
|
||||
|
||||
if entity_type in app_label_map:
|
||||
try:
|
||||
content_type = ContentType.objects.get(
|
||||
app_label=app_label_map[entity_type], model=entity_type
|
||||
)
|
||||
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
|
||||
@@ -377,9 +359,7 @@ class UpdateModerationReportSerializer(serializers.ModelSerializer):
|
||||
def validate_status(self, value):
|
||||
"""Validate status transitions."""
|
||||
if self.instance and self.instance.status == "RESOLVED" and value != "RESOLVED":
|
||||
raise serializers.ValidationError(
|
||||
"Cannot change status of resolved report"
|
||||
)
|
||||
raise serializers.ValidationError("Cannot change status of resolved report")
|
||||
return value
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@@ -462,13 +442,9 @@ class ModerationQueueSerializer(serializers.ModelSerializer):
|
||||
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
|
||||
)
|
||||
completion_time = obj.assigned_at + timedelta(minutes=obj.estimated_review_time)
|
||||
else:
|
||||
completion_time = timezone.now() + timedelta(
|
||||
minutes=obj.estimated_review_time
|
||||
)
|
||||
completion_time = timezone.now() + timedelta(minutes=obj.estimated_review_time)
|
||||
|
||||
return completion_time.isoformat()
|
||||
|
||||
@@ -484,12 +460,10 @@ class AssignQueueItemSerializer(serializers.Serializer):
|
||||
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"
|
||||
)
|
||||
raise serializers.ValidationError("User must be a moderator, admin, or superuser")
|
||||
return value
|
||||
except User.DoesNotExist:
|
||||
raise serializers.ValidationError("Moderator not found")
|
||||
raise serializers.ValidationError("Moderator not found") from None
|
||||
|
||||
|
||||
class CompleteQueueItemSerializer(serializers.Serializer):
|
||||
@@ -514,9 +488,7 @@ class CompleteQueueItemSerializer(serializers.Serializer):
|
||||
|
||||
# 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}"}
|
||||
)
|
||||
raise serializers.ValidationError({"notes": f"Notes are required for action: {action}"})
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -536,9 +508,7 @@ class ModerationActionSerializer(serializers.ModelSerializer):
|
||||
# Computed fields
|
||||
is_expired = serializers.SerializerMethodField()
|
||||
time_remaining = serializers.SerializerMethodField()
|
||||
action_type_display = serializers.CharField(
|
||||
source="get_action_type_display", read_only=True
|
||||
)
|
||||
action_type_display = serializers.CharField(source="get_action_type_display", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ModerationAction
|
||||
@@ -620,7 +590,7 @@ class CreateModerationActionSerializer(serializers.ModelSerializer):
|
||||
User.objects.get(id=value)
|
||||
return value
|
||||
except User.DoesNotExist:
|
||||
raise serializers.ValidationError("Target user not found")
|
||||
raise serializers.ValidationError("Target user not found") from None
|
||||
|
||||
def validate_related_report_id(self, value):
|
||||
"""Validate related report exists."""
|
||||
@@ -629,7 +599,7 @@ class CreateModerationActionSerializer(serializers.ModelSerializer):
|
||||
ModerationReport.objects.get(id=value)
|
||||
return value
|
||||
except ModerationReport.DoesNotExist:
|
||||
raise serializers.ValidationError("Related report not found")
|
||||
raise serializers.ValidationError("Related report not found") from None
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
@@ -640,17 +610,11 @@ class CreateModerationActionSerializer(serializers.ModelSerializer):
|
||||
# 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}"}
|
||||
)
|
||||
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)"}
|
||||
)
|
||||
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
|
||||
|
||||
@@ -668,9 +632,7 @@ class CreateModerationActionSerializer(serializers.ModelSerializer):
|
||||
|
||||
# Set expiration time for temporary actions
|
||||
if validated_data.get("duration_hours"):
|
||||
validated_data["expires_at"] = timezone.now() + timedelta(
|
||||
hours=validated_data["duration_hours"]
|
||||
)
|
||||
validated_data["expires_at"] = timezone.now() + timedelta(hours=validated_data["duration_hours"])
|
||||
|
||||
return super().create(validated_data)
|
||||
|
||||
@@ -688,9 +650,7 @@ class BulkOperationSerializer(serializers.ModelSerializer):
|
||||
# Computed fields
|
||||
progress_percentage = serializers.SerializerMethodField()
|
||||
estimated_completion = serializers.SerializerMethodField()
|
||||
operation_type_display = serializers.CharField(
|
||||
source="get_operation_type_display", read_only=True
|
||||
)
|
||||
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:
|
||||
@@ -741,17 +701,13 @@ class BulkOperationSerializer(serializers.ModelSerializer):
|
||||
if obj.status == "COMPLETED":
|
||||
return obj.completed_at.isoformat() if obj.completed_at else None
|
||||
|
||||
if obj.status == "RUNNING" and obj.started_at:
|
||||
if obj.status == "RUNNING" and obj.started_at: # noqa: SIM102
|
||||
# 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
|
||||
)
|
||||
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()
|
||||
|
||||
@@ -759,9 +715,7 @@ class BulkOperationSerializer(serializers.ModelSerializer):
|
||||
if obj.schedule_for:
|
||||
return obj.schedule_for.isoformat()
|
||||
elif obj.estimated_duration_minutes:
|
||||
completion_time = timezone.now() + timedelta(
|
||||
minutes=obj.estimated_duration_minutes
|
||||
)
|
||||
completion_time = timezone.now() + timedelta(minutes=obj.estimated_duration_minutes)
|
||||
return completion_time.isoformat()
|
||||
|
||||
return None
|
||||
@@ -801,9 +755,7 @@ class CreateBulkOperationSerializer(serializers.ModelSerializer):
|
||||
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}'
|
||||
)
|
||||
raise serializers.ValidationError(f'Parameter "{param}" is required for {operation_type}')
|
||||
|
||||
return value
|
||||
|
||||
@@ -902,27 +854,28 @@ class UserModerationProfileSerializer(serializers.Serializer):
|
||||
class StateLogSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for FSM transition history."""
|
||||
|
||||
user = serializers.CharField(source='by.username', read_only=True)
|
||||
model = serializers.CharField(source='content_type.model', read_only=True)
|
||||
from_state = serializers.CharField(source='source_state', read_only=True)
|
||||
to_state = serializers.CharField(source='state', read_only=True)
|
||||
reason = serializers.CharField(source='description', read_only=True)
|
||||
user = serializers.CharField(source="by.username", read_only=True)
|
||||
model = serializers.CharField(source="content_type.model", read_only=True)
|
||||
from_state = serializers.CharField(source="source_state", read_only=True)
|
||||
to_state = serializers.CharField(source="state", read_only=True)
|
||||
reason = serializers.CharField(source="description", read_only=True)
|
||||
|
||||
class Meta:
|
||||
from django_fsm_log.models import StateLog
|
||||
|
||||
model = StateLog
|
||||
fields = [
|
||||
'id',
|
||||
'timestamp',
|
||||
'model',
|
||||
'object_id',
|
||||
'state',
|
||||
'from_state',
|
||||
'to_state',
|
||||
'transition',
|
||||
'user',
|
||||
'description',
|
||||
'reason',
|
||||
"id",
|
||||
"timestamp",
|
||||
"model",
|
||||
"object_id",
|
||||
"state",
|
||||
"from_state",
|
||||
"to_state",
|
||||
"transition",
|
||||
"user",
|
||||
"description",
|
||||
"reason",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
@@ -931,9 +884,7 @@ class PhotoSubmissionSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for PhotoSubmission."""
|
||||
|
||||
submitted_by = UserBasicSerializer(source="user", read_only=True)
|
||||
content_type_name = serializers.CharField(
|
||||
source="content_type.model", read_only=True
|
||||
)
|
||||
content_type_name = serializers.CharField(source="content_type.model", read_only=True)
|
||||
photo_url = serializers.SerializerMethodField()
|
||||
|
||||
# UI Metadata
|
||||
@@ -1012,4 +963,3 @@ class PhotoSubmissionSerializer(serializers.ModelSerializer):
|
||||
else:
|
||||
minutes = diff.seconds // 60
|
||||
return f"{minutes} minutes ago"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user