Files
school-meal/backend/meals/views_dashboard.py
T
Chunyu d7553520ce feat: 现代化翻页组件 + 完整项目更新
- 新增 Pagination 通用组件:首页/上一页/页码/下一页/末页导航
- PickModal 和 SwapModal 集成分页功能,每页20项
- 搜索/筛选切换自动回到第1页
- 现代化CSS样式:hover浮起动效、主色高亮、移动端适配
- 新增 dashboard、admin 管理模板
- 菜系(cuisine)模型和迁移
- 构建脚本 build.py 支持 PyInstaller 打包
- 前端资源重新构建
2026-08-28 13:44:21 +08:00

91 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Dashboard API & view – visualization panel for the school meal planner."""
import json
from collections import Counter
from django.http import JsonResponse
from django.shortcuts import render
from django.db.models import Avg, Count, Max, Min, Sum
from .models import Dish
def _dashboard_stats():
"""Return aggregate statistics for the dashboard."""
total = Dish.objects.count()
active = Dish.objects.filter(is_active=True).count()
inactive = total - active
# Count by type
type_counts = dict(
Dish.objects.values_list('dish_type').annotate(c=Count('id')).values_list('dish_type', 'c')
)
# Count by cuisine
cuisine_counts = dict(
Dish.objects.values_list('cuisine').annotate(c=Count('id')).values_list('cuisine', 'c')
)
# Nutrition averages (active dishes only, meat + veg only for meaningful stats)
nutrition = Dish.objects.filter(is_active=True, dish_type__in=('meat', 'veg')).aggregate(
avg_protein=Avg('protein'),
avg_fat=Avg('fat'),
avg_calorie=Avg('calorie'),
max_calorie=Max('calorie'),
min_calorie=Min('calorie'),
)
# Per-type nutrition averages
type_nutrition = {}
for dt in ('meat', 'veg', 'soup', 'staple'):
agg = Dish.objects.filter(is_active=True, dish_type=dt).aggregate(
avg_protein=Avg('protein'),
avg_fat=Avg('fat'),
avg_calorie=Avg('calorie'),
count=Count('id'),
)
type_nutrition[dt] = agg
# Recent 10 dishes
recent = list(
Dish.objects.order_by('-created_at')[:10].values(
'id', 'name', 'dish_type', 'cuisine', 'protein', 'fat', 'calorie', 'is_active', 'created_at',
)
)
for r in recent:
r['created_at'] = r['created_at'].strftime('%Y-%m-%d %H:%M') if r['created_at'] else ''
return {
'total': total,
'active': active,
'inactive': inactive,
'type_counts': type_counts,
'cuisine_counts': cuisine_counts,
'nutrition': {
'avg_protein': round(float(nutrition['avg_protein'] or 0), 1),
'avg_fat': round(float(nutrition['avg_fat'] or 0), 1),
'avg_calorie': round(float(nutrition['avg_calorie'] or 0)),
'max_calorie': int(nutrition['max_calorie'] or 0),
'min_calorie': int(nutrition['min_calorie'] or 0),
},
'type_nutrition': {
k: {
'avg_protein': round(float(v['avg_protein'] or 0), 1),
'avg_fat': round(float(v['avg_fat'] or 0), 1),
'avg_calorie': round(float(v['avg_calorie'] or 0)),
'count': v['count'],
}
for k, v in type_nutrition.items()
},
'recent': recent,
}
def dashboard_api(request):
"""GET /api/dashboard/ -> JSON stats for the dashboard."""
return JsonResponse(_dashboard_stats())
def dashboard_view(request):
"""GET /dashboard/ -> serve the dashboard HTML page."""
return render(request, 'dashboard.html')