give content editors a drill-down browser and per-question signals
پیدا کردنِ «سوالهای فلان مبحث» در فهرستِ پیشفرضِ جنگو عملاً ممکن نبود: باید
بینِ چند صفحهی جدا با دراپداونهای هزارتایی بالا و پایین میرفتی.
/admin/content/question/browse/ چهار ستونِ آبشاری دارد — پایه → فصل → درس →
مبحث — و هر ستون فقط وقتی پر میشود که قبلی انتخاب شده باشد، پس هیچوقت
فهرستِ هزارتایی جلوی چشم نیست. تهاش سوالهای همان مبحث با گزینههایشان
میآیند و از همانجا ویرایش یا اضافه میشوند.
کنارِ هر سوال دو عددِ تصمیمساز: گزارشهای بازِ کاربران، و درصدِ پاسخِ غلط.
درصد زیرِ ۵ پاسخ نشان داده نمیشود — یک نمونهی کوچک سوالِ سالم را
«مشکلدار» جلوه میدهد. همین دو ستون به فهرستِ سوالها هم اضافه شدند.
content.QuestionReport و POST /api/questions/{id}/report تازهاند: تنها
بازخوردی که تا امروز جمع میشد مالِ چتِ دستیار بود، و روی سوالهای مسیرِ
آموزشی هیچ راهی نبود که کاربر بگوید گزینهی درست غلط است. گزارشِ بازِ تکراریِ
همان کاربر ردیفِ تازه نمیسازد تا دابلکلیک آمار را بههم نزند.
وابستگیِ تازهای اضافه نشد: چند اندپوینتِ کوچکِ JSON و JavaScript ساده.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,8 @@ from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework import status
|
||||
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import ScopedRateThrottle
|
||||
@@ -13,7 +14,10 @@ from rest_framework.views import APIView
|
||||
from progress.models import UserHearts, UserStats
|
||||
from subscription.services import has_unlimited_hearts
|
||||
|
||||
from .models import Choice, Grade, Lesson, NodeProgress, Question, UserAnswer, UserLessonCompletion
|
||||
from .models import (
|
||||
Choice, Grade, Lesson, NodeProgress, Question, QuestionReport, UserAnswer,
|
||||
UserLessonCompletion,
|
||||
)
|
||||
from .serializers import (
|
||||
AnswerRequestSerializer,
|
||||
AnswerResponseSerializer,
|
||||
@@ -24,6 +28,8 @@ from .serializers import (
|
||||
LessonIntroResponseSerializer,
|
||||
LessonQuestionsResponseSerializer,
|
||||
NodeProgressListResponseSerializer,
|
||||
QuestionReportRequestSerializer,
|
||||
QuestionReportResponseSerializer,
|
||||
)
|
||||
|
||||
|
||||
@@ -535,3 +541,51 @@ class NodeProgressListView(APIView):
|
||||
|
||||
data = {'progress': list(qs.order_by('node__unit_id', 'node__order'))}
|
||||
return Response(NodeProgressListResponseSerializer(data).data)
|
||||
|
||||
|
||||
class QuestionReportView(APIView):
|
||||
"""
|
||||
POST /api/questions/{id}/report — «این سوال ایراد دارد»
|
||||
|
||||
چرا لازم بود: تنها بازخوردی که تا امروز جمع میشد مالِ چتِ دستیار بود.
|
||||
روی خودِ سوالهای مسیرِ آموزشی هیچ راهی نبود که کاربر بگوید گزینهی درست
|
||||
غلط است یا صورتِ سوال گنگ — و کسی که محتوا را نگه میدارد هیچ سیگنالی
|
||||
نداشت جز حدس.
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
throttle_scope = 'question_report'
|
||||
throttle_classes = [ScopedRateThrottle]
|
||||
|
||||
@extend_schema(
|
||||
request=QuestionReportRequestSerializer,
|
||||
responses=QuestionReportResponseSerializer,
|
||||
tags=['محتوا (content)'],
|
||||
)
|
||||
def post(self, request, question_id: int):
|
||||
question = Question.objects.filter(pk=question_id).first()
|
||||
if question is None:
|
||||
raise NotFound('سوال یافت نشد.')
|
||||
|
||||
serializer = QuestionReportRequestSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
# گزارشِ بازِ تکراریِ همان کاربر روی همان سوال، ردیفِ تازه نمیسازد:
|
||||
# دابلکلیک نباید صف را شلوغ کند و آمار را بههم بزند.
|
||||
report, created = QuestionReport.objects.get_or_create(
|
||||
question=question,
|
||||
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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user