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