Finishing a lesson or island needs recorded answers and an unlocked island; auto DB backup before migrate; throttles on 15 more routes
- content: POST nodes/{id}/quiz/complete and lessons/{id}/complete refuse a locked island
(403 node_locked, same rule as /api/home) and a user who has not answered every question
(409 answers_missing). Before, only a user with zero hearts and no answers was refused, so
anyone else finished any island, locked or not, and finishing a locked island's lessons
unlocked it. A node or lesson with no questions still passes when unlocked, with no XP.
The client can no longer understate totalQuestions to buy stars.
- throttle_scope on 15 routes that had none (question bank without login, intro, hearts,
notifications, tickets, plans, subscription status, orders, offers, affiliate, assistant
history and feedback, logout).
- admin: OTPCode and Session can only be added/changed by a superuser.
- deploy.sh: integrity-checked DB backup before migrate (keeps the last 2); no migrate without it.
- docs and CLAUDE.md updated to say what is verified and what is not.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
27
content/testing.py
Normal file
27
content/testing.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""کمکتابعِ تستها: جوابِ ثبتشده مثلِ اپ (اپ هر سوال را جدا با /answer ثبت میکند و بعد پایان را میزند)."""
|
||||
from .models import Choice, Question, UserAnswer
|
||||
|
||||
|
||||
def record_answers(user_id, *, lesson=None, node=None, correct=True):
|
||||
"""
|
||||
به همهی سوالهای یک درس یا یک جزیره جوابِ ثبتشده میدهد (مستقیم در DB، بیقلب و بیthrottle).
|
||||
`correct=True` گزینهی درست، `False` گزینهی غلط را میزند؛ سوالی که آن گزینه را ندارد
|
||||
گزینهاش ساخته میشود. سوالی که پیشتر جواب داده شده دست نمیخورد. تعدادِ سوالهای جوابِدادهشده را برمیگرداند.
|
||||
"""
|
||||
questions = Question.objects.all()
|
||||
if lesson is not None:
|
||||
questions = questions.filter(lesson=lesson)
|
||||
if node is not None:
|
||||
questions = questions.filter(lesson__node=node)
|
||||
count = 0
|
||||
for question in questions.prefetch_related('choices'):
|
||||
choice = next((c for c in question.choices.all() if c.is_correct == correct), None)
|
||||
if choice is None:
|
||||
choice = Choice.objects.create(
|
||||
question=question, value='درست' if correct else 'غلط', is_correct=correct)
|
||||
UserAnswer.objects.get_or_create(
|
||||
user_id=user_id, question=question,
|
||||
defaults={'choice': choice, 'correct': correct, 'xp_earned': 10 if correct else 0},
|
||||
)
|
||||
count += 1
|
||||
return count
|
||||
Reference in New Issue
Block a user