winofy-backend/apps/stores/serializers.py
Ali Asadi 755d74a9ff Return phone_number and distance_km on store list
phone_number was only exposed on the store detail serializer; add it
to the list too. Also surface the distance annotation (already
computed when lat/lng are passed) as distance_km on both, instead of
using it only for ordering.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 17:04:21 +03:30

152 lines
6 KiB
Python

from django.contrib.gis.geos import Point
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from apps.core.media import presigned_media_url
from apps.locations.models import City, Neighborhood
from apps.locations.serializers import CitySerializer, NeighborhoodSerializer
from .models import Store, StoreBankAccount, StoreCategory, StoreWorkingHours
class StoreCategorySerializer(serializers.ModelSerializer):
icon_url = serializers.SerializerMethodField()
class Meta:
model = StoreCategory
fields = ('uuid', 'name', 'icon', 'icon_url', 'order')
@extend_schema_field(serializers.URLField(allow_null=True))
def get_icon_url(self, obj):
return presigned_media_url(obj.icon)
class StoreWorkingHoursSerializer(serializers.ModelSerializer):
class Meta:
model = StoreWorkingHours
fields = ('weekday', 'opens_at', 'closes_at', 'is_closed')
class StoreListSerializer(serializers.ModelSerializer):
category = StoreCategorySerializer(read_only=True)
logo_url = serializers.SerializerMethodField()
cover_image_url = serializers.SerializerMethodField()
distance_km = serializers.SerializerMethodField()
class Meta:
model = Store
fields = (
'uuid', 'name', 'category', 'logo', 'logo_url', 'cover_image', 'cover_image_url',
'phone_number', 'rating_avg', 'rating_count', 'min_order_amount', 'delivery_fee',
'free_delivery_threshold', 'is_open', 'distance_km',
)
@extend_schema_field(serializers.URLField(allow_null=True))
def get_logo_url(self, obj):
return presigned_media_url(obj.logo)
@extend_schema_field(serializers.URLField(allow_null=True))
def get_cover_image_url(self, obj):
return presigned_media_url(obj.cover_image)
@extend_schema_field(serializers.FloatField(allow_null=True))
def get_distance_km(self, obj):
"""Only present when the request passed `lat`/`lng` (see StoreViewSet.get_queryset)."""
distance = getattr(obj, 'distance', None)
return round(distance.km, 2) if distance is not None else None
class StoreDetailSerializer(StoreListSerializer):
working_hours = StoreWorkingHoursSerializer(many=True, read_only=True)
class Meta(StoreListSerializer.Meta):
fields = StoreListSerializer.Meta.fields + (
'description', 'address',
'accepts_wallet', 'accepts_online', 'accepts_cash_on_delivery', 'working_hours',
)
class TopProductSerializer(serializers.Serializer):
name = serializers.CharField()
units_sold = serializers.IntegerField()
revenue = serializers.IntegerField()
class SalesChartPointSerializer(serializers.Serializer):
bucket = serializers.DateTimeField()
sales = serializers.IntegerField()
class SellerAnalyticsSerializer(serializers.Serializer):
period = serializers.CharField()
order_count = serializers.IntegerField()
total_sales = serializers.IntegerField()
cancellation_rate = serializers.FloatField()
average_order_value = serializers.IntegerField()
chart = SalesChartPointSerializer(many=True)
top_products = TopProductSerializer(many=True)
class StoreBankAccountSerializer(serializers.ModelSerializer):
class Meta:
model = StoreBankAccount
fields = ('iban', 'account_holder_name')
class SellerStoreSerializer(serializers.ModelSerializer):
"""Read/write serializer for the authenticated seller's own store."""
category = StoreCategorySerializer(read_only=True)
category_uuid = serializers.PrimaryKeyRelatedField(
source='category', queryset=StoreCategory.objects.all(), write_only=True,
)
city = CitySerializer(read_only=True)
city_uuid = serializers.PrimaryKeyRelatedField(
source='city', queryset=City.objects.filter(is_active=True), write_only=True,
)
service_neighborhoods = NeighborhoodSerializer(many=True, read_only=True)
service_neighborhood_uuids = serializers.PrimaryKeyRelatedField(
source='service_neighborhoods', queryset=Neighborhood.objects.filter(is_active=True),
write_only=True, many=True, required=False,
)
latitude = serializers.FloatField(write_only=True, required=False)
longitude = serializers.FloatField(write_only=True, required=False)
bank_account = StoreBankAccountSerializer(read_only=True)
logo_url = serializers.SerializerMethodField()
cover_image_url = serializers.SerializerMethodField()
class Meta:
model = Store
fields = (
'uuid', 'name', 'category', 'category_uuid', 'description', 'logo', 'logo_url',
'cover_image', 'cover_image_url',
'phone_number', 'city', 'city_uuid', 'address', 'latitude', 'longitude',
'service_neighborhoods', 'service_neighborhood_uuids', 'delivery_radius_km',
'min_order_amount', 'delivery_fee', 'free_delivery_threshold',
'accepts_wallet', 'accepts_online', 'accepts_cash_on_delivery',
'rating_avg', 'rating_count', 'status', 'is_open', 'bank_account', 'created_at',
)
read_only_fields = ('rating_avg', 'rating_count', 'status')
@extend_schema_field(serializers.URLField(allow_null=True))
def get_logo_url(self, obj):
return presigned_media_url(obj.logo)
@extend_schema_field(serializers.URLField(allow_null=True))
def get_cover_image_url(self, obj):
return presigned_media_url(obj.cover_image)
def _pop_location(self, validated_data):
lat = validated_data.pop('latitude', None)
lng = validated_data.pop('longitude', None)
if lat is not None and lng is not None:
validated_data['location'] = Point(lng, lat, srid=4326)
def create(self, validated_data):
self._pop_location(validated_data)
validated_data['owner'] = self.context['request'].user
return super().create(validated_data)
def update(self, instance, validated_data):
self._pop_location(validated_data)
return super().update(instance, validated_data)