创建数据看板¶
学习如何为您的 Django Admin 界面创建数据看板。本指南涵盖从数据看板创建到高级功能的所有内容。
目录¶
数据看板基础¶
什么是数据看板?¶
Django Admin Dashboards 中的数据看板是一个扩展了基础 Dashboard
类的 Python 类。它定义:
- 布局: 组件在页面上的排列方式
- 组件: 要显示的数据可视化(卡片、图表、表格)
- 配置: 显示选项和行为控制
- 数据源: 数据来源(模型、API 等)
数据看板示例¶
# myapp/dashboards.py
from django_admin_dashboards.base import Dashboard, CardComponent, Layout
from django.utils.translation import gettext_lazy as _
class BasicDashboard(Dashboard):
"""简单示例数据看板"""
title = _("Basic Dashboard")
show_dashboard_title = True
show_admin_title = False
def get_layout(self):
"""定义数据看板布局"""
layout = Layout(columns=12)
# 添加一个卡片组件
layout.add_row([
(CardComponent(
title="Welcome",
value="Your dashboard is ready!",
color="primary",
icon_class="ri-check-line"
), 12)
], height="auto")
return layout
数据看板类结构¶
必需组件¶
每个数据看板通常包含以下元素:
from django_admin_dashboards.base import Dashboard, Layout
class MyDashboard(Dashboard):
# 1. 标题(可选,为空时不显示)
title = "My Dashboard"
# 2. 布局方法(必需)
def get_layout(self):
layout = Layout(columns=12)
# Add components here
return layout
# 可选:数据获取方法
def get_cards(self):
"""使用数据生成 卡片"""
pass
def get_charts(self):
"""使用数据生成图表"""
pass
def get_tables(self):
"""使用数据生成表格"""
pass
可选配置属性¶
class ConfiguredDashboard(Dashboard):
# 显示配置
title = _("Configured Dashboard")
show_dashboard_title = True # 显示数据看板标题
show_admin_title = False # 隐藏默认的admin标题
# 全屏功能
hide_others_in_fullscreen = True # 启用全屏隐藏
force_hide_others = False # 启用强制隐藏
# 模板自定义
template_name = "admin/custom_dashboard.html"
# 媒体配置
class Media:
css = {
"all": (
"django_admin_dashboards/css/dashboard.css",
"remixicon/remixicon.css",
"myapp/css/custom.css", # 自定义样式
)
}
js = (
"django_admin_dashboards/js/chart.umd.js",
"myapp/js/custom.js", # 自定义脚本
)
布局管理¶
理解网格系统¶
Django Admin Dashboards 使用 12 列响应式网格系统:
layout = Layout(columns=12) # 创建 12 列布局
# 组件占据全部宽度(12列)
layout.add_row([(component, 12)], height="auto")
# 两个组件,各占6列
layout.add_row([(component1, 6), (component2, 6)], height="auto")
# 三个组件,各占4列
layout.add_row([(c1, 4), (c2, 4), (c3, 4)], height="auto")
# Mixed column widths
layout.add_row([(c1, 8), (c2, 4)], height="auto")
行高度配置¶
# 自动高度(内容决定高度)
layout.add_row([...], height="auto")
# 固定高度(像素)
layout.add_row([...], height="300px")
layout.add_row([...], height="400px")
# 最小高度
layout.add_row([...], height="min-height: 200px")
# 响应式高度
layout.add_row([...], height="clamp(300px, 50vh, 600px)")
复杂布局示例¶
def get_layout(self):
layout = Layout(columns=12)
# 第1行:标题卡片(3个并排)
layout.add_row([
(self.get_header_card("Users", "ri-user-line", "primary"), 4),
(self.get_header_card("Orders", "ri-shopping-cart-line", "success"), 4),
(self.get_header_card("Revenue", "ri-money-dollar-circle-line", "warning"), 4),
], height="auto")
# 第2行:主图表(全宽度)
layout.add_row([
(self.get_main_chart(), 12)
], height="500px")
# 第3行:并排表格
layout.add_row([
(self.get_recent_activity_table(), 6),
(self.get_top_performers_table(), 6),
], height="auto")
# 第4行:详细指标(4个并排)
layout.add_row([
(self.get_metric_card("Conversion", "3.2%"), 3),
(self.get_metric_card("Avg. Session", "4m 32s"), 3),
(self.get_metric_card("Bounce Rate", "42%"), 3),
(self.get_metric_card("Pages/Session", "5.8"), 3),
], height="auto")
return layout
动态数据集成¶
数据库 Integration¶
from django.db.models import Count, Sum, Avg
from datetime import datetime, timedelta
from django.utils import timezone
from .models import User, Order
class DataDrivenDashboard(Dashboard):
def get_cards(self):
"""使用数据库数据生成 卡片"""
today = timezone.now().date()
last_30_days = today - timedelta(days=30)
# 计算指标
total_users = User.objects.count()
new_users = User.objects.filter(
date_joined__gte=last_30_days
).count()
total_revenue = Order.objects.aggregate(
total=Sum('amount')
)['total'] or 0
avg_order_value = Order.objects.aggregate(
avg=Avg('amount')
)['avg'] or 0
return [
CardComponent(
title="Total Users",
value=total_users,
change=f"+{new_users}",
trend="up" if new_users > 0 else "neutral",
color="primary",
icon_class="ri-user-line"
),
CardComponent(
title="Total Revenue",
value=f"${total_revenue:,.2f}",
color="success",
icon_class="ri-money-dollar-circle-line"
),
CardComponent(
title="Avg Order Value",
value=f"${avg_order_value:.2f}",
color="warning",
icon_class="ri-trophy-line"
),
]
实时数据更新¶
class RealTimeDashboard(Dashboard):
def get_layout(self):
layout = Layout(columns=12)
# Add auto-refresh script
layout.add_row([
(self.get_real_time_card(), 12)
], height="auto")
return layout
def get_real_time_card(self):
"""具有自动刷新功能的卡片"""
card = CardComponent(
title="Active Sessions",
value=self.get_active_session_count(),
color="info",
icon_class="ri-user-heart-line"
)
# Add refresh attribute
card.refresh_interval = 30000 # 30 seconds
card.data_url = "/api/active-sessions/"
return card
def get_active_session_count(self):
"""获取当前活跃会话数"""
from django.contrib.sessions.models import Session
from django.utils import timezone
# Count active sessions (last 30 minutes)
active_since = timezone.now() - timedelta(minutes=30)
return Session.objects.filter(
expire_date__gt=timezone.now()
).count()
API 数据集成¶
import requests
import json
from django.core.cache import cache
class APIDashboard(Dashboard):
def get_external_data(self):
"""从外部 API 获取数据"""
cache_key = "external_api_data"
data = cache.get(cache_key)
if not data:
try:
response = requests.get(
"https://api.example.com/metrics",
headers={"Authorization": "Bearer YOUR_TOKEN"},
timeout=5
)
response.raise_for_status()
data = response.json()
cache.set(cache_key, data, timeout=300) # 缓存5分钟
except requests.RequestException as e:
# Fallback to cached or default data
data = {"error": str(e)}
return data
def get_api_cards(self):
"""根据 API 数据创建卡片"""
data = self.get_external_data()
if "error" in data:
return [
CardComponent(
title="API Error",
value=data["error"],
color="danger",
icon_class="ri-error-warning-line"
)
]
return [
CardComponent(
title=data.get("metric1_name", "Metric 1"),
value=data.get("metric1_value", 0),
change=data.get("metric1_change", "0%"),
color="primary",
icon_class="ri-line-chart-line"
),
# ... more cards from API data
]
高级数据看板功能¶
条件性显示¶
class ConditionalDashboard(Dashboard):
def get_layout(self):
layout = Layout(columns=12)
# 检查用户权限
if self.request.user.has_perm('auth.view_user'):
layout.add_row([
(self.get_user_metrics(), 12)
], height="auto")
# 检查功能标志
if self.is_feature_enabled('advanced_analytics'):
layout.add_row([
(self.get_advanced_charts(), 12)
], height="400px")
# 基于时间的显示
hour = timezone.now().hour
if 9 <= hour <= 17:
layout.add_row([
(self.get_business_hours_card(), 12)
], height="auto")
else:
layout.add_row([
(self.get_after_hours_card(), 12)
], height="auto")
return layout
def is_feature_enabled(self, feature_name):
"""检查功能是否启用"""
# 可以检查数据库、环境变量等
return feature_name in os.getenv('ENABLED_FEATURES', '').split(',')
数据看板选项卡¶
class TabbedDashboard(Dashboard):
def get_layout(self):
layout = Layout(columns=12)
# Add tab navigation
layout.add_row([
(self.get_tab_navigation(), 12)
], height="auto")
# Default tab content
active_tab = self.request.GET.get('tab', 'overview')
if active_tab == 'overview':
layout.add_row([
(self.get_overview_content(), 12)
], height="auto")
elif active_tab == 'details':
layout.add_row([
(self.get_detail_content(), 12)
], height="auto")
elif active_tab == 'analytics':
layout.add_row([
(self.get_analytics_content(), 12)
], height="auto")
return layout
def get_tab_navigation(self):
"""创建选项卡导航组件"""
tabs = [
{"name": "overview", "label": "Overview", "icon": "ri-dashboard-line"},
{"name": "details", "label": "Details", "icon": "ri-file-list-line"},
{"name": "analytics", "label": "Analytics", "icon": "ri-bar-chart-line"},
]
# Create a custom component for tabs
# This would require a custom template
return Component(
component_type="tabs",
tabs=tabs,
active_tab=self.request.GET.get('tab', 'overview')
)
导出与共享¶
class ExportableDashboard(Dashboard):
def get_layout(self):
layout = Layout(columns=12)
# Add export buttons
layout.add_row([
(self.get_export_controls(), 12)
], height="auto")
# Add dashboard content
layout.add_row([
(self.get_main_content(), 12)
], height="auto")
return layout
def get_export_controls(self):
"""创建导出控制组件"""
return Component(
component_type="export_controls",
formats=[
{"name": "pdf", "label": "PDF", "icon": "ri-file-pdf-line"},
{"name": "excel", "label": "Excel", "icon": "ri-file-excel-line"},
{"name": "csv", "label": "CSV", "icon": "ri-file-text-line"},
],
dashboard_name=self.title
)
最佳实践¶
1. 性能优化¶
class OptimizedDashboard(Dashboard):
def get_layout(self):
# 对昂贵的计算使用缓存
cache_key = f"dashboard_{self.__class__.__name__}"
layout = cache.get(cache_key)
if not layout:
layout = self.build_layout()
cache.set(cache_key, layout, timeout=300) # 5 minutes
return layout
def build_layout(self):
"""无缓存构建布局"""
layout = Layout(columns=12)
# 优化数据库查询
# 使用 select_related 和 prefetch_related
users = User.objects.select_related('profile').prefetch_related('groups')
# Use aggregation instead of counting in Python
stats = Order.objects.aggregate(
total=Count('id'),
revenue=Sum('amount'),
avg_value=Avg('amount')
)
# ... build layout with optimized data
return layout
2. Error Handling¶
class RobustDashboard(Dashboard):
def get_layout(self):
try:
layout = Layout(columns=12)
# Try to add each component, with fallbacks
try:
layout.add_row([
(self.get_database_cards(), 12)
], height="auto")
except Exception as e:
layout.add_row([
(self.get_error_card("Database Error", str(e)), 12)
], height="auto")
try:
layout.add_row([
(self.get_api_charts(), 12)
], height="400px")
except Exception as e:
layout.add_row([
(self.get_error_card("API Error", str(e)), 12)
], height="auto")
return layout
except Exception as e:
# Complete fallback
return self.get_fallback_layout(str(e))
def get_error_card(self, title, message):
"""创建错误卡片"""
return CardComponent(
title=title,
value=message[:100], # Truncate long messages
color="danger",
icon_class="ri-error-warning-line"
)
3. Code Organization¶
myapp/
├── dashboards/
│ ├── __init__.py
│ ├── base.py # Base dashboard classes
│ ├── sales.py # Sales-specific dashboards
│ ├── inventory.py # Inventory dashboards
│ └── analytics.py # Analytics dashboards
├── components/
│ ├── __init__.py
│ ├── cards.py # Custom card components
│ ├── charts.py # Custom chart components
│ └── tables.py # Custom table components
└── utils/
├── data_fetchers.py # Data fetching utilities
└── formatters.py # Data formatting utilities
4. 测试数据看板¶
# tests/test_dashboards.py
from django.test import TestCase
from myapp.dashboards.sales import SalesDashboard
class DashboardTestCase(TestCase):
def test_dashboard_creation(self):
"""测试数据看板可以实例化"""
dashboard = SalesDashboard()
self.assertIsNotNone(dashboard)
self.assertEqual(dashboard.title, "Sales Dashboard")
def test_layout_generation(self):
"""测试布局是否正确生成"""
dashboard = SalesDashboard()
layout = dashboard.get_layout()
self.assertIsNotNone(layout)
self.assertGreater(len(layout.rows), 0)
def test_cards(self):
"""测试 卡片生成"""
dashboard = SalesDashboard()
cards = dashboard.get_cards()
self.assertGreaterEqual(len(cards), 3)
self.assertEqual(cards[0].component_type, "card")
def test_dashboard_rendering(self):
"""测试数据看板通过admin渲染"""
from django.test import Client
from django.urls import reverse
client = Client()
response = client.get(reverse('admin:index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Sales Dashboard")
故障排除¶
常见问题与解决方案¶
1. 数据看板未显示¶
- 检查:
INSTALLED_APPS中的应用顺序 - 检查:
DJANGO_ADMIN_DASHBOARDS配置 - 检查: 用户具有必需权限
2. 布局不正常¶
- 检查: 每行列跨度总和为 12
- 检查: 组件已正确实例化
- 检查:
get_layout()方法中没有语法错误
3. 数据未加载¶
- 检查: 数据库查询正确
- 检查: API 端点可访问
- 检查: 错误处理已就位
4. 性能问题¶
- 检查: 数据库查询已优化
- 检查: 已实现缓存
- 检查: 昂贵的计算已进行记忆化
调试技巧¶
# 向数据看板添加调试信息
class DebugDashboard(Dashboard):
def get_layout(self):
import sys
print(f"Building layout for {self.__class__.__name__}", file=sys.stderr)
print(f"Request user: {self.request.user}", file=sys.stderr)
# Log database queries
from django.db import connection
print(f"Initial query count: {len(connection.queries)}", file=sys.stderr)
layout = Layout(columns=12)
# ... build layout
print(f"Final query count: {len(connection.queries)}", file=sys.stderr)
for query in connection.queries:
print(f"Query: {query['sql'][:100]}...", file=sys.stderr)
return layout