from django.shortcuts import render, get_object_or_404 from rest_framework import viewsets, permissions, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.views import APIView from django.db.models import Q, Count, Sum from django.utils import timezone from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from .models import Food, UserFood, FoodUsageLog from .serializers import ( FoodSerializer, FoodListSerializer, UserFoodSerializer, FoodUsageLogSerializer, UseFoodSerializer ) from userapp.authentication import RedisTokenAuthentication class FoodViewSet(viewsets.ModelViewSet): """ 食物管理API 提供食物的完整CRUD功能,包括: - 获取食物列表 - 获取食物详情 - 创建新食物 - 更新食物信息 - 删除食物 - 按类型、稀有度筛选 - 搜索功能 """ permission_classes = [permissions.IsAuthenticated] authentication_classes = [RedisTokenAuthentication] def get_serializer_class(self): if self.action == 'list': return FoodListSerializer return FoodSerializer def get_queryset(self): """获取食物列表""" # 管理员可以看到所有食物,普通用户只能看到已发布的食物 if self.request.user.is_staff or self.request.user.is_superuser: queryset = Food.objects.all() elif self.action in ['list', 'retrieve']: queryset = Food.objects.filter(status='published') else: queryset = Food.objects.all() # 筛选参数 food_type = self.request.query_params.get('food_type') rarity = self.request.query_params.get('rarity') search = self.request.query_params.get('search') if food_type: queryset = queryset.filter(food_type=food_type) if rarity: queryset = queryset.filter(rarity=rarity) if search: queryset = queryset.filter( Q(name__icontains=search) | Q(description__icontains=search) | Q(taste_tags__icontains=search) ) return queryset.order_by('-created_at') @swagger_auto_schema( operation_description="获取食物分类信息", responses={200: openapi.Response( description="分类信息", examples={ "application/json": { "food_types": [["fruit", "水果"], ["vegetable", "蔬菜"]], "rarities": [["common", "普通"], ["rare", "稀有"]], "statuses": [["draft", "草稿"], ["published", "已发布"]] } } )} ) @action(detail=False, methods=['get']) def categories(self, request): """获取食物类型列表""" return Response({ 'food_types': Food.TYPE_CHOICES, 'rarities': Food.RARITY_CHOICES, 'statuses': Food.STATUS_CHOICES }) @swagger_auto_schema( operation_description="发布食物", responses={ 200: openapi.Response(description="发布成功"), 400: "食物状态不允许发布" } ) @action(detail=True, methods=['post']) def publish(self, request, pk=None): """发布食物""" food = self.get_object() if food.status == 'published': return Response( {'error': '该食物已经是发布状态'}, status=status.HTTP_400_BAD_REQUEST ) if food.status == 'archived': return Response( {'error': '已归档的食物不能发布'}, status=status.HTTP_400_BAD_REQUEST ) food.publish() serializer = self.get_serializer(food) return Response({ 'message': f'食物 "{food.name}" 发布成功', 'food': serializer.data }) @swagger_auto_schema( operation_description="归档食物", responses={ 200: openapi.Response(description="归档成功"), 400: "食物状态不允许归档" } ) @action(detail=True, methods=['post']) def archive(self, request, pk=None): """归档食物""" food = self.get_object() if food.status == 'archived': return Response( {'error': '该食物已经是归档状态'}, status=status.HTTP_400_BAD_REQUEST ) food.archive() serializer = self.get_serializer(food) return Response({ 'message': f'食物 "{food.name}" 已归档', 'food': serializer.data }) class UserFoodViewSet(viewsets.ReadOnlyModelViewSet): """ 用户食物管理API 用户查看自己拥有的食物,包括: - 查看拥有的食物列表 - 查看食物详情和使用状态 - 查看获得时间和方式 """ serializer_class = UserFoodSerializer permission_classes = [permissions.IsAuthenticated] def get_queryset(self): """获取当前用户的食物""" return UserFood.objects.filter( user=self.request.user ).select_related('food', 'user').order_by('-obtained_at') class FoodUsageLogViewSet(viewsets.ReadOnlyModelViewSet): """ 食物使用记录API 用户查看自己的食物使用历史,包括: - 使用时间和场景 - 应用的效果 - 使用备注 """ serializer_class = FoodUsageLogSerializer permission_classes = [permissions.IsAuthenticated] def get_queryset(self): """获取当前用户的使用记录""" return FoodUsageLog.objects.filter( user=self.request.user ).select_related('food', 'user').order_by('-used_at') class UseFoodView(APIView): """ 使用食物API 允许用户使用拥有的食物,记录使用效果和场景 """ permission_classes = [permissions.IsAuthenticated] @swagger_auto_schema( operation_description="使用食物", request_body=UseFoodSerializer, responses={ 200: openapi.Response( description="使用成功", examples={ "application/json": { "message": "食物使用成功", "remaining_quantity": 2, "effect_applied": {"health": 10, "energy": 5}, "usage_log_id": 123 } } ), 400: "请求参数错误或食物无法使用" } ) def post(self, request): """使用食物""" serializer = UseFoodSerializer(data=request.data) if not serializer.is_valid(): return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST ) food_id = serializer.validated_data['food_id'] usage_context = serializer.validated_data.get('usage_context', '') notes = serializer.validated_data.get('notes', '') # 获取用户食物 try: user_food = UserFood.objects.get( user=request.user, food_id=food_id ) except UserFood.DoesNotExist: return Response( {'error': '您没有这个食物'}, status=status.HTTP_400_BAD_REQUEST ) # 检查是否可以使用 if not user_food.can_use(): return Response( {'error': '食物无法使用'}, status=status.HTTP_400_BAD_REQUEST ) # 使用食物 success = user_food.use_food() if not success: return Response( {'error': '食物使用失败'}, status=status.HTTP_400_BAD_REQUEST ) # 创建使用记录 usage_log = FoodUsageLog.objects.create( user=request.user, food=user_food.food, usage_context=usage_context, notes=notes, effect_applied=user_food.food.boost_attributes ) return Response({ 'message': '食物使用成功', 'remaining_quantity': user_food.quantity, 'effect_applied': user_food.food.boost_attributes, 'usage_log_id': usage_log.id }) class MyFoodsView(APIView): """ 我的食物概览API 获取用户的食物统计信息和概览数据 """ permission_classes = [permissions.IsAuthenticated] @swagger_auto_schema( operation_description="获取用户的食物统计概览", responses={ 200: openapi.Response( description="用户食物统计", examples={ "application/json": { "total_foods": 15, "total_quantity": 48, "type_stats": { "fruit": {"name": "水果", "count": 5}, "vegetable": {"name": "蔬菜", "count": 3} }, "rarity_stats": { "common": {"name": "普通", "count": 10}, "rare": {"name": "稀有", "count": 2} } } } ) } ) def get(self, request): """获取用户的食物统计""" user_foods = UserFood.objects.filter(user=request.user) # 统计数据 total_foods = user_foods.count() total_quantity = user_foods.aggregate( total=Sum('quantity') )['total'] or 0 # 按类型统计 type_stats = {} for food_type, display_name in Food.TYPE_CHOICES: count = user_foods.filter(food__food_type=food_type).count() if count > 0: type_stats[food_type] = { 'name': display_name, 'count': count } # 按稀有度统计 rarity_stats = {} for rarity, display_name in Food.RARITY_CHOICES: count = user_foods.filter(food__rarity=rarity).count() if count > 0: rarity_stats[rarity] = { 'name': display_name, 'count': count } # 最近获得的食物 recent_foods = user_foods.order_by('-obtained_at')[:5] recent_foods_data = UserFoodSerializer(recent_foods, many=True).data return Response({ 'total_foods': total_foods, 'total_quantity': total_quantity, 'type_stats': type_stats, 'rarity_stats': rarity_stats, 'recent_foods': recent_foods_data }) class FoodStatsView(APIView): """ 食物系统统计API 获取整个食物系统的统计数据 """ permission_classes = [permissions.IsAuthenticated] @swagger_auto_schema( operation_description="获取食物系统统计数据", responses={ 200: openapi.Response( description="系统统计数据", examples={ "application/json": { "total_foods": 50, "total_users_with_foods": 20, "total_usage_count": 150, "popular_foods": [ {"food__name": "苹果", "food__id": 1, "user_count": 15} ], "most_used_foods": [ {"food__name": "面包", "food__id": 2, "usage_count": 30} ] } } ) } ) def get(self, request): """获取食物系统统计数据""" # 总体统计 total_foods = Food.objects.filter(status='published').count() total_users_with_foods = UserFood.objects.values('user').distinct().count() total_usage_count = FoodUsageLog.objects.count() # 最受欢迎的食物(被最多用户拥有) popular_foods = ( UserFood.objects .values('food__name', 'food__id') .annotate(user_count=Count('user')) .order_by('-user_count')[:5] ) # 使用最多的食物 most_used_foods = ( FoodUsageLog.objects .values('food__name', 'food__id') .annotate(usage_count=Count('id')) .order_by('-usage_count')[:5] ) return Response({ 'total_foods': total_foods, 'total_users_with_foods': total_users_with_foods, 'total_usage_count': total_usage_count, 'popular_foods': popular_foods, 'most_used_foods': most_used_foods })