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:
@@ -204,6 +204,8 @@ def _sync_questions(rows, ledger, counts):
|
||||
to_create.append(({**{k: v for k, v in row.items() if k != 'path'}, 'lessonId': lesson_id}, qkey))
|
||||
existing.add((lesson_id, prompt))
|
||||
|
||||
_refresh_practice(rows, lessons, ledger, counts)
|
||||
|
||||
if missing:
|
||||
raise question_io.ImportError_(
|
||||
'این درسها در برنامهی درسی پیدا نشدند (اول کوریکولوم را وارد کنید): '
|
||||
@@ -216,3 +218,61 @@ def _sync_questions(rows, ledger, counts):
|
||||
counts['created'] += result['created']
|
||||
for _row, qkey in to_create:
|
||||
ledger.record(qkey, 'question')
|
||||
|
||||
|
||||
PRACTICE_PREFIX = 'تمرین: '
|
||||
|
||||
|
||||
def _refresh_practice(rows, lessons, ledger, counts):
|
||||
"""
|
||||
جزیرههای «تمرین: …» را فایل میسازد و ادمین سوالهایش را کموبیش دست نمیزند؛ پس اصلاحِ فایل
|
||||
(گزینهی همارزِ جواب، جوابِ غلط) باید به سوالِ موجود برسد، وگرنه سید فقط «سوالِ تازه» میسازد و نسخهی
|
||||
معیوب میماند:
|
||||
- همان صورت، گزینهها/پاسخِ درست فرق دارد → گزینهها جا به جا (همان idها) بهروز میشود تا پاسخهای
|
||||
ثبتشده نشکنند؛
|
||||
- سوالی که خودِ سید قبلاً تحویل داده (دفتر) و حالا در فایل نیست و کسی جوابش نداده → پاک میشود.
|
||||
سوالِ دستیِ ادمین (در دفتر نیست) هرگز دست نمیخورد. آزموده: content/tests_seed_content.py.
|
||||
"""
|
||||
practice_lessons = {
|
||||
lid for path, lid in lessons.items() if path[2].startswith(PRACTICE_PREFIX)
|
||||
}
|
||||
if not practice_lessons:
|
||||
return
|
||||
want = {}
|
||||
file_keys = set()
|
||||
for row in rows:
|
||||
path = tuple(str(p).strip() for p in row.get('path', []))
|
||||
if len(path) != 4 or not path[2].startswith(PRACTICE_PREFIX):
|
||||
continue
|
||||
lid = lessons.get(path)
|
||||
prompt = (row.get('prompt') or '').strip()
|
||||
if lid is None:
|
||||
continue
|
||||
want[(lid, prompt)] = row
|
||||
file_keys.add(_key('question', *path, prompt))
|
||||
by_id = {}
|
||||
for lid, path in ((v, k) for k, v in lessons.items() if v in practice_lessons):
|
||||
by_id[lid] = path
|
||||
qs = Question.objects.filter(lesson_id__in=practice_lessons).prefetch_related('choices')
|
||||
stale = []
|
||||
for q in qs.iterator(chunk_size=500):
|
||||
row = want.get((q.lesson_id, q.prompt))
|
||||
if row is None:
|
||||
if _key('question', *by_id[q.lesson_id], q.prompt) in ledger.known:
|
||||
stale.append(q.pk)
|
||||
continue
|
||||
new_choices = [str(c).strip() for c in row['choices']]
|
||||
old = sorted(q.choices.all(), key=lambda c: c.pk)
|
||||
if len(old) != len(new_choices):
|
||||
continue
|
||||
for index, choice in enumerate(old):
|
||||
value, correct = new_choices[index], index == row['correct']
|
||||
if choice.value != value or choice.is_correct != correct:
|
||||
choice.value, choice.is_correct = value, correct
|
||||
choice.save(update_fields=['value', 'is_correct'])
|
||||
counts['updated'] += 1
|
||||
if stale:
|
||||
removable = list(Question.objects.filter(pk__in=stale, answers__isnull=True).values_list('pk', flat=True))
|
||||
if removable:
|
||||
Question.objects.filter(pk__in=removable).delete()
|
||||
counts['retired'] += len(removable)
|
||||
|
||||
Reference in New Issue
Block a user