Like/dislike + report under every question and lesson, lesson reports and vote columns in admin; practice islands refresh from file; audit fixes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ from .services import (
|
||||
playable_lesson_ids, quiz_completed_node_ids, verified_correct_count,
|
||||
)
|
||||
from .models import (
|
||||
Choice, Grade, Lesson, Node, NodeProgress, Question, QuestionReport, Subject, UserAnswer,
|
||||
Choice, ContentVote, Grade, Lesson, LessonReport, Node, NodeProgress, Question, QuestionReport, Subject, UserAnswer,
|
||||
UserLessonCompletion, UserNodeCompletion,
|
||||
)
|
||||
from .serializers import (
|
||||
@@ -37,6 +37,7 @@ from .serializers import (
|
||||
LessonCompleteRequestSerializer,
|
||||
LessonCompleteResponseSerializer,
|
||||
LessonIntroResponseSerializer,
|
||||
LessonReportRequestSerializer,
|
||||
LessonQuestionsResponseSerializer,
|
||||
NodeLessonsResponseSerializer,
|
||||
NodeQuizCompleteRequestSerializer,
|
||||
@@ -988,6 +989,79 @@ class QuestionReportView(APIView):
|
||||
)
|
||||
|
||||
|
||||
class _VoteBase(APIView):
|
||||
"""POST …/vote {value: 1|-1|0} — لایک/دیسلایکِ دانشآموز؛ ۰ یعنی رأی را پس میگیرد."""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
throttle_scope = 'content_vote'
|
||||
throttle_classes = [ScopedRateThrottle]
|
||||
kind = ''
|
||||
|
||||
def _exists(self, target_id) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def post(self, request, **kwargs):
|
||||
target_id = next(iter(kwargs.values()))
|
||||
if not self._exists(target_id):
|
||||
raise NotFound('پیدا نشد.')
|
||||
value = request.data.get('value')
|
||||
if value not in (1, -1, 0) or isinstance(value, bool):
|
||||
raise ValidationError({'value': 'باید ۱، −۱ یا ۰ باشد.'})
|
||||
if value == 0:
|
||||
ContentVote.objects.filter(kind=self.kind, target_id=target_id, user_id=request.user.id).delete()
|
||||
else:
|
||||
ContentVote.objects.update_or_create(
|
||||
kind=self.kind, target_id=target_id, user_id=request.user.id, defaults={'value': value})
|
||||
return Response({'value': value})
|
||||
|
||||
|
||||
class QuestionVoteView(_VoteBase):
|
||||
kind = 'question'
|
||||
|
||||
@extend_schema(request=None, responses=OpenApiTypes.OBJECT, tags=['Curriculum (content)'])
|
||||
def post(self, request, question_id: int):
|
||||
return super().post(request, question_id=question_id)
|
||||
|
||||
def _exists(self, target_id):
|
||||
return Question.objects.filter(pk=target_id).exists()
|
||||
|
||||
|
||||
class LessonVoteView(_VoteBase):
|
||||
kind = 'lesson'
|
||||
|
||||
@extend_schema(request=None, responses=OpenApiTypes.OBJECT, tags=['Curriculum (content)'])
|
||||
def post(self, request, lesson_id: int):
|
||||
return super().post(request, lesson_id=lesson_id)
|
||||
|
||||
def _exists(self, target_id):
|
||||
return Lesson.objects.filter(pk=target_id).exists()
|
||||
|
||||
|
||||
class LessonReportView(APIView):
|
||||
"""POST /api/lessons/{id}/report — «متنِ این درس ایراد دارد» (همتای گزارشِ سوال)."""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
throttle_scope = 'question_report'
|
||||
throttle_classes = [ScopedRateThrottle]
|
||||
|
||||
@extend_schema(request=LessonReportRequestSerializer, responses=QuestionReportResponseSerializer,
|
||||
tags=['Curriculum (content)'])
|
||||
def post(self, request, lesson_id: int):
|
||||
lesson = Lesson.objects.filter(pk=lesson_id).first()
|
||||
if lesson is None:
|
||||
raise NotFound('درس یافت نشد.')
|
||||
serializer = LessonReportRequestSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
report, created = LessonReport.objects.get_or_create(
|
||||
lesson=lesson, user_id=request.user.id, status='open',
|
||||
defaults={'reason': serializer.validated_data['reason'], 'note': serializer.validated_data.get('note', '')},
|
||||
)
|
||||
return Response(
|
||||
QuestionReportResponseSerializer({'success': True, 'reportId': report.pk, 'alreadyReported': not created}).data,
|
||||
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class NodeQuizView(APIView):
|
||||
"""
|
||||
GET /api/nodes/{id}/quiz — یک آزمونِ آماده برای کلِ مبحث.
|
||||
|
||||
Reference in New Issue
Block a user