winofy-backend/apps/stores/views.py
Ali Asadi 674ca34041 Add missing required_alternate_scopes across OAS-gated views
Several views using IsAuthenticatedOrTokenMatchesOASRequirements were
missing scope entries for methods they actually expose (GET on
list/retrieve-only viewsets, PUT/PATCH/DELETE on ModelViewSets), and
two had a stale POST entry for a method that doesn't exist
(SellerStoreWorkingHoursView, OrderGroupViewSet). Fixes:
- SellerStoreView, SellerStoreWorkingHoursView (apps/stores/views.py)
- AddressViewSet (apps/locations/views.py)
- SellerReviewViewSet (apps/reviews/views.py)
- SellerProductViewSet (apps/catalog/views.py)
- OrderGroupViewSet, OrderViewSet, SellerOrderViewSet,
  NotificationViewSet (apps/orders/views.py)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 14:46:02 +03:30

133 lines
5.1 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 = {
"GET": [[]],
"POST": [[]],
"PATCH": [[]],
}
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 = {
"GET": [[]],
"PUT": [[]],
}
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)