rcnn/backend/apps/challenges/serializers.py
munsel 42197bfbc9 add coaster naming, challenge detail panel, and fix GeoJSON id bug
- Coaster editor: name input in top bar, saved/loaded with coaster data
- CoasterListPanel: show coaster name prominently alongside creator username
- ChallengesListPanel: drill-in detail view with center map, plan coaster,
  and accept challenge buttons; coaster count shown in list and detail
- AllCoastersPanel: coaster count visible in challenge entries
- Backend: add coaster_count to ChallengeMapSerializer and ChallengeDetailSerializer
- Fix: ChallengeLayer and ChallengesListPanel were reading f.properties.id
  (always undefined) instead of f.id — GeoFeatureModelSerializer puts the pk
  at the GeoJSON Feature level, not in properties
- Types: remove id from ChallengeMapProperties to reflect actual data shape

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 01:43:10 +02:00

115 lines
3.7 KiB
Python

from rest_framework import serializers
from rest_framework_gis.fields import GeometryField
from rest_framework_gis.serializers import GeoFeatureModelSerializer
from apps.utils.storage import preview_url
from .models import Challenge, ChallengeParticipant
class ChallengeCreateSerializer(serializers.ModelSerializer):
# Accepts GeoJSON Polygon from the client
region = GeometryField(precision=6)
class Meta:
model = Challenge
fields = ["title", "description", "region", "max_submissions", "expires_at"]
def validate_region(self, value):
if value.geom_type != "Polygon":
raise serializers.ValidationError("region must be a GeoJSON Polygon.")
return value
class ChallengeUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Challenge
fields = ["title", "description", "expires_at", "status"]
class ChallengeMapSerializer(GeoFeatureModelSerializer):
"""
GeoJSON FeatureCollection using region_centroid as geometry.
Used for the map pin list — does not include the full region polygon.
"""
coaster_count = serializers.SerializerMethodField()
class Meta:
model = Challenge
geo_field = "region_centroid"
fields = [
"id", "title", "status",
"submission_count", "max_submissions",
"coaster_count",
"expires_at", "created_at",
]
def get_coaster_count(self, obj):
return obj.coasters.count()
class ChallengeSplatPreviewSerializer(serializers.Serializer):
id = serializers.UUIDField()
preview_url = serializers.SerializerMethodField()
created_at = serializers.DateTimeField()
def get_preview_url(self, obj):
return preview_url(obj.preview_key)
class ChallengeDetailSerializer(serializers.ModelSerializer):
region = serializers.SerializerMethodField()
region_centroid = serializers.SerializerMethodField()
creator_username = serializers.CharField(source="creator.username", read_only=True)
participant_count = serializers.SerializerMethodField()
is_participating = serializers.SerializerMethodField()
preview_splats = serializers.SerializerMethodField()
coaster_count = serializers.SerializerMethodField()
class Meta:
model = Challenge
fields = [
"id", "title", "description", "status",
"creator_username",
"region", "region_centroid",
"max_submissions", "submission_count",
"participant_count", "is_participating",
"coaster_count",
"preview_splats",
"expires_at", "created_at", "updated_at",
]
def get_region(self, obj):
import json
return json.loads(obj.region.geojson) if obj.region else None
def get_region_centroid(self, obj):
import json
return json.loads(obj.region_centroid.geojson) if obj.region_centroid else None
def get_participant_count(self, obj):
return obj.participants.count()
def get_is_participating(self, obj):
request = self.context.get("request")
if not request or not request.user.is_authenticated:
return False
return obj.participants.filter(user=request.user).exists()
def get_coaster_count(self, obj):
return obj.coasters.count()
def get_preview_splats(self, obj):
from apps.splats.models import Splat
qs = (
Splat.objects.filter(challenge=obj, is_published=True)
.order_by("-created_at")[:5]
)
return ChallengeSplatPreviewSerializer(qs, many=True).data
class ChallengeParticipantSerializer(serializers.ModelSerializer):
class Meta:
model = ChallengeParticipant
fields = ["id", "joined_at"]
read_only_fields = fields