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:
@@ -15,7 +15,7 @@ from .curriculum_io import parse as parse_curriculum
|
||||
from .question_io import ImportError_, export_questions, import_questions, parse
|
||||
|
||||
from .models import (
|
||||
Choice, Grade, Lesson, Node, NodeProgress, Question, QuestionReport, Subject, Unit,
|
||||
Choice, ContentVote, Grade, Lesson, LessonReport, Node, NodeProgress, Question, QuestionReport, Subject, Unit,
|
||||
UserAnswer, UserLessonCompletion,
|
||||
)
|
||||
|
||||
@@ -108,9 +108,57 @@ class NodeAdmin(admin.ModelAdmin):
|
||||
search_fields = ('title', 'unit__title')
|
||||
|
||||
|
||||
def _vote_counts(kind):
|
||||
"""Subquery: (likes, dislikes) برای هر هدف؛ ContentVote ارجاعِ سبک است، پس annotate مستقیم ممکن نیست."""
|
||||
from django.db.models import IntegerField, OuterRef, Subquery
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
def sub(value):
|
||||
return Coalesce(Subquery(
|
||||
ContentVote.objects.filter(kind=kind, target_id=OuterRef('pk'), value=value)
|
||||
.order_by().values('target_id').annotate(n=Count('id')).values('n'),
|
||||
output_field=IntegerField()), 0)
|
||||
return sub(1), sub(-1)
|
||||
|
||||
|
||||
class VoteColumnsMixin:
|
||||
vote_kind = ''
|
||||
|
||||
def get_queryset(self, request):
|
||||
likes, dislikes = _vote_counts(self.vote_kind)
|
||||
return super().get_queryset(request).annotate(_likes=likes, _dislikes=dislikes)
|
||||
|
||||
@admin.display(description='👍', ordering='_likes')
|
||||
def likes(self, obj):
|
||||
return obj._likes or '—'
|
||||
|
||||
@admin.display(description='👎', ordering='_dislikes')
|
||||
def dislikes(self, obj):
|
||||
if not obj._dislikes:
|
||||
return '—'
|
||||
return format_html('<span style="color:#b91c1c;font-weight:600">{}</span>', obj._dislikes)
|
||||
|
||||
|
||||
@admin.register(LessonReport)
|
||||
class LessonReportAdmin(admin.ModelAdmin):
|
||||
"""گزارشِ دانشآموزان روی متنِ درسها."""
|
||||
|
||||
list_display = ('id', 'lesson', 'reason', 'status', 'note', 'created_at')
|
||||
list_filter = ('status', 'reason', 'lesson__node__unit__grade')
|
||||
search_fields = ('lesson__title', 'note')
|
||||
autocomplete_fields = ['lesson']
|
||||
actions = ['mark_resolved']
|
||||
|
||||
@admin.action(description='علامت زدن بهعنوانِ بررسیشده', permissions=['change'])
|
||||
def mark_resolved(self, request, queryset):
|
||||
updated = queryset.update(status='resolved', resolved_at=timezone.now())
|
||||
self.message_user(request, f'{updated} گزارش بررسیشده علامت خورد.', messages.SUCCESS)
|
||||
|
||||
|
||||
@admin.register(Lesson)
|
||||
class LessonAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'title', 'node')
|
||||
class LessonAdmin(VoteColumnsMixin, admin.ModelAdmin):
|
||||
vote_kind = 'lesson'
|
||||
list_display = ('id', 'title', 'node', 'likes', 'dislikes')
|
||||
list_filter = ('node',)
|
||||
# بدونِ این، انتخابِ درس در فرمِ سوال یک دراپداونِ بیش از هزارتایی است
|
||||
# که عملاً غیرقابلِ استفاده است.
|
||||
@@ -118,7 +166,8 @@ class LessonAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Question)
|
||||
class QuestionAdmin(admin.ModelAdmin):
|
||||
class QuestionAdmin(VoteColumnsMixin, admin.ModelAdmin):
|
||||
vote_kind = 'question'
|
||||
"""
|
||||
سوالها علاوه بر ویرایشِ تکی، از همینجا فلهای وارد و خارج میشوند.
|
||||
|
||||
@@ -127,7 +176,7 @@ class QuestionAdmin(admin.ModelAdmin):
|
||||
میپذیرد، پس چرخهی ویرایش بسته است.
|
||||
"""
|
||||
|
||||
list_display = ('id', 'prompt', 'lesson', 'type', 'open_reports', 'wrong_percent')
|
||||
list_display = ('id', 'prompt', 'lesson', 'type', 'open_reports', 'likes', 'dislikes', 'wrong_percent')
|
||||
list_filter = ('type', 'lesson__node__unit__grade', 'lesson__node__unit')
|
||||
inlines = [ChoiceInline]
|
||||
actions = ['export_selected']
|
||||
|
||||
Reference in New Issue
Block a user