API output without Persian diacritics or written ezafe ی (company decision); tutor answers too
plain_persian() strips kasra/fatha/damma/shadda/sukun/hamza-above and the written «هی» ezafe, keeping tanwin. Applied at output, not in data: PlainPersianJSONRenderer runs one byte-level pass over every JSON response (~5 ms per MB locally), and to_plain() cleans tutor answers including the stream. Rewriting titles in the DB/seed would have broken question binding by title path and SeedRecord keys. Tests updated to expect the cleaned text; config/tests_plain_persian.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
"""
|
||||
پایانِ آزمونِ جزیره → جزیرهی بعدی باز میشود.
|
||||
پایان آزمون جزیره → جزیره بعدی باز میشود.
|
||||
|
||||
شکافی که این تستها میبندند: اپ با آزمونِ چهارسوالیِ هر جزیره بازی
|
||||
میکند، ولی جزیره فقط وقتی «تمام» حساب میشد که تکتکِ درسهایش (۸ تا ۱۳
|
||||
درس) جدا تمام شده باشند؛ دانشآموز بعد از جزیرهی اولِ هر فصل گیر میکرد.
|
||||
شکافی که این تستها میبندند: اپ با آزمون چهارسوالی هر جزیره بازی
|
||||
میکند، ولی جزیره فقط وقتی «تمام» حساب میشد که تکتک درسهایش (۸ تا ۱۳
|
||||
درس) جدا تمام شده باشند؛ دانشآموز بعد از جزیره اول هر فصل گیر میکرد.
|
||||
"""
|
||||
from subscription.models import Subscription
|
||||
from django.core.cache import cache
|
||||
@@ -28,7 +28,7 @@ class NodeQuizCompleteTests(TestCase):
|
||||
self.next_unit = Unit.objects.create(grade=self.grade, title='فصل ۲', order=2)
|
||||
self.first = self._node(self.unit, 'اول', 1, lessons=5)
|
||||
self.second = self._node(self.unit, 'دوم', 2, lessons=3)
|
||||
self.other_unit_first = self._node(self.next_unit, 'فصلِ بعد', 1, lessons=2)
|
||||
self.other_unit_first = self._node(self.next_unit, 'فصل بعد', 1, lessons=2)
|
||||
|
||||
self.user = User.objects.create(phone='09120000009', grade_id=self.grade.id)
|
||||
# فصلِ دوم اشتراک میخواهد (content.views.unit_is_free)؛ این تست ترتیبِ باز شدن را میسنجد، نه پرداخت.
|
||||
@@ -46,7 +46,7 @@ class NodeQuizCompleteTests(TestCase):
|
||||
return node
|
||||
|
||||
def _answer(self, node, correct, wrong=0):
|
||||
"""مثلِ اپ: اول جوابها ثبت میشوند (`correct` تای اول درست، `wrong` تای بعدی غلط)، بعد پایانِ آزمون."""
|
||||
"""مثل اپ: اول جوابها ثبت میشوند (`correct` تای اول درست، `wrong` تای بعدی غلط)، بعد پایان آزمون."""
|
||||
questions = list(Question.objects.filter(lesson__node=node).order_by('id')[:correct + wrong])
|
||||
for index, question in enumerate(questions):
|
||||
choice = question.choices.get(is_correct=index < correct)
|
||||
@@ -122,7 +122,7 @@ class NodeQuizCompleteTests(TestCase):
|
||||
self.assertEqual(body['xpEarned'], 20)
|
||||
|
||||
def test_understating_the_question_count_does_not_buy_stars(self):
|
||||
"""با totalQuestions=1 و یک جوابِ درست ۳ ستاره میشد؛ آزمونِ استانداردِ سرور ۴ سوال است."""
|
||||
"""با totalQuestions=1 و یک جواب درست ۳ ستاره میشد؛ آزمون استاندارد سرور ۴ سوال است."""
|
||||
self._answer(self.first, correct=1, wrong=3)
|
||||
body = self._complete(self.first, correct=1, total=1, answer=False).json()
|
||||
self.assertEqual(body['stars'], 1) # ۱ از ۴
|
||||
@@ -135,7 +135,7 @@ class NodeQuizCompleteTests(TestCase):
|
||||
self.assertEqual((response.json()['answered'], response.json()['required']), (3, 4))
|
||||
|
||||
def test_a_locked_island_cannot_be_finished(self):
|
||||
"""جزیرهی دوم تا اولی تمام نشده قفل است؛ حتی با جوابِ همهی سوالهایش."""
|
||||
"""جزیره دوم تا اولی تمام نشده قفل است؛ حتی با جواب همه سوالهایش."""
|
||||
self._answer(self.second, correct=3)
|
||||
response = self._complete(self.second, correct=3, total=3, answer=False)
|
||||
self.assertEqual((response.status_code, response.json()['code']), (403, 'node_locked'))
|
||||
@@ -143,14 +143,14 @@ class NodeQuizCompleteTests(TestCase):
|
||||
self.assertEqual(self._statuses()['دوم'], 'locked')
|
||||
|
||||
def test_the_first_island_of_every_unit_is_open(self):
|
||||
"""همانطور که نقشه نشان میدهد: اولین جزیرهی فصلها بیقید باز است."""
|
||||
self.assertEqual(self._statuses()['فصلِ بعد'], 'unlocked')
|
||||
"""همانطور که نقشه نشان میدهد: اولین جزیره فصلها بیقید باز است."""
|
||||
self.assertEqual(self._statuses()['فصل بعد'], 'unlocked')
|
||||
self.assertEqual(self._complete(self.other_unit_first, correct=2, total=2).status_code, 200)
|
||||
|
||||
def test_an_open_island_without_questions_passes_without_answers_and_earns_nothing(self):
|
||||
"""چیزی برای جوابدادن نیست؛ رد میشود تا مسیرِ بقیه را نبندد (بیستاره، بیامتیاز)."""
|
||||
"""چیزی برای جوابدادن نیست؛ رد میشود تا مسیر بقیه را نبندد (بیستاره، بیامتیاز)."""
|
||||
empty = Node.objects.create(unit=self.next_unit, title='خالی', order=0) # اولین جزیرهی فصل: باز
|
||||
Lesson.objects.create(node=empty, title='درسِ بیسوال')
|
||||
Lesson.objects.create(node=empty, title='درس بیسوال')
|
||||
response = self.client.post(
|
||||
f'/api/nodes/{empty.pk}/quiz/complete', {'correctCount': 0, 'totalQuestions': 1},
|
||||
content_type='application/json', **self.auth)
|
||||
@@ -159,8 +159,8 @@ class NodeQuizCompleteTests(TestCase):
|
||||
self.assertEqual(response.json()['nextNodeId'], str(self.other_unit_first.pk))
|
||||
|
||||
def test_a_locked_island_without_questions_is_still_locked(self):
|
||||
empty = Node.objects.create(unit=self.unit, title='خالیِ قفل', order=9) # بعد از دو جزیرهی تمامنشده
|
||||
Lesson.objects.create(node=empty, title='درسِ بیسوال')
|
||||
empty = Node.objects.create(unit=self.unit, title='خالی قفل', order=9) # بعد از دو جزیرهی تمامنشده
|
||||
Lesson.objects.create(node=empty, title='درس بیسوال')
|
||||
response = self.client.post(
|
||||
f'/api/nodes/{empty.pk}/quiz/complete', {'correctCount': 0, 'totalQuestions': 1},
|
||||
content_type='application/json', **self.auth)
|
||||
@@ -197,7 +197,7 @@ class NodeQuizCompleteTests(TestCase):
|
||||
self.assertIn('اول', notes.first().body)
|
||||
|
||||
def test_node_progress_is_completed_and_stays_completed(self):
|
||||
"""درسِ تازه در همان جزیره نباید آن را به in_progress برگرداند."""
|
||||
"""درس تازه در همان جزیره نباید آن را به in_progress برگرداند."""
|
||||
self._complete(self.first)
|
||||
lesson = self.first.lessons.first()
|
||||
record_answers(self.user.id, lesson=lesson)
|
||||
@@ -211,7 +211,7 @@ class NodeQuizCompleteTests(TestCase):
|
||||
self.assertEqual(progress.status, 'completed')
|
||||
|
||||
def test_progress_percent_counts_the_islands_lessons(self):
|
||||
"""جزیرهی ۵ درسی از ۱۰ درسِ پایه → ۵۰٪، و فقط در پایهی خودِ کاربر."""
|
||||
"""جزیره ۵ درسی از ۱۰ درس پایه → ۵۰٪، و فقط در پایه خود کاربر."""
|
||||
Grade.objects.create(title='پایه دهم', icon='', color='') # مخرج را بزرگ نکند
|
||||
body = self._complete(self.first).json()
|
||||
|
||||
@@ -234,7 +234,7 @@ class NodeQuizCompleteTests(TestCase):
|
||||
|
||||
|
||||
class NodeQuizStabilityTests(TestCase):
|
||||
"""آزمونِ یک جزیره برای یک کاربر ثابت میماند، حتی بعد از تمامکردنِ آزمون و حتی با استخرِ بزرگ."""
|
||||
"""آزمون یک جزیره برای یک کاربر ثابت میماند، حتی بعد از تمامکردن آزمون و حتی با استخر بزرگ."""
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
|
||||
Reference in New Issue
Block a user