mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-27 13:47:04 -05:00
refactor: Relocate ride services from services.py to services_core.py and refine admin display fields.
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -132,3 +132,8 @@ 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
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user