django-filter was already installed and set as DEFAULT_FILTER_BACKENDS but unused everywhere. Replace hand-rolled get_queryset filtering with FilterSet classes (stores/catalog/locations/reviews) and filterset_fields (orders status). drf-spectacular auto-documents these for swagger, so the manual OpenApiParameter declarations for the migrated fields are removed (stores keeps lat/lng manual since geo-distance isn't a plain filter). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
130 lines
5 KiB
Python
130 lines
5 KiB
Python
from django.contrib.gis.db.models.functions import Distance
|
|
from django.contrib.gis.geos import Point
|
|
from django.contrib.gis.measure import D
|
|
from django.shortcuts import get_object_or_404
|
|
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
|
|
from rest_framework import mixins, status, viewsets
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
|
|
|
from apps.core.permissions import IsStoreOwner
|
|
|
|
from .filters import StoreFilter
|
|
from .models import Store, StoreCategory, StoreWorkingHours
|
|
from .serializers import (
|
|
SellerStoreSerializer,
|
|
StoreCategorySerializer,
|
|
StoreDetailSerializer,
|
|
StoreListSerializer,
|
|
StoreWorkingHoursSerializer,
|
|
)
|
|
|
|
|
|
class StoreCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
|
schema_tags = ['Stores']
|
|
permission_classes = [AllowAny]
|
|
serializer_class = StoreCategorySerializer
|
|
queryset = StoreCategory.objects.all()
|
|
|
|
|
|
@extend_schema_view(
|
|
list=extend_schema(
|
|
parameters=[
|
|
OpenApiParameter('lat', float, description='Latitude; used with `lng` to sort by distance (15km radius).'),
|
|
OpenApiParameter('lng', float, description='Longitude; used with `lat` to sort by distance (15km radius).'),
|
|
],
|
|
),
|
|
)
|
|
class StoreViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
|
|
"""Customer-facing store browsing (home feed, store page)."""
|
|
|
|
schema_tags = ['Stores']
|
|
permission_classes = [AllowAny]
|
|
queryset = Store.objects.filter(status=Store.Status.APPROVED).select_related('category', 'city').distinct()
|
|
filterset_class = StoreFilter
|
|
|
|
def get_serializer_class(self):
|
|
if self.action == 'retrieve':
|
|
return StoreDetailSerializer
|
|
return StoreListSerializer
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
|
|
lat = self.request.query_params.get('lat')
|
|
lng = self.request.query_params.get('lng')
|
|
if lat and lng:
|
|
point = Point(float(lng), float(lat), srid=4326)
|
|
queryset = queryset.filter(location__distance_lte=(point, D(km=15))).annotate(
|
|
distance=Distance('location', point)
|
|
).order_by('distance')
|
|
|
|
return queryset
|
|
|
|
|
|
class SellerStoreView(APIView):
|
|
"""The authenticated seller's own store — GET/PATCH to manage it, POST to create it."""
|
|
|
|
schema_tags = ['Seller · Store']
|
|
serializer_class = SellerStoreSerializer
|
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
required_alternate_scopes = {
|
|
"POST": [[]],
|
|
}
|
|
|
|
def get(self, request):
|
|
store = get_object_or_404(Store, owner=request.user)
|
|
return Response(SellerStoreSerializer(store, context={'request': request}).data)
|
|
|
|
def post(self, request):
|
|
if hasattr(request.user, 'store'):
|
|
return Response(
|
|
{'detail': 'شما قبلاً یک فروشگاه ثبت کردهاید.'}, status=status.HTTP_409_CONFLICT,
|
|
)
|
|
serializer = SellerStoreSerializer(data=request.data, context={'request': request})
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
|
|
@extend_schema(request=SellerStoreSerializer)
|
|
def patch(self, request):
|
|
store = get_object_or_404(Store, owner=request.user)
|
|
serializer = SellerStoreSerializer(
|
|
store, data=request.data, partial=True, context={'request': request},
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
return Response(serializer.data)
|
|
|
|
|
|
class SellerStoreWorkingHoursView(APIView):
|
|
"""Bulk get/set the authenticated seller's weekly working hours (S14)."""
|
|
|
|
schema_tags = ['Seller · Store']
|
|
serializer_class = StoreWorkingHoursSerializer
|
|
# permission_classes = [IsAuthenticated, IsStoreOwner]
|
|
# TODO:
|
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
required_alternate_scopes = {
|
|
"POST": [[]],
|
|
}
|
|
|
|
def get(self, request):
|
|
hours = StoreWorkingHours.objects.filter(store=request.user.store)
|
|
return Response(StoreWorkingHoursSerializer(hours, many=True).data)
|
|
|
|
@extend_schema(request=StoreWorkingHoursSerializer(many=True))
|
|
def put(self, request):
|
|
store = request.user.store
|
|
entries = request.data if isinstance(request.data, list) else request.data.get('working_hours', [])
|
|
serializer = StoreWorkingHoursSerializer(data=entries, many=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
StoreWorkingHours.objects.filter(store=store).delete()
|
|
StoreWorkingHours.objects.bulk_create(
|
|
[StoreWorkingHours(store=store, **entry) for entry in serializer.validated_data]
|
|
)
|
|
return Response(StoreWorkingHoursSerializer(store.working_hours.all(), many=True).data)
|