- 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>
251 lines
9.4 KiB
Python
251 lines
9.4 KiB
Python
from django.shortcuts import render
|
|
from rest_framework import viewsets, mixins, status, permissions
|
|
from rest_framework.decorators import action
|
|
from rest_framework.response import Response
|
|
from django.shortcuts import get_object_or_404
|
|
from django.db import transaction, models
|
|
from django.utils import timezone
|
|
from userapp.authentication import RedisTokenAuthentication
|
|
|
|
from .models import Achievement, UserAchievement
|
|
from .serializers import (
|
|
AchievementSerializer,
|
|
UserAchievementSerializer,
|
|
UserAchievementCreateSerializer,
|
|
UserAchievementListSerializer
|
|
)
|
|
|
|
class IsAdminOrReadOnly(permissions.BasePermission):
|
|
"""管理员可写,其他人只读"""
|
|
def has_permission(self, request, view):
|
|
if request.method in permissions.SAFE_METHODS:
|
|
return request.user and request.user.is_authenticated
|
|
return request.user and request.user.is_staff
|
|
|
|
|
|
class AchievementViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
成就相关视图集
|
|
管理员可增删改查,普通用户只读
|
|
"""
|
|
serializer_class = AchievementSerializer
|
|
permission_classes = [IsAdminOrReadOnly]
|
|
authentication_classes = [RedisTokenAuthentication]
|
|
|
|
def get_queryset(self):
|
|
"""根据请求过滤成就列表"""
|
|
queryset = Achievement.objects.all()
|
|
|
|
# 排除隐藏成就(除非客户端特别请求)
|
|
show_hidden = self.request.query_params.get('show_hidden', 'false').lower() == 'true'
|
|
if not show_hidden:
|
|
# 过滤出用户已获得的隐藏成就和非隐藏成就
|
|
user_achievement_ids = UserAchievement.objects.filter(
|
|
user=self.request.user
|
|
).values_list('achievement_id', flat=True)
|
|
|
|
queryset = queryset.filter(
|
|
# 非隐藏成就或已经获得的隐藏成就
|
|
models.Q(is_hidden=False) | models.Q(id__in=user_achievement_ids)
|
|
)
|
|
|
|
# 按类型过滤
|
|
achievement_type = self.request.query_params.get('type')
|
|
if achievement_type:
|
|
queryset = queryset.filter(achievement_type=achievement_type)
|
|
|
|
# 按稀有度过滤
|
|
rarity = self.request.query_params.get('rarity')
|
|
if rarity:
|
|
queryset = queryset.filter(rarity=rarity)
|
|
|
|
return queryset
|
|
|
|
@action(detail=True, methods=['get'])
|
|
def check_achievement(self, request, pk=None):
|
|
"""
|
|
检查当前用户是否获得了指定成就
|
|
"""
|
|
achievement = self.get_object()
|
|
has_achievement = UserAchievement.objects.filter(
|
|
user=request.user,
|
|
achievement=achievement
|
|
).exists()
|
|
|
|
return Response({
|
|
'has_achievement': has_achievement,
|
|
'achievement': AchievementSerializer(achievement).data
|
|
})
|
|
|
|
|
|
class UserAchievementViewSet(mixins.ListModelMixin,
|
|
mixins.RetrieveModelMixin,
|
|
viewsets.GenericViewSet):
|
|
"""
|
|
用户成就相关视图集
|
|
"""
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
authentication_classes = [RedisTokenAuthentication]
|
|
|
|
def get_serializer_class(self):
|
|
if self.action == 'create_achievement':
|
|
return UserAchievementCreateSerializer
|
|
elif self.action == 'list':
|
|
return UserAchievementListSerializer
|
|
return UserAchievementSerializer
|
|
|
|
def get_queryset(self):
|
|
"""获取当前用户的成就"""
|
|
return UserAchievement.objects.filter(user=self.request.user)
|
|
|
|
@action(detail=False, methods=['post'], permission_classes=[permissions.IsAdminUser])
|
|
def create_achievement(self, request):
|
|
"""
|
|
管理员手动授予用户成就
|
|
"""
|
|
serializer = self.get_serializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def check_and_grant(self, request):
|
|
"""
|
|
检查并授予成就
|
|
前端传递相关数据,后端根据成就条件检查是否满足条件并授予成就
|
|
"""
|
|
achievement_id = request.data.get('achievement_id')
|
|
check_data = request.data.get('check_data', {})
|
|
|
|
if not achievement_id:
|
|
return Response(
|
|
{"detail": "必须提供成就ID"},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
# 获取成就
|
|
achievement = get_object_or_404(Achievement, id=achievement_id)
|
|
|
|
# 检查用户是否已经获得该成就
|
|
if UserAchievement.objects.filter(
|
|
user=request.user,
|
|
achievement=achievement
|
|
).exists():
|
|
return Response(
|
|
{"detail": "用户已获得该成就"},
|
|
status=status.HTTP_200_OK
|
|
)
|
|
|
|
# TODO: 根据成就的conditions字段和前端传递的check_data检查是否满足条件
|
|
# 这部分逻辑需要根据具体成就类型和条件来实现
|
|
# 这里只是一个简化的示例
|
|
conditions_met = True # 默认满足条件
|
|
|
|
if conditions_met:
|
|
# 满足条件,授予成就
|
|
user_achievement = UserAchievement.objects.create(
|
|
user=request.user,
|
|
achievement=achievement,
|
|
acquisition_data=check_data
|
|
)
|
|
|
|
return Response({
|
|
"detail": "成就获得成功",
|
|
"achievement": AchievementSerializer(achievement).data
|
|
}, status=status.HTTP_201_CREATED)
|
|
else:
|
|
return Response({
|
|
"detail": "未满足成就条件"
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def unread_notifications(self, request):
|
|
"""
|
|
获取用户未读的成就通知
|
|
"""
|
|
unread_achievements = UserAchievement.objects.filter(
|
|
user=request.user,
|
|
notification_shown=False
|
|
)
|
|
|
|
serializer = UserAchievementSerializer(unread_achievements, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=True, methods=['post'])
|
|
def mark_notification_shown(self, request, pk=None):
|
|
"""
|
|
标记成就通知为已显示
|
|
"""
|
|
user_achievement = self.get_object()
|
|
user_achievement.notification_shown = True
|
|
user_achievement.save()
|
|
|
|
return Response({"detail": "成功标记为已显示"})
|
|
|
|
@action(detail=False, methods=['post'])
|
|
def mark_all_notifications_shown(self, request):
|
|
"""
|
|
标记所有成就通知为已显示
|
|
"""
|
|
UserAchievement.objects.filter(
|
|
user=request.user,
|
|
notification_shown=False
|
|
).update(notification_shown=True)
|
|
|
|
return Response({"detail": "所有通知已标记为已显示"})
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def stats(self, request):
|
|
"""
|
|
获取用户成就统计信息
|
|
"""
|
|
user = request.user
|
|
|
|
# 获取成就总数
|
|
total_achievements = Achievement.objects.count()
|
|
hidden_achievements = Achievement.objects.filter(is_hidden=True).count()
|
|
visible_achievements = total_achievements - hidden_achievements
|
|
|
|
# 获取用户已获得的成就数
|
|
user_achievements = UserAchievement.objects.filter(user=user).count()
|
|
|
|
# 按稀有度统计
|
|
rarity_stats = {}
|
|
for rarity_code, rarity_name in Achievement.RARITY_CHOICES:
|
|
total = Achievement.objects.filter(rarity=rarity_code).count()
|
|
acquired = UserAchievement.objects.filter(
|
|
user=user,
|
|
achievement__rarity=rarity_code
|
|
).count()
|
|
rarity_stats[rarity_code] = {
|
|
'name': rarity_name,
|
|
'total': total,
|
|
'acquired': acquired,
|
|
'percentage': round(acquired / total * 100, 2) if total > 0 else 0
|
|
}
|
|
|
|
# 按类型统计
|
|
type_stats = {}
|
|
for type_code, type_name in Achievement.TYPE_CHOICES:
|
|
total = Achievement.objects.filter(achievement_type=type_code).count()
|
|
acquired = UserAchievement.objects.filter(
|
|
user=user,
|
|
achievement__achievement_type=type_code
|
|
).count()
|
|
type_stats[type_code] = {
|
|
'name': type_name,
|
|
'total': total,
|
|
'acquired': acquired,
|
|
'percentage': round(acquired / total * 100, 2) if total > 0 else 0
|
|
}
|
|
|
|
return Response({
|
|
'total_achievements': total_achievements,
|
|
'visible_achievements': visible_achievements,
|
|
'hidden_achievements': hidden_achievements,
|
|
'user_achievements': user_achievements,
|
|
'completion_percentage': round(user_achievements / total_achievements * 100, 2) if total_achievements > 0 else 0,
|
|
'rarity_stats': rarity_stats,
|
|
'type_stats': type_stats
|
|
})
|