from django.contrib.admin import AdminSite from django.utils import timezone class HulumathAdminSite(AdminSite): site_header = "葫芦数学运营后台" site_title = "葫芦数学后台" index_title = "运营总览" site_url = "/" index_template = "admin/index.html" login_template = "admin/login.html" APP_ORDER = { "accounts": 10, "math_life": 20, "contest": 30, "content": 40, "latex_lab": 50, "progression": 60, "engagement": 70, "auth": 90, } MODEL_LABELS = { "accounts.user": "用户账号", "accounts.invitecode": "邀请码", "accounts.invitecodeusage": "邀请码使用记录", "accounts.usersession": "登录设备", "accounts.visitormigration": "游客数据迁移", "accounts.auditlog": "管理员审计日志", "math_life.mathidentity": "数学人格", "math_life.mathbtiassessment": "MathBTI 测评版本", "math_life.mathbtiresult": "MathBTI 用户结果", "math_life.character": "剧情人物", "math_life.story": "数学人生", "math_life.storyversion": "人生版本", "math_life.storyrun": "用户人生存档", "math_life.storychoice": "用户选择记录", "math_life.userrelationship": "人物关系", "math_life.skillpackage": "人物 Skill 包", "contest.question": "题目", "contest.questionversion": "题目版本", "contest.contest": "比赛", "contest.contestattempt": "答题记录", "contest.contestanswer": "逐题答案", "contest.realtimematch": "实时对局", "contest.ratinghistory": "Rating 变化", "contest.leaderboardsnapshot": "排行榜快照", "contest.cheatflag": "异常成绩", "content.contentitem": "视频与内容", "content.contentinteraction": "内容互动", "content.videoprogress": "视频观看进度", "latex_lab.formuladocument": "用户公式文档", "latex_lab.formularevision": "公式版本", "latex_lab.latexcourse": "LaTeX 课程", "latex_lab.latexexercise": "LaTeX 练习", "latex_lab.latexattempt": "练习记录", "latex_lab.handwritingrecognitionjob": "手写识别任务", "progression.userpet": "数学精灵", "progression.userability": "五维能力", "progression.card": "数学人物卡", "progression.usercard": "用户卡册", "progression.rewardtransaction": "奖励流水", "auth.group": "后台角色组", } MODEL_ORDER = { label: index for index, label in enumerate( ( "用户账号", "邀请码", "邀请码使用记录", "登录设备", "游客数据迁移", "管理员审计日志", "数学人生", "人生版本", "剧情人物", "人物 Skill 包", "数学人格", "MathBTI 测评版本", "用户人生存档", "人物关系", "比赛", "题目", "题目版本", "实时对局", "答题记录", "异常成绩", "视频与内容", "视频观看进度", "内容互动", "LaTeX 课程", "LaTeX 练习", "用户公式文档", "数学精灵", "五维能力", "数学人物卡", "用户卡册", ) ) } def get_app_list(self, request, app_label=None): app_list = super().get_app_list(request, app_label) for app in app_list: app["models"] = list(app["models"]) for model in app["models"]: object_name = model["object_name"].lower() key = f"{app['app_label']}.{object_name}" model["name"] = self.MODEL_LABELS.get(key, model["name"]) app["models"].sort( key=lambda model: ( self.MODEL_ORDER.get(model["name"], 999), model["name"], ) ) app_list.sort( key=lambda app: ( self.APP_ORDER.get(app["app_label"], 999), app["name"], ) ) return app_list def each_context(self, request): context = super().each_context(request) context["admin_environment"] = "生产运营" return context def index(self, request, extra_context=None): from accounts.models import InviteCode, User from content.models import ContentItem from contest.models import CheatFlag, ContestAttempt, RealtimeMatch from math_life.models import StoryRun today = timezone.localdate() extra_context = { **(extra_context or {}), "dashboard_metrics": [ { "label": "总用户", "value": User.objects.filter(is_active=True).count(), "hint": f"今日新增 {User.objects.filter(date_joined__date=today).count()}", "tone": "green", }, { "label": "进行中的人生", "value": StoryRun.objects.filter(status=StoryRun.Status.ACTIVE).count(), "hint": "用户当前存档", "tone": "blue", }, { "label": "今日比赛完成", "value": ContestAttempt.objects.filter( submitted_at__date=today ).count(), "hint": "含实时与异步", "tone": "orange", }, { "label": "已发布内容", "value": ContentItem.objects.filter(is_published=True).count(), "hint": "视频、知识与人物", "tone": "purple", }, ], "dashboard_alerts": [ { "label": "待处理异常成绩", "value": CheatFlag.objects.filter( status=CheatFlag.Status.OPEN ).count(), "url": "/admin/contest/cheatflag/?status__exact=open", }, { "label": "等待中的实时对局", "value": RealtimeMatch.objects.filter( status=RealtimeMatch.Status.WAITING ).count(), "url": "/admin/contest/realtimematch/?status__exact=waiting", }, { "label": "可用邀请码", "value": InviteCode.objects.filter(is_active=True).count(), "url": "/admin/accounts/invitecode/", }, ], } return super().index(request, extra_context)