lty/qy_lty/food_app/views.py
pmc bd95ba470c feat: update admin panel, API modules, and add migrations
- Update food, outfits, props, home-decor pages and components
- Add permissions page and sidebar updates
- Update API client and all API modules (auth, food, dances, etc.)
- Add card model migrations for optional fields
- Update Django views, serializers, and authentication
- Add affinity level migrations and user app updates
- Add project documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 13:06:50 +08:00

394 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
})