feat: Add blog, media, and support apps, implement ride credits and image API, and remove toplist feature.

This commit is contained in:
pacnpal
2025-12-26 15:15:28 -05:00
parent cd8868a591
commit 00699d53b4
77 changed files with 7274 additions and 538 deletions

View File

@@ -22,6 +22,7 @@ from .models import (
ModerationAction,
BulkOperation,
EditSubmission,
PhotoSubmission,
)
User = get_user_model()
@@ -65,6 +66,7 @@ class EditSubmissionSerializer(serializers.ModelSerializer):
"""Serializer for EditSubmission with UI metadata for Nuxt frontend."""
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
)
@@ -91,6 +93,8 @@ class EditSubmissionSerializer(serializers.ModelSerializer):
"rejection_reason",
"submitted_by",
"reviewed_by",
"claimed_by",
"claimed_at",
"created_at",
"updated_at",
"time_since_created",
@@ -100,6 +104,8 @@ class EditSubmissionSerializer(serializers.ModelSerializer):
"created_at",
"updated_at",
"submitted_by",
"claimed_by",
"claimed_at",
"status_color",
"status_icon",
"status_display",
@@ -111,6 +117,7 @@ class EditSubmissionSerializer(serializers.ModelSerializer):
"""Return hex color based on status for UI badges."""
colors = {
"PENDING": "#f59e0b", # Amber
"CLAIMED": "#3b82f6", # Blue
"APPROVED": "#10b981", # Emerald
"REJECTED": "#ef4444", # Red
"ESCALATED": "#8b5cf6", # Violet
@@ -121,6 +128,7 @@ class EditSubmissionSerializer(serializers.ModelSerializer):
"""Return Heroicons icon name based on status."""
icons = {
"PENDING": "heroicons:clock",
"CLAIMED": "heroicons:user-circle",
"APPROVED": "heroicons:check-circle",
"REJECTED": "heroicons:x-circle",
"ESCALATED": "heroicons:arrow-up-circle",
@@ -148,6 +156,9 @@ class EditSubmissionListSerializer(serializers.ModelSerializer):
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
)
@@ -162,6 +173,8 @@ class EditSubmissionListSerializer(serializers.ModelSerializer):
"content_type_name",
"object_id",
"submitted_by_username",
"claimed_by_username",
"claimed_at",
"status_color",
"status_icon",
"created_at",
@@ -171,6 +184,7 @@ class EditSubmissionListSerializer(serializers.ModelSerializer):
def get_status_color(self, obj) -> str:
colors = {
"PENDING": "#f59e0b",
"CLAIMED": "#3b82f6",
"APPROVED": "#10b981",
"REJECTED": "#ef4444",
"ESCALATED": "#8b5cf6",
@@ -180,6 +194,7 @@ class EditSubmissionListSerializer(serializers.ModelSerializer):
def get_status_icon(self, obj) -> str:
icons = {
"PENDING": "heroicons:clock",
"CLAIMED": "heroicons:user-circle",
"APPROVED": "heroicons:check-circle",
"REJECTED": "heroicons:x-circle",
"ESCALATED": "heroicons:arrow-up-circle",
@@ -911,3 +926,90 @@ class StateLogSerializer(serializers.ModelSerializer):
]
read_only_fields = fields
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
)
photo_url = serializers.SerializerMethodField()
# UI Metadata
status_display = serializers.CharField(source="get_status_display", read_only=True)
status_color = serializers.SerializerMethodField()
status_icon = serializers.SerializerMethodField()
time_since_created = serializers.SerializerMethodField()
class Meta:
model = PhotoSubmission
fields = [
"id",
"status",
"status_display",
"status_color",
"status_icon",
"content_type",
"content_type_name",
"object_id",
"photo",
"photo_url",
"caption",
"date_taken",
"submitted_by",
"handled_by",
"handled_at",
"notes",
"created_at",
"time_since_created",
]
read_only_fields = [
"id",
"created_at",
"submitted_by",
"handled_by",
"handled_at",
"status_display",
"status_color",
"status_icon",
"content_type_name",
"photo_url",
"time_since_created",
]
def get_photo_url(self, obj) -> str | None:
if obj.photo:
return obj.photo.image_url
return None
def get_status_color(self, obj) -> str:
colors = {
"PENDING": "#f59e0b",
"APPROVED": "#10b981",
"REJECTED": "#ef4444",
}
return colors.get(obj.status, "#6b7280")
def get_status_icon(self, obj) -> str:
icons = {
"PENDING": "heroicons:clock",
"APPROVED": "heroicons:check-circle",
"REJECTED": "heroicons:x-circle",
}
return icons.get(obj.status, "heroicons:question-mark-circle")
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"