progress/views.py
- GuestAwareReadThrottle (same guest-by-IP pattern as the other apps);
throttle_scope='read' on UserSummaryView, StreakCalendarView,
LeaderboardView.
notifications/views.py
- GuestAwareReadThrottle + throttle_scope='read' on NotificationListView.
- list capped at NOTIFICATIONS_MAX=100 newest (Meta.ordering already
newest-first); unreadCount still counted over the full set.
notifications/tests.py
- 150 rows -> 100 returned newest-first, unreadCount = 75.
rag/views.py
- ChunkListView: explicit order_by('-id') before the existing [:200] cap.
ai_tutor/views.py
- ConversationMessagesView: return the newest 200 messages in
chronological order instead of the whole conversation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
2.6 KiB
Python
59 lines
2.6 KiB
Python
from django.shortcuts import get_object_or_404
|
|
from drf_spectacular.utils import extend_schema
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from rest_framework.throttling import ScopedRateThrottle
|
|
from rest_framework.views import APIView
|
|
|
|
from .models import Notification
|
|
from .serializers import MarkReadResponseSerializer, NotificationListResponseSerializer
|
|
|
|
# سقفِ تعدادِ اعلانِ برگشتی؛ جلوی رشدِ نامحدودِ پاسخ برای کاربرِ قدیمی رو
|
|
# میگیره. صفحهبندیِ واقعی (cursor) وقتی فرانت infinite-scroll اضافه کنه.
|
|
NOTIFICATIONS_MAX = 100
|
|
|
|
|
|
class GuestAwareReadThrottle(ScopedRateThrottle):
|
|
"""همون الگوی GuestAware*Throttle: مهمان با IP کلید میخوره."""
|
|
|
|
def get_cache_key(self, request, view):
|
|
user = request.user
|
|
if user and user.is_authenticated and getattr(user, 'is_guest', False):
|
|
ident = f'guest-ip:{self.get_ident(request)}'
|
|
elif user and user.is_authenticated:
|
|
ident = user.pk
|
|
else:
|
|
ident = self.get_ident(request)
|
|
return self.cache_format % {'scope': self.scope, 'ident': ident}
|
|
|
|
|
|
class NotificationListView(APIView):
|
|
"""GET /api/notifications — فهرست اعلانهای کاربر (جدیدترین اول)"""
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
throttle_scope = 'read'
|
|
throttle_classes = [GuestAwareReadThrottle]
|
|
|
|
@extend_schema(responses=NotificationListResponseSerializer, tags=['Notifications (notifications)'])
|
|
def get(self, request):
|
|
qs = Notification.objects.filter(user_id=request.user.id) # Meta.ordering: جدیدترین اول
|
|
unread_count = qs.filter(is_read=False).count()
|
|
|
|
data = {'items': list(qs[:NOTIFICATIONS_MAX]), 'unreadCount': unread_count}
|
|
return Response(NotificationListResponseSerializer(data).data)
|
|
|
|
|
|
class NotificationMarkReadView(APIView):
|
|
"""PATCH /api/notifications/{id}/read — علامتگذاری یک اعلان بهعنوان خواندهشده"""
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
@extend_schema(responses=MarkReadResponseSerializer, tags=['Notifications (notifications)'])
|
|
def patch(self, request, id):
|
|
notification = get_object_or_404(Notification, pk=id, user_id=request.user.id)
|
|
if not notification.is_read:
|
|
notification.is_read = True
|
|
notification.save(update_fields=['is_read'])
|
|
|
|
return Response(MarkReadResponseSerializer({'success': True}).data)
|