Django:长期存储数据库查询变量并生成图表展示
实现Django仪表盘的长期查询存储与图表展示
嘿,针对你正在构建的合同管理仪表盘,我把需求拆成两个核心部分来给你讲清楚:
一、长期存储数据库查询变量
这里有两种常用方案,你可以根据实际需求选择:
1. 使用Django缓存框架(适合频繁查询、无需持久化的场景)
Django自带的缓存系统能帮你把高频统计的查询结果缓存起来,避免每次请求都重复跑数据库。比如总合同数、即将到期的合同数这类数据就很适合用缓存:
from django.core.cache import cache from .models import Contract from datetime import datetime, timedelta def get_contract_dashboard_stats(): # 先尝试从缓存读取数据 cached_stats = cache.get("contract_dashboard_core_stats") if not cached_stats: # 执行数据库查询 total_contracts = Contract.objects.count() expiring_soon = Contract.objects.filter( contract_expires__lte=datetime.now() + timedelta(days=30), contract_expires__isnull=False ).count() # 统计过去6个月的新增合同数 monthly_new_contracts = [] for i in range(6): target_month = datetime.now() - timedelta(days=i*30) count = Contract.objects.filter( contract_date__year=target_month.year, contract_date__month=target_month.month ).count() monthly_new_contracts.append({ "month": target_month.strftime("%b"), "count": count }) # 组装统计数据 cached_stats = { "total_contracts": total_contracts, "expiring_soon": expiring_soon, "monthly_new": monthly_new_contracts } # 缓存24小时(可根据需求调整过期时间) cache.set("contract_dashboard_core_stats", cached_stats, 60*60*24) return cached_stats
2. 用模型持久化存储(适合需要长期保留、可追溯的统计数据)
如果需要把统计数据永久存在数据库里(比如要查看历史统计变化),可以创建一个专门的统计模型,再配合定时任务定期更新:
第一步:创建统计模型
from django.db import models class DashboardContractStats(models.Model): stat_identifier = models.CharField(max_length=60, unique=True) # 比如"total_contracts" numeric_value = models.IntegerField() recorded_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = "合同仪表盘统计" verbose_name_plural = "合同仪表盘统计" def __str__(self): return f"{self.stat_identifier}: {self.numeric_value}"
第二步:编写定时更新任务
用Celery或者Django Q这类任务调度工具,定期计算并更新统计数据。这里以Celery为例:
from celery import shared_task from .models import Contract, DashboardContractStats from datetime import datetime, timedelta @shared_task def refresh_contract_dashboard_stats(): # 更新总合同数 total_count = Contract.objects.count() DashboardContractStats.objects.update_or_create( stat_identifier="total_contracts", defaults={"numeric_value": total_count} ) # 更新即将到期的合同数 expiring_count = Contract.objects.filter( contract_expires__lte=datetime.now() + timedelta(days=30), contract_expires__isnull=False ).count() DashboardContractStats.objects.update_or_create( stat_identifier="expiring_soon", defaults={"numeric_value": expiring_count} ) # 其他统计项同理扩展
然后在Celery配置里设置定时任务(比如每天凌晨1点执行):
from celery.schedules import crontab CELERY_BEAT_SCHEDULE = { 'refresh-contract-stats-daily': { 'task': 'your_app.tasks.refresh_contract_dashboard_stats', 'schedule': crontab(hour=1, minute=0), }, }
二、将数据以图表形式展示
前端图表库是最佳选择,推荐用Chart.js(轻量易用)或者ECharts(功能强大)。这里以Chart.js结合Django模板为例:
1. 在视图里传递数据到模板
from django.shortcuts import render from .utils import get_contract_dashboard_stats # 前面的缓存函数 def contract_dashboard(request): stats = get_contract_dashboard_stats() return render(request, "dashboard/contract_dashboard.html", { "total_contracts": stats["total_contracts"], "expiring_soon": stats["expiring_soon"], "monthly_new_data": stats["monthly_new"] })
2. 在模板里渲染图表
<!-- dashboard/contract_dashboard.html --> <div class="chart-container" style="width: 80%; margin: 0 auto;"> <canvas id="monthlyNewContracts"></canvas> </div> <!-- 引入Chart.js --> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> // 从模板获取数据 const monthlyData = {{ monthly_new_data|safe }}; // 初始化图表 const ctx = document.getElementById('monthlyNewContracts').getContext('2d'); new Chart(ctx, { type: 'bar', data: { labels: monthlyData.map(item => item.month), datasets: [{ label: '每月新增合同数', data: monthlyData.map(item => item.count), backgroundColor: 'rgba(75, 192, 192, 0.6)', borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 1 }] }, options: { responsive: true, scales: { y: { beginAtZero: true, title: { display: true, text: '合同数量' } }, x: { title: { display: true, text: '月份' } } } } }); </script>
小提示:修复你现有Contract模型的小问题
你的save方法里如果contract_expires为null会报错,建议加个判断:
def save(self, *args, **kwargs): if self.contract_date and self.contract_expires: self.contract_length = (self.contract_expires - self.contract_date).days else: self.contract_length = None # 或者设置一个默认值 super().save(*args, **kwargs)
内容的提问来源于stack exchange,提问作者Preben Hesvik
相关产品推荐
相关产品推荐

