import logging from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from .models import Notification from .serializers import NotificationSerializer logger = logging.getLogger(__name__) def _safe_int(value, default=0): """安全转 int — 防止前端传非数字字符导致 500。""" try: return int(value) except (TypeError, ValueError): return default @api_view(['GET']) @permission_classes([IsAuthenticated]) def notifications_list_view(request): """GET /api/v1/notifications/ Query params: unread_only: 'true' / 'false' (default 'false') page: 默认 1 page_size: 默认 20, 上限 100 Response: { "total": int, # 当前过滤条件下总条数 "unread_count": int, # 该用户全部未读数(不受 unread_only/分页影响) "page": int, "page_size": int, "results": [...] } """ user = request.user unread_only_raw = (request.query_params.get('unread_only') or 'false').strip().lower() unread_only = unread_only_raw in ('true', '1', 'yes') page = max(_safe_int(request.query_params.get('page'), 1), 1) page_size = _safe_int(request.query_params.get('page_size'), 20) if page_size <= 0: page_size = 20 page_size = min(page_size, 100) base_qs = Notification.objects.filter(recipient=user) qs = base_qs if unread_only: qs = qs.filter(is_read=False) total = qs.count() # unread_count 必须基于该用户全部通知,不受 unread_only/分页影响 unread_count = base_qs.filter(is_read=False).count() offset = (page - 1) * page_size records = list(qs.order_by('-created_at')[offset:offset + page_size]) results = NotificationSerializer(records, many=True).data return Response({ 'total': total, 'unread_count': unread_count, 'page': page, 'page_size': page_size, 'results': results, }) @api_view(['GET']) @permission_classes([IsAuthenticated]) def notifications_unread_count_view(request): """GET /api/v1/notifications/unread-count 前端 60s 轮询,只拿数字不拉列表。 """ count = Notification.objects.filter(recipient=request.user, is_read=False).count() return Response({'unread_count': count}) @api_view(['PATCH']) @permission_classes([IsAuthenticated]) def notification_mark_read_view(request, notification_id): """PATCH /api/v1/notifications//read 标记某条通知为已读。404 if not found 或不属于当前用户。 """ try: notification = Notification.objects.get(pk=notification_id, recipient=request.user) except Notification.DoesNotExist: return Response({'error': '通知不存在'}, status=status.HTTP_404_NOT_FOUND) if not notification.is_read: notification.is_read = True notification.save(update_fields=['is_read']) return Response({'id': notification.id, 'is_read': True}) @api_view(['POST']) @permission_classes([IsAuthenticated]) def notifications_mark_all_read_view(request): """POST /api/v1/notifications/read-all 一键已读。返回被标已读的条数。 """ updated = Notification.objects.filter( recipient=request.user, is_read=False ).update(is_read=True) return Response({'updated': updated})