refactor: Relocate ride services from services.py to services_core.py and refine admin display fields.

This commit is contained in:
pacnpal
2025-12-26 08:26:19 -05:00
parent a9f5644c5c
commit ed04b30469
7 changed files with 148 additions and 13 deletions

7
.gitignore vendored
View File

@@ -131,4 +131,9 @@ frontend/.env
django-forwardemail/ django-forwardemail/
frontend/ frontend/
frontend frontend
.snapshots .snapshots
web/next-env.d.ts
web/.next/types/cache-life.d.ts
.gitignore
web/.next/types/routes.d.ts
web/.next/types/validator.ts

View File

@@ -21,6 +21,7 @@ from .models import (
ModerationQueue, ModerationQueue,
ModerationAction, ModerationAction,
BulkOperation, BulkOperation,
EditSubmission,
) )
User = get_user_model() User = get_user_model()
@@ -60,6 +61,137 @@ class ContentTypeSerializer(serializers.ModelSerializer):
# ============================================================================ # ============================================================================
class EditSubmissionSerializer(serializers.ModelSerializer):
"""Serializer for EditSubmission with UI metadata for Nuxt frontend."""
submitted_by = UserBasicSerializer(source="user", 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()
status_icon = serializers.SerializerMethodField()
status_display = serializers.CharField(source="get_status_display", read_only=True)
time_since_created = serializers.SerializerMethodField()
class Meta:
model = EditSubmission
fields = [
"id",
"status",
"status_display",
"status_color",
"status_icon",
"content_type",
"content_type_name",
"object_id",
"changes",
"moderator_changes",
"rejection_reason",
"submitted_by",
"reviewed_by",
"created_at",
"updated_at",
"time_since_created",
]
read_only_fields = [
"id",
"created_at",
"updated_at",
"submitted_by",
"status_color",
"status_icon",
"status_display",
"content_type_name",
"time_since_created",
]
def get_status_color(self, obj) -> str:
"""Return hex color based on status for UI badges."""
colors = {
"PENDING": "#f59e0b", # Amber
"APPROVED": "#10b981", # Emerald
"REJECTED": "#ef4444", # Red
"ESCALATED": "#8b5cf6", # Violet
}
return colors.get(obj.status, "#6b7280") # Default gray
def get_status_icon(self, obj) -> str:
"""Return Heroicons icon name based on status."""
icons = {
"PENDING": "heroicons:clock",
"APPROVED": "heroicons:check-circle",
"REJECTED": "heroicons:x-circle",
"ESCALATED": "heroicons:arrow-up-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"
class EditSubmissionListSerializer(serializers.ModelSerializer):
"""Optimized serializer for EditSubmission lists."""
submitted_by_username = serializers.CharField(
source="user.username", read_only=True
)
content_type_name = serializers.CharField(
source="content_type.model", read_only=True
)
status_color = serializers.SerializerMethodField()
status_icon = serializers.SerializerMethodField()
class Meta:
model = EditSubmission
fields = [
"id",
"status",
"content_type_name",
"object_id",
"submitted_by_username",
"status_color",
"status_icon",
"created_at",
]
read_only_fields = fields
def get_status_color(self, obj) -> str:
colors = {
"PENDING": "#f59e0b",
"APPROVED": "#10b981",
"REJECTED": "#ef4444",
"ESCALATED": "#8b5cf6",
}
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",
"ESCALATED": "heroicons:arrow-up-circle",
}
return icons.get(obj.status, "heroicons:question-mark-circle")
# ============================================================================
# Moderation Report Serializers
# ============================================================================
class ModerationReportSerializer(serializers.ModelSerializer): class ModerationReportSerializer(serializers.ModelSerializer):
"""Full moderation report serializer with all details.""" """Full moderation report serializer with all details."""

View File

@@ -101,7 +101,7 @@ class ParkLocationAdmin(QueryOptimizationMixin, GISModelAdmin):
"country", "country",
"street_address", "street_address",
) )
readonly_fields = ("latitude", "longitude", "coordinates", "created_at", "updated_at") readonly_fields = ("latitude", "longitude", "coordinates")
autocomplete_fields = ["park"] autocomplete_fields = ["park"]
list_per_page = 50 list_per_page = 50
show_full_result_count = False show_full_result_count = False
@@ -156,13 +156,7 @@ class ParkLocationAdmin(QueryOptimizationMixin, GISModelAdmin):
"description": "OpenStreetMap identifiers for data synchronization.", "description": "OpenStreetMap identifiers for data synchronization.",
}, },
), ),
(
"Metadata",
{
"fields": ("created_at", "updated_at"),
"classes": ("collapse",),
},
),
) )
@admin.display(description="Park") @admin.display(description="Park")
@@ -234,7 +228,7 @@ class ParkAdmin(
"operator__name", "operator__name",
) )
readonly_fields = ("created_at", "updated_at", "ride_count", "average_rating") readonly_fields = ("created_at", "updated_at", "ride_count", "average_rating")
autocomplete_fields = ["operator", "property_owner", "banner_image", "card_image"] autocomplete_fields = ["operator", "property_owner"]
date_hierarchy = "created_at" date_hierarchy = "created_at"
ordering = ("-created_at",) ordering = ("-created_at",)
inlines = [ParkLocationInline, ParkAreaInline] inlines = [ParkLocationInline, ParkAreaInline]

View File

@@ -242,8 +242,8 @@ class RideAdmin(
"manufacturer", "manufacturer",
"designer", "designer",
"ride_model", "ride_model",
"banner_image", # "banner_image",
"card_image", # "card_image",
] ]
inlines = [RideLocationInline, RollerCoasterStatsInline] inlines = [RideLocationInline, RollerCoasterStatsInline]
date_hierarchy = "opening_date" date_hierarchy = "opening_date"

View File

@@ -1,4 +1,7 @@
from .location_service import RideLocationService from .location_service import RideLocationService
from .media_service import RideMediaService from .media_service import RideMediaService
# Import from the services_core module (was services.py, renamed to avoid package collision)
from ..services_core import RideService
__all__ = ["RideLocationService", "RideMediaService", "RideService"]
__all__ = ["RideLocationService", "RideMediaService"]

View File

@@ -36,6 +36,7 @@ ACCOUNT_LOGIN_METHODS = {"email", "username"}
ACCOUNT_EMAIL_VERIFICATION = config( ACCOUNT_EMAIL_VERIFICATION = config(
"ACCOUNT_EMAIL_VERIFICATION", default="mandatory" "ACCOUNT_EMAIL_VERIFICATION", default="mandatory"
) )
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION_SUPPORTS_CHANGE = True ACCOUNT_EMAIL_VERIFICATION_SUPPORTS_CHANGE = True
ACCOUNT_EMAIL_VERIFICATION_SUPPORTS_RESEND = True ACCOUNT_EMAIL_VERIFICATION_SUPPORTS_RESEND = True