from pathlib import Path from urllib.parse import urljoin import uuid from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.files.storage import default_storage from django.http import JsonResponse from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView from menu.models import Dish, DishCategory, Order from menu.serializers import DishSerializer, DishCategorySerializer, OrderSerializer from menu.services import DishService, OrderService, ParserService @login_required def index(request): """首页""" return render(request, 'index.html') @csrf_exempt @login_required def upload_image(request): """上传图片""" if request.method != 'POST': return JsonResponse({'error': 'Method not allowed'}, status=405) # Rate limit: 20 uploads per minute per user from django.core.cache import cache user_key = f'upload_throttle_{request.user.id}' cache.add(user_key, 0, 60) count = cache.get(user_key, 0) if count >= 20: return JsonResponse({'error': '上传太频繁,请稍后再试'}, status=429) cache.set(user_key, count + 1, 60) image = request.FILES.get('image') if not image: return JsonResponse({'error': 'No image provided'}, status=400) ext = Path(image.name).suffix.lower() if ext not in settings.ALLOWED_UPLOAD_IMAGE_EXTENSIONS: return JsonResponse({'error': 'Unsupported image extension'}, status=400) content_type = (image.content_type or '').lower() if content_type not in settings.ALLOWED_UPLOAD_IMAGE_CONTENT_TYPES: return JsonResponse({'error': 'Unsupported image content type'}, status=400) if image.size > settings.MAX_UPLOAD_IMAGE_SIZE: return JsonResponse({'error': 'Image file too large'}, status=400) filename = f"dishes/{uuid.uuid4().hex}{ext}" saved_path = default_storage.save(filename, image) media_path = str(saved_path).lstrip('/') media_url = urljoin(settings.MEDIA_URL, media_path) return JsonResponse({'url': media_url}) class DishCategoryViewSet(viewsets.ModelViewSet): """分类视图""" queryset = DishCategory.objects.all() serializer_class = DishCategorySerializer pagination_class = None class DishViewSet(viewsets.ModelViewSet): """菜品视图""" serializer_class = DishSerializer pagination_class = None def get_queryset(self): category_id = self.request.query_params.get('category') search = self.request.query_params.get('search') return DishService.get_queryset(category_id, search) def retrieve(self, request, *args, **kwargs): instance = self.get_object() DishService.increment_view_count(instance) serializer = self.get_serializer(instance) return Response(serializer.data) class OrderViewSet(viewsets.ModelViewSet): """订单视图""" serializer_class = OrderSerializer pagination_class = None def get_queryset(self): status_filter = self.request.query_params.get('status') return OrderService.get_queryset(status_filter) def create(self, request, *args, **kwargs): request_data = request.data.copy() dish_ids = request_data.pop('dish_ids', []) serializer = self.get_serializer(data=request_data) serializer.is_valid(raise_exception=True) order = OrderService.create_order(serializer, dish_ids) response_serializer = self.get_serializer(order) return Response(response_serializer.data, status=status.HTTP_201_CREATED) @action(detail=True, methods=['post']) def complete(self, request, pk=None): order = self.get_object() order = OrderService.complete_order(order) serializer = self.get_serializer(order) return Response(serializer.data) @action(detail=True, methods=['get']) def share(self, request, pk=None): order = self.get_object() serializer = self.get_serializer(order) return Response(serializer.data) class ParseUrlView(APIView): """解析外部链接""" throttle_scope = 'parse-url' permission_classes = [AllowAny] def post(self, request): url = request.data.get('url', '') try: result = ParserService.parse_url(url) return Response(result) except ValueError as e: return Response({'error': f'解析失败: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST) except TypeError as e: return Response({'error': f'解析失败: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST) except Exception as e: return Response({'error': f'解析失败: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)