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

52
content/tests_feedback.py Normal file
View File

@@ -0,0 +1,52 @@
"""لایک/دیسلایک و گزارشِ درس/سوال: API و ستون‌های ادمین."""
from django.core.cache import cache
from django.test import TestCase
from accounts.authentication import issue_access_token
from accounts.models import User
from content.models import Choice, ContentVote, Grade, Lesson, LessonReport, Node, Question, Unit
class FeedbackTests(TestCase):
def setUp(self):
cache.clear()
g = Grade.objects.create(title='پایه نهم', icon='', color='')
u = Unit.objects.create(grade=g, title='فصل ۱', order=1)
n = Node.objects.create(unit=u, title='مبحث', order=1)
self.lesson = Lesson.objects.create(node=n, title='درس')
self.q = Question.objects.create(lesson=self.lesson, type='multiple_choice', prompt='؟')
Choice.objects.create(question=self.q, value='الف', is_correct=True)
Choice.objects.create(question=self.q, value='ب', is_correct=False)
self.user = User.objects.create(phone='09120000051')
self.auth = {'HTTP_AUTHORIZATION': f'Bearer {issue_access_token(self.user.id)}'}
def _post(self, url, body):
return self.client.post(url, body, content_type='application/json', **self.auth)
def test_vote_toggle_and_retract(self):
url = f'/api/questions/{self.q.pk}/vote'
self.assertEqual(self._post(url, {'value': 1}).status_code, 200)
self.assertEqual(self._post(url, {'value': -1}).status_code, 200)
self.assertEqual(ContentVote.objects.get().value, -1)
self._post(url, {'value': 0})
self.assertEqual(ContentVote.objects.count(), 0)
def test_vote_rejects_bad_value_and_anonymous(self):
url = f'/api/lessons/{self.lesson.pk}/vote'
self.assertEqual(self._post(url, {'value': 5}).status_code, 400)
self.assertEqual(self.client.post(url, {'value': 1}, content_type='application/json').status_code, 401)
def test_lesson_report_is_deduplicated(self):
url = f'/api/lessons/{self.lesson.pk}/report'
r1 = self._post(url, {'reason': 'typo', 'note': 'غلط'})
r2 = self._post(url, {'reason': 'typo'})
self.assertEqual((r1.status_code, r2.status_code), (201, 200))
self.assertEqual(LessonReport.objects.count(), 1)
def test_admin_lists_show_votes(self):
ContentVote.objects.create(kind='question', target_id=self.q.pk, user_id=self.user.id, value=-1)
from django.contrib.auth.models import User as DjUser
DjUser.objects.create_superuser('root', 'r@x.ir', 'pw')
self.client.login(username='root', password='pw')
for path in ('/admin/content/question/', '/admin/content/lesson/', '/admin/content/lessonreport/'):
self.assertEqual(self.client.get(path).status_code, 200, path)