vault backup: 2025-12-26 02:09:22
All checks were successful
Deploy Quartz site to GitHub Pages / build (push) Successful in 2m29s
All checks were successful
Deploy Quartz site to GitHub Pages / build (push) Successful in 2m29s
This commit is contained in:
18
stroma/quiz/admin/__init__.py
Normal file
18
stroma/quiz/admin/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from .course_admin import CourseAdmin
|
||||
from .exam_admin import ExamAdmin
|
||||
from .option_inline import OptionInline
|
||||
from .question_admin import QuestionAdmin
|
||||
from .option_admin import OptionAdmin
|
||||
from .quiz_user_admin import QuizUserAdmin
|
||||
from .quiz_result_admin import QuizResultAdmin
|
||||
|
||||
__all__ = [
|
||||
'CourseAdmin',
|
||||
'ExamAdmin',
|
||||
'OptionInline',
|
||||
'QuestionAdmin',
|
||||
'OptionAdmin',
|
||||
'QuizUserAdmin',
|
||||
'QuizResultAdmin',
|
||||
]
|
||||
|
||||
16
stroma/quiz/admin/course_admin.py
Normal file
16
stroma/quiz/admin/course_admin.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.contrib import admin
|
||||
from quiz.models import Course
|
||||
|
||||
|
||||
@admin.register(Course)
|
||||
class CourseAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for Courses"""
|
||||
list_display = ['id', 'name', 'code', 'exam_count', 'created_at']
|
||||
search_fields = ['name', 'code']
|
||||
readonly_fields = ['created_at']
|
||||
|
||||
def exam_count(self, obj):
|
||||
"""Show number of exams"""
|
||||
return obj.exams.count()
|
||||
exam_count.short_description = '# Exams'
|
||||
|
||||
17
stroma/quiz/admin/exam_admin.py
Normal file
17
stroma/quiz/admin/exam_admin.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.contrib import admin
|
||||
from quiz.models import Exam
|
||||
|
||||
|
||||
@admin.register(Exam)
|
||||
class ExamAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for Exams"""
|
||||
list_display = ['id', 'course', 'date', 'question_count', 'folder_path', 'created_at']
|
||||
list_filter = ['course', 'date']
|
||||
search_fields = ['name', 'folder_path']
|
||||
readonly_fields = ['created_at']
|
||||
|
||||
def question_count(self, obj):
|
||||
"""Show number of questions"""
|
||||
return obj.questions.count()
|
||||
question_count.short_description = '# Questions'
|
||||
|
||||
30
stroma/quiz/admin/option_admin.py
Normal file
30
stroma/quiz/admin/option_admin.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from quiz.models import Option
|
||||
|
||||
|
||||
@admin.register(Option)
|
||||
class OptionAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for Options"""
|
||||
list_display = ['id', 'question_preview', 'letter', 'text_preview', 'is_correct']
|
||||
list_filter = ['letter']
|
||||
search_fields = ['text', 'question__text']
|
||||
readonly_fields = ['question']
|
||||
|
||||
def question_preview(self, obj):
|
||||
"""Show question preview"""
|
||||
return obj.question.text[:40] + '...'
|
||||
question_preview.short_description = 'Question'
|
||||
|
||||
def text_preview(self, obj):
|
||||
"""Show option text preview"""
|
||||
return obj.text[:50] + '...' if len(obj.text) > 50 else obj.text
|
||||
text_preview.short_description = 'Option Text'
|
||||
|
||||
def is_correct(self, obj):
|
||||
"""Highlight if this is the correct answer"""
|
||||
if obj.question.correct_answer and obj.letter in obj.question.correct_answer:
|
||||
return format_html('<span style="color: green; font-weight: bold;">✓ Correct</span>')
|
||||
return format_html('<span style="color: #999;">-</span>')
|
||||
is_correct.short_description = 'Status'
|
||||
|
||||
11
stroma/quiz/admin/option_inline.py
Normal file
11
stroma/quiz/admin/option_inline.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from quiz.models import Option
|
||||
|
||||
|
||||
class OptionInline(admin.TabularInline):
|
||||
"""Inline admin for question options"""
|
||||
model = Option
|
||||
extra = 0
|
||||
fields = ['letter', 'text']
|
||||
ordering = ['letter']
|
||||
|
||||
58
stroma/quiz/admin/question_admin.py
Normal file
58
stroma/quiz/admin/question_admin.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from quiz.models import Question
|
||||
from .option_inline import OptionInline
|
||||
|
||||
|
||||
@admin.register(Question)
|
||||
class QuestionAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for Questions"""
|
||||
list_display = ['id', 'question_preview', 'exam', 'correct_answer', 'option_count', 'file_source', 'updated_at']
|
||||
list_filter = ['exam__course', 'exam', 'created_at', 'updated_at']
|
||||
search_fields = ['text', 'file_path', 'correct_answer']
|
||||
readonly_fields = ['file_path', 'file_mtime', 'created_at', 'updated_at', 'formatted_mtime']
|
||||
fieldsets = [
|
||||
('Question Content', {
|
||||
'fields': ['exam', 'text', 'correct_answer']
|
||||
}),
|
||||
('File Tracking', {
|
||||
'fields': ['file_path', 'file_mtime', 'formatted_mtime'],
|
||||
'classes': ['collapse']
|
||||
}),
|
||||
('Timestamps', {
|
||||
'fields': ['created_at', 'updated_at'],
|
||||
'classes': ['collapse']
|
||||
}),
|
||||
]
|
||||
inlines = [OptionInline]
|
||||
|
||||
def question_preview(self, obj):
|
||||
"""Show question text preview"""
|
||||
return obj.text[:60] + '...' if len(obj.text) > 60 else obj.text
|
||||
question_preview.short_description = 'Question'
|
||||
|
||||
def option_count(self, obj):
|
||||
"""Show number of options"""
|
||||
return obj.options.count()
|
||||
option_count.short_description = '# Options'
|
||||
|
||||
def file_source(self, obj):
|
||||
"""Show file path with folder highlight"""
|
||||
if obj.file_path:
|
||||
parts = obj.file_path.split('/')
|
||||
if len(parts) > 1:
|
||||
folder = parts[-2]
|
||||
filename = parts[-1]
|
||||
return format_html('<span style="color: #666;">{}/</span><strong>{}</strong>', folder, filename)
|
||||
return obj.file_path or '-'
|
||||
file_source.short_description = 'Source File'
|
||||
|
||||
def formatted_mtime(self, obj):
|
||||
"""Show formatted modification time"""
|
||||
if obj.file_mtime:
|
||||
from datetime import datetime
|
||||
dt = datetime.fromtimestamp(obj.file_mtime)
|
||||
return dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
return '-'
|
||||
formatted_mtime.short_description = 'File Modified'
|
||||
|
||||
35
stroma/quiz/admin/quiz_result_admin.py
Normal file
35
stroma/quiz/admin/quiz_result_admin.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.safestring import mark_safe
|
||||
from quiz.models import QuizResult
|
||||
|
||||
|
||||
@admin.register(QuizResult)
|
||||
class QuizResultAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for Quiz Results"""
|
||||
list_display = ['id', 'user_preview', 'question_preview', 'selected_answer', 'correct_answer', 'result_status', 'answered_at']
|
||||
list_filter = ['is_correct', 'answered_at']
|
||||
search_fields = ['user__session_key', 'question__text']
|
||||
readonly_fields = ['user', 'question', 'selected_answer', 'is_correct', 'answered_at']
|
||||
|
||||
def user_preview(self, obj):
|
||||
"""Show user session preview"""
|
||||
return f"{obj.user.session_key[:8]}..."
|
||||
user_preview.short_description = 'User'
|
||||
|
||||
def question_preview(self, obj):
|
||||
"""Show question preview"""
|
||||
return obj.question.text[:40] + '...'
|
||||
question_preview.short_description = 'Question'
|
||||
|
||||
def correct_answer(self, obj):
|
||||
"""Show correct answer"""
|
||||
return obj.question.correct_answer
|
||||
correct_answer.short_description = 'Correct'
|
||||
|
||||
def result_status(self, obj):
|
||||
"""Show visual result status"""
|
||||
if obj.is_correct:
|
||||
return mark_safe('<span style="color: green; font-weight: bold;">✓ Correct</span>')
|
||||
return mark_safe('<span style="color: red; font-weight: bold;">✗ Wrong</span>')
|
||||
result_status.short_description = 'Result'
|
||||
|
||||
46
stroma/quiz/admin/quiz_user_admin.py
Normal file
46
stroma/quiz/admin/quiz_user_admin.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.safestring import mark_safe
|
||||
from quiz.models import QuizUser
|
||||
|
||||
|
||||
@admin.register(QuizUser)
|
||||
class QuizUserAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for Quiz Users"""
|
||||
list_display = ['id', 'session_preview', 'result_count', 'score_percentage', 'created_at']
|
||||
list_filter = ['created_at']
|
||||
search_fields = ['session_key']
|
||||
readonly_fields = ['session_key', 'created_at', 'full_session_key']
|
||||
fieldsets = [
|
||||
('User Info', {
|
||||
'fields': ['full_session_key', 'created_at']
|
||||
}),
|
||||
]
|
||||
|
||||
def session_preview(self, obj):
|
||||
"""Show session key preview"""
|
||||
return f"{obj.session_key[:12]}..."
|
||||
session_preview.short_description = 'Session'
|
||||
|
||||
def result_count(self, obj):
|
||||
"""Show number of quiz results"""
|
||||
return obj.results.count()
|
||||
result_count.short_description = '# Answers'
|
||||
|
||||
def score_percentage(self, obj):
|
||||
"""Show score percentage"""
|
||||
total = obj.results.count()
|
||||
if total == 0:
|
||||
return '-'
|
||||
correct = obj.results.filter(is_correct=True).count()
|
||||
percentage = (correct / total * 100)
|
||||
color = 'green' if percentage >= 70 else 'orange' if percentage >= 50 else 'red'
|
||||
return mark_safe(
|
||||
f'<span style="color: {color}; font-weight: bold;">{percentage:.1f}%</span> ({correct}/{total})'
|
||||
)
|
||||
score_percentage.short_description = 'Score'
|
||||
|
||||
def full_session_key(self, obj):
|
||||
"""Show full session key"""
|
||||
return obj.session_key
|
||||
full_session_key.short_description = 'Full Session Key'
|
||||
|
||||
Reference in New Issue
Block a user