Files
Hulumath-Web/backend/common/admin_site.py
T
Jacky 43e425edbe
CI / test (push) Canceled after 4m2s
v 0.3 Preview: Update admin
2026-08-08 21:47:29 +08:00

190 lines
7.1 KiB
Python

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)