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:
2026-10-12 12:56:47 +03:30
parent b075b93f2a
commit 2e0753587c
12 changed files with 1331 additions and 951 deletions

View File

@@ -398,3 +398,50 @@ class SeedRecord(models.Model):
def __str__(self):
return f'seed-{self.kind}-{self.key[:8]}'
class LessonReport(models.Model):
"""گزارشِ ایراد روی متنِ یک درس (توضیح، مثال، قدم‌ها)؛ همتای `QuestionReport`."""
REASON_CHOICES = [
('wrong_content', 'مطلبِ درس اشتباه است'),
('unclear', 'توضیح گنگ است'),
('typo', 'غلطِ املایی/نگارشی'),
('bad_figure', 'شکل اشکال دارد'),
('other', 'موردِ دیگر'),
]
STATUS_CHOICES = [('open', 'باز'), ('resolved', 'بررسی‌شده'), ('rejected', 'رد شد')]
lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, related_name='reports')
user_id = models.IntegerField(db_index=True) # ارجاع سبک به accounts.User
reason = models.CharField(max_length=20, choices=REASON_CHOICES, default='other')
note = models.TextField(blank=True)
status = models.CharField(max_length=15, choices=STATUS_CHOICES, default='open', db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
resolved_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ['-created_at']
indexes = [models.Index(fields=['lesson', 'status'])]
def __str__(self):
return f'report on lesson {self.lesson_id} ({self.get_reason_display()})'
class ContentVote(models.Model):
"""
لایک/دیسلایکِ دانش‌آموز روی یک سوال یا یک درس. هر کاربر روی هر هدف یک رأی دارد (تغییرپذیر).
ارجاعِ سبک (kind + target_id) تا یک جدول هر دو را بپوشاند؛ شمارش در ادمین با Subquery است.
"""
KIND_CHOICES = [('question', 'سوال'), ('lesson', 'درس')]
kind = models.CharField(max_length=10, choices=KIND_CHOICES)
target_id = models.IntegerField()
user_id = models.IntegerField()
value = models.SmallIntegerField() # ۱ = لایک، −۱ = دیسلایک
updated_at = models.DateTimeField(auto_now=True)
class Meta:
constraints = [models.UniqueConstraint(fields=['kind', 'target_id', 'user_id'], name='one_vote_per_user_target')]
indexes = [models.Index(fields=['kind', 'target_id'])]