数据看板示例¶
本指南将带领您从头开始为电子商务应用程序创建一个数据看板。您将学习如何设计、实现和部署一个生产就绪的数据看板,用于显示销售分析、库存状态和客户洞察。
目录¶
概述¶
我们将构建什么¶
数据看板,包含:
- 销售指标: 收入、订单数、平均订单价值
- 库存状态: 低库存警报、类别分布
- 客户洞察: 新客户、客户位置
- 产品性能: 热门产品、销售趋势
- 交互式功能: 日期筛选、导出功能
数据看板预览¶
┌─────────────────────────────────────────────────────────────────────┐
│ E-Commerce Analytics Dashboard │
├─────────────┬─────────────┬─────────────┬─────────────┬─────────────┤
│ Revenue │ Orders │ Avg Order │ New Customers│ Conversion │
│ $125,430 │ 1,245 │ $100.75 │ 85 │ 4.2% │
├─────────────┴─────────────┴─────────────┴─────────────┴─────────────┤
│ Sales Trend (Last 30 Days) │ Inventory Status │
│ [Line Chart] │ [Pie Chart] │
├─────────────────────────────────────────────────────────────────────┤
│ Top Selling Products │ Customer Locations│
│ [Bar Chart] │ [Map/Table] │
├─────────────────────────────────────────────────────────────────────┤
│ Recent Orders │ Low Stock Alerts │
│ [Table] │ [Table] │
└─────────────────────────────────────────────────────────────────────┘
先决条件¶
- Django 3.2+
- Django Admin Dashboards 已安装
- 基础 Django 模型(Order、Product、Customer 等)
- Python 3.8+
项目设置¶
1. 创建 Django App¶
# Create app for our dashboard
python manage.py startapp analytics
# Add to INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
# ... other apps ...
'analytics',
'django_admin_dashboards',
]
2. 定义模型¶
# analytics/models.py
from django.db import models
from django.contrib.auth.models import User
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
join_date = models.DateTimeField(auto_now_add=True)
location = models.CharField(max_length=100)
def __str__(self):
return self.name
class Product(models.Model):
CATEGORY_CHOICES = [
('electronics', 'Electronics'),
('clothing', 'Clothing'),
('home', 'Home & Garden'),
('books', 'Books'),
('other', 'Other'),
]
name = models.CharField(max_length=200)
category = models.CharField(max_length=50, choices=CATEGORY_CHOICES)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock_quantity = models.IntegerField(default=0)
low_stock_threshold = models.IntegerField(default=10)
def __str__(self):
return self.name
@property
def is_low_stock(self):
return self.stock_quantity <= self.low_stock_threshold
class Order(models.Model):
STATUS_CHOICES = [
('pending', 'Pending'),
('processing', 'Processing'),
('shipped', 'Shipped'),
('delivered', 'Delivered'),
('cancelled', 'Cancelled'),
]
order_number = models.CharField(max_length=50, unique=True)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
order_date = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return f"Order {self.order_number}"
class Meta:
ordering = ['-order_date']
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
@property
def subtotal(self):
return self.quantity * self.price
3. 创建 Admin 配置¶
# analytics/admin.py
from django.contrib import admin
from .models import Customer, Product, Order, OrderItem
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ['name', 'email', 'join_date', 'location']
search_fields = ['name', 'email']
list_filter = ['location']
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'category', 'price', 'stock_quantity', 'is_low_stock']
list_filter = ['category', 'is_low_stock']
search_fields = ['name']
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ['order_number', 'customer', 'order_date', 'status', 'total_amount']
list_filter = ['status', 'order_date']
search_fields = ['order_number', 'customer__name']
admin.site.register(OrderItem)
数据看板设计¶
需求分析¶
编码之前,定义数据看板需要显示的内容:
-
业务关键绩效指标 (KPI):
-
总收入
-
订单数量
-
平均订单价值
-
转化率
-
新客户
-
运营指标:
-
按类别划分的库存状态
-
低库存警报
-
订单状态分布
-
趋势分析:
-
随时间变化的销售趋势
-
热销产品
-
客户获取趋势
-
最近活动:
-
最近订单
-
新客户
-
库存变化
布局规划¶
使用12列网格设计布局:
- 行 1: 5 个 卡片(收入、订单、平均订单、新客户、转化率)
- 行 2: 销售趋势图表(8列)+ 库存状态(4列)
- 行 3: 热门产品图表(8列)+ 客户地图/表格(4列)
- 行 4: 最近订单表格(8列)+ 低库存警报(4列)
数据来源¶
识别数据来源:
- Sales 数据:
OrderandOrderItemmodels - Customer 数据:
Customer模型 - Inventory 数据:
Product模型 - Calculated Metrics: Aggregations and annotations
逐步实现¶
步骤 1: 创建数据看板基类¶
# analytics/dashboards.py
from datetime import datetime, timedelta
from django.db.models import Count, Sum, Avg, Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django_admin_dashboards.base import Dashboard, CardComponent, ChartComponent, Layout, TableComponent
from .models import Order, Product, Customer, OrderItem
class EcommerceDashboard(Dashboard):
"""E-commerce analytics dashboard"""
# Dashboard configuration
title = _("E-Commerce Analytics")
show_dashboard_title = True
show_admin_title = False
# Enable fullscreen and dark mode features
hide_others_in_fullscreen = True
force_hide_others = False
# Custom media assets
class Media:
css = {
"all": (
"django_admin_dashboards/css/dashboard.css",
"remixicon/remixicon.css",
"analytics/css/custom.css", # Custom CSS
)
}
js = (
"django_admin_dashboards/js/chart.umd.js",
"analytics/js/custom.js", # Custom JavaScript
)
def __init__(self, request=None):
super().__init__(request)
self.timeframe_days = self.get_timeframe_from_request(request)
def get_timeframe_from_request(self, request):
"""Get timeframe from URL parameters or use default"""
if request:
days = request.GET.get('days', '30')
try:
return int(days)
except ValueError:
pass
return 30 # Default 30 days
步骤 2: 实现 卡片¶
def get_cards(self):
"""Create card components with e-commerce metrics"""
# Calculate date range
end_date = timezone.now()
start_date = end_date - timedelta(days=self.timeframe_days)
# Calculate metrics
order_stats = Order.objects.filter(
order_date__range=(start_date, end_date)
).aggregate(
total_revenue=Sum('total_amount'),
total_orders=Count('id'),
avg_order_value=Avg('total_amount')
)
new_customers = Customer.objects.filter(
join_date__range=(start_date, end_date)
).count()
# Calculate conversion rate (simplified)
# In real app, this might come from analytics platform
total_visitors = 10000 # Example - would come from analytics
conversion_rate = (order_stats['total_orders'] / total_visitors * 100) if total_visitors > 0 else 0
# Create cards
return [
# Revenue card
CardComponent(
title=_("Revenue"),
value=f"${order_stats['total_revenue'] or 0:,.2f}",
change="+12.5%", # Would calculate from previous period
trend="up",
color="primary",
icon_class="ri-money-dollar-circle-line",
),
# Orders card
CardComponent(
title=_("Orders"),
value=order_stats['total_orders'] or 0,
change="+8.2%",
trend="up",
color="success",
icon_class="ri-shopping-cart-2-line",
),
# Average order value card
CardComponent(
title=_("Avg Order"),
value=f"${order_stats['avg_order_value'] or 0:.2f}",
change="+3.1%",
trend="up",
color="info",
icon_class="ri-price-tag-3-line",
),
# New customers card
CardComponent(
title=_("New Customers"),
value=new_customers,
change="+15.7%",
trend="up",
color="warning",
icon_class="ri-user-add-line",
),
# Conversion rate card
CardComponent(
title=_("Conversion Rate"),
value=f"{conversion_rate:.1f}%",
change="+0.4%",
trend="up",
color="secondary",
icon_class="ri-bar-chart-line",
),
]
步骤 3: 实现图表¶
def get_charts(self):
"""Create chart components with e-commerce data visualizations"""
return [
self.create_sales_trend_chart(),
self.create_inventory_chart(),
self.create_top_products_chart(),
]
def create_sales_trend_chart(self):
"""Create sales trend line chart"""
end_date = timezone.now()
start_date = end_date - timedelta(days=self.timeframe_days)
# Get daily sales data
daily_sales = Order.objects.filter(
order_date__range=(start_date, end_date)
).extra({
'date': "date(order_date)"
}).values('date').annotate(
total=Sum('total_amount'),
count=Count('id')
).order_by('date')
# Prepare chart data
dates = []
revenue = []
orders = []
for day in daily_sales:
date_obj = day['date']
if hasattr(date_obj, 'strftime'):
dates.append(date_obj.strftime('%Y-%m-%d'))
else:
dates.append(str(date_obj))
revenue.append(float(day['total'] or 0))
orders.append(day['count'])
# Fill missing days
all_dates = [
(start_date + timedelta(days=i)).strftime('%Y-%m-%d')
for i in range(self.timeframe_days + 1)
]
# Create data lookup
revenue_lookup = dict(zip(dates, revenue))
orders_lookup = dict(zip(dates, orders))
revenue_filled = [revenue_lookup.get(date, 0) for date in all_dates]
orders_filled = [orders_lookup.get(date, 0) for date in all_dates]
# Create chart
return ChartComponent(
title=_("Sales Trend"),
chart_type="line",
data={
"labels": all_dates,
"datasets": [
{
"label": _("Revenue"),
"data": revenue_filled,
"borderColor": "rgb(75, 192, 192)",
"backgroundColor": "rgba(75, 192, 192, 0.2)",
"yAxisID": "y",
},
{
"label": _("Orders"),
"data": orders_filled,
"borderColor": "rgb(255, 99, 132)",
"backgroundColor": "rgba(255, 99, 132, 0.2)",
"yAxisID": "y1",
}
]
},
options={
"responsive": True,
"interaction": {
"mode": "index",
"intersect": False,
},
"scales": {
"x": {
"display": True,
"title": {
"display": True,
"text": _("Date")
}
},
"y": {
"type": "linear",
"display": True,
"position": "left",
"title": {
"display": True,
"text": _("Revenue ($)")
}
},
"y1": {
"type": "linear",
"display": True,
"position": "right",
"title": {
"display": True,
"text": _("Orders")
},
"grid": {
"drawOnChartArea": False,
},
},
},
"plugins": {
"title": {
"display": True,
"text": _("Sales Trend (Last {days} Days)").format(days=self.timeframe_days)
}
}
},
height=400
)
def create_inventory_chart(self):
"""Create inventory status pie chart"""
# Get inventory by category
inventory_by_category = Product.objects.values('category').annotate(
total_products=Count('id'),
total_stock=Sum('stock_quantity'),
low_stock=Count('id', filter=Q(stock_quantity__lte=models.F('low_stock_threshold')))
)
# Prepare chart data
categories = []
product_counts = []
colors = [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(153, 102, 255)',
]
for item in inventory_by_category:
# Get display name for category
category_display = dict(Product.CATEGORY_CHOICES).get(item['category'], item['category'])
categories.append(category_display)
product_counts.append(item['total_products'])
return ChartComponent(
title=_("Inventory by Category"),
chart_type="pie",
data={
"labels": categories,
"datasets": [{
"label": _("Products"),
"data": product_counts,
"backgroundColor": colors[:len(categories)],
"hoverOffset": 4
}]
},
options={
"responsive": True,
"plugins": {
"legend": {
"position": "right",
},
"title": {
"display": True,
"text": _("Product Distribution by Category")
}
}
},
height=400
)
def create_top_products_chart(self):
"""Create top-selling products bar chart"""
end_date = timezone.now()
start_date = end_date - timedelta(days=self.timeframe_days)
# Get top products by revenue
top_products = OrderItem.objects.filter(
order__order_date__range=(start_date, end_date)
).values(
'product__name'
).annotate(
total_revenue=Sum(models.F('quantity') * models.F('price')),
total_quantity=Sum('quantity')
).order_by('-total_revenue')[:10]
# Prepare chart data
product_names = [item['product__name'][:20] + "..." if len(item['product__name']) > 20
else item['product__name'] for item in top_products]
revenues = [float(item['total_revenue'] or 0) for item in top_products]
return ChartComponent(
title=_("Top Selling Products"),
chart_type="bar",
data={
"labels": product_names,
"datasets": [{
"label": _("Revenue ($)"),
"data": revenues,
"backgroundColor": "rgba(54, 162, 235, 0.5)",
"borderColor": "rgb(54, 162, 235)",
"borderWidth": 1
}]
},
options={
"responsive": True,
"plugins": {
"legend": {
"display": True,
"position": "top",
},
"title": {
"display": True,
"text": _("Top 10 Products by Revenue")
}
},
"scales": {
"y": {
"beginAtZero": True,
"title": {
"display": True,
"text": _("Revenue ($)")
}
}
}
},
height=400
)
步骤 4: 实现表格¶
def get_tables(self):
"""Create table components with recent data"""
return [
self.create_recent_orders_table(),
self.create_low_stock_table(),
self.create_customer_locations_table(),
]
def create_recent_orders_table(self):
"""Create table of recent orders"""
recent_orders = Order.objects.all().order_by('-order_date')[:10]
return TableComponent(
title=_("Recent Orders"),
columns=[
{"name": _("Order #"), "key": "order_number", "width": "120px"},
{"name": _("Customer"), "key": "customer"},
{"name": _("Date"), "key": "order_date", "type": "datetime"},
{"name": _("Amount"), "key": "total_amount", "type": "currency"},
{"name": _("Status"), "key": "status", "type": "badge", "badge_colors": {
"pending": "warning",
"processing": "info",
"shipped": "primary",
"delivered": "success",
"cancelled": "danger",
}},
],
data=[
{
"order_number": order.order_number,
"customer": order.customer.name,
"order_date": order.order_date,
"total_amount": order.total_amount,
"status": order.status,
}
for order in recent_orders
]
)
def create_low_stock_table(self):
"""Create table of low stock products"""
low_stock_products = Product.objects.filter(
stock_quantity__lte=models.F('low_stock_threshold')
).order_by('stock_quantity')[:10]
return TableComponent(
title=_("Low Stock Alerts"),
columns=[
{"name": _("Product"), "key": "name"},
{"name": _("Category"), "key": "category"},
{"name": _("Current Stock"), "key": "stock_quantity", "type": "number"},
{"name": _("Threshold"), "key": "low_stock_threshold", "type": "number"},
{"name": _("Status"), "key": "status", "type": "badge"},
],
data=[
{
"name": product.name,
"category": dict(Product.CATEGORY_CHOICES).get(product.category, product.category),
"stock_quantity": product.stock_quantity,
"low_stock_threshold": product.low_stock_threshold,
"status": "Critical" if product.stock_quantity <= 5 else "Warning",
}
for product in low_stock_products
]
)
def create_customer_locations_table(self):
"""Create table of customer locations"""
customer_locations = Customer.objects.values('location').annotate(
customer_count=Count('id')
).order_by('-customer_count')[:10]
return TableComponent(
title=_("Customer Locations"),
columns=[
{"name": _("Location"), "key": "location"},
{"name": _("Customers"), "key": "customer_count", "type": "number"},
{"name": _("Percentage"), "key": "percentage", "type": "percentage"},
],
data=[
{
"location": item['location'],
"customer_count": item['customer_count'],
"percentage": (item['customer_count'] / Customer.objects.count() * 100)
if Customer.objects.count() > 0 else 0,
}
for item in customer_locations
]
)
步骤 5: 实现布局¶
def get_layout(self):
"""Create the dashboard layout"""
layout = Layout(columns=12)
# Get all components
cards = self.get_cards()
charts = self.get_charts()
tables = self.get_tables()
# Row 1: cards (5 cards with custom widths)
# Since 5 doesn't divide evenly into 12, we use: 2-3-2-3-2
layout.add_row([
(cards[0], 2), # Revenue
(cards[1], 3), # Orders
(cards[2], 2), # Avg Order
(cards[3], 3), # New Customers
(cards[4], 2), # Conversion Rate
], height="auto")
# Row 2: Sales trend and inventory charts
layout.add_row([
(charts[0], 8), # Sales trend (line chart)
(charts[1], 4), # Inventory (pie chart)
], height="400px")
# Row 3: Top products chart and customer locations
layout.add_row([
(charts[2], 8), # Top products (bar chart)
(tables[2], 4), # Customer locations (table)
], height="400px")
# Row 4: Recent orders and low stock alerts
layout.add_row([
(tables[0], 8), # Recent orders
(tables[1], 4), # Low stock alerts
], height="auto")
# Row 5: Timeframe selector (custom component)
if hasattr(self, 'create_timeframe_selector'):
selector = self.create_timeframe_selector()
layout.add_row([(selector, 12)], height="auto")
return layout
步骤 6: 添加上下文数据¶
def get_context_data(self, request, **kwargs):
"""Add dashboard-specific context data"""
context = super().get_context_data(request, **kwargs)
# Add timeframe for template use
context['timeframe_days'] = self.timeframe_days
# Add date range for display
end_date = timezone.now()
start_date = end_date - timedelta(days=self.timeframe_days)
context['date_range'] = {
'start': start_date.strftime('%Y-%m-%d'),
'end': end_date.strftime('%Y-%m-%d'),
}
# Add dashboard statistics
context['dashboard_stats'] = {
'total_products': Product.objects.count(),
'total_customers': Customer.objects.count(),
'total_orders': Order.objects.count(),
'active_timeframe': self.timeframe_days,
}
# Add export URLs
if request:
context['export_urls'] = {
'csv': request.path + '?export=csv',
'pdf': request.path + '?export=pdf',
'excel': request.path + '?export=excel',
}
return context
高级功能¶
时间范围选择器组件¶
def create_timeframe_selector(self):
"""Create a timeframe selector component"""
from django_admin_dashboards.base import Component
class TimeframeSelector(Component):
component_type = "timeframe_selector"
template_name = "analytics/components/timeframe_selector.html"
def __init__(self, current_days=30, **kwargs):
super().__init__(**kwargs)
self.current_days = current_days
self.options = [
{"value": 7, "label": "Last 7 Days"},
{"value": 30, "label": "Last 30 Days"},
{"value": 90, "label": "Last 90 Days"},
{"value": 365, "label": "Last Year"},
]
def get_context_data(self, request):
context = super().get_context_data(request)
context.update({
"current_days": self.current_days,
"options": self.options,
})
return context
return TimeframeSelector(current_days=self.timeframe_days)
自定义 Template¶
<div class="timeframe-selector">
<div class="selector-label">
<i class="ri-calendar-line"></i>
<span>Timeframe:</span>
</div>
<div class="selector-options">
{% for option in options %}
<a href="?days={{ option.value }}"
class="timeframe-option {% if current_days == option.value %}active{% endif %}">
{{ option.label }}
</a>
{% endfor %}
</div>
<div class="selector-custom">
<form method="get" class="custom-days-form">
<input type="number" name="days" value="{{ current_days }}"
min="1" max="365" class="custom-days-input">
<button type="submit" class="custom-days-submit">
<i class="ri-check-line"></i>
</button>
</form>
</div>
</div>
自定义 CSS¶
/* static/analytics/css/custom.css */
/* card customizations */
.dashboard .card-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.dashboard .card-success {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
}
.dashboard .card-warning {
background: linear-gradient(135deg, #f7971e 0%, #ffd200 100%);
color: #333;
}
/* Timeframe selector */
.timeframe-selector {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
margin-bottom: 20px;
}
.selector-label {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: #495057;
}
.timeframe-option {
padding: 6px 12px;
background: white;
border: 1px solid #dee2e6;
border-radius: 4px;
color: #495057;
text-decoration: none;
transition: all 0.2s;
}
.timeframe-option:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.timeframe-option.active {
background: #007bff;
color: white;
border-color: #007bff;
}
/* Dark mode support */
[data-theme="dark"] .timeframe-selector {
background: #2d3748;
}
[data-theme="dark"] .selector-label {
color: #e2e8f0;
}
[data-theme="dark"] .timeframe-option {
background: #4a5568;
border-color: #718096;
color: #e2e8f0;
}
[data-theme="dark"] .timeframe-option:hover {
background: #5a6578;
}
自定义 JavaScript¶
// static/analytics/js/custom.js
document.addEventListener('DOMContentLoaded', function() {
// Add export button functionality
const exportButtons = document.querySelectorAll('.export-btn');
exportButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
const format = this.dataset.format;
const url = this.href;
// Show loading indicator
showLoadingIndicator();
// Perform export
fetch(url)
.then(response => response.blob())
.then(blob => {
// Create download link
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = `dashboard_export.${format}`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(downloadUrl);
// Hide loading indicator
hideLoadingIndicator();
})
.catch(error => {
console.error('Export error:', error);
hideLoadingIndicator();
alert('Export failed. Please try again.');
});
});
});
// Auto-refresh dashboard data
if (window.location.search.includes('auto_refresh=true')) {
const refreshInterval = parseInt(
new URLSearchParams(window.location.search).get('refresh_interval') || '30'
);
if (refreshInterval > 0) {
setInterval(() => {
refreshDashboardData();
}, refreshInterval * 1000);
}
}
function refreshDashboardData() {
// Implementation depends on your needs
console.log('Refreshing dashboard data...');
// You might:
// 1. Reload specific components via AJAX
// 2. Refresh the entire page
// 3. Update charts with new data
}
function showLoadingIndicator() {
// Implementation
}
function hideLoadingIndicator() {
// Implementation
}
});
配置¶
设置配置¶
# settings.py
# Add to INSTALLED_APPS
INSTALLED_APPS = [
# ... other apps ...
'django_admin_dashboards',
'analytics',
]
# Configure dashboards
DJANGO_ADMIN_DASHBOARDS = {
# Admin index dashboard
"admin:index": "analytics.dashboards.EcommerceDashboard",
# App-specific dashboards
"admin:app_list": {
"analytics": "analytics.dashboards.EcommerceDashboard",
},
}
# Optional: Dashboard-specific settings
DASHBOARD_SETTINGS = {
'ECOMMERCE_DASHBOARD': {
'default_timeframe': 30,
'enable_export': True,
'auto_refresh': False,
'refresh_interval': 30, # seconds
}
}
URL 配置¶
# analytics/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('dashboard/', views.DashboardView.as_view(), name='ecommerce_dashboard'),
path('dashboard/export/', views.ExportView.as_view(), name='dashboard_export'),
]
视图集成¶
# analytics/views.py
from django.views.generic import TemplateView
from django.http import HttpResponse, JsonResponse
from .dashboards import EcommerceDashboard
import csv
import json
class DashboardView(TemplateView):
template_name = 'analytics/dashboard.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Create dashboard instance
dashboard = EcommerceDashboard(request=self.request)
# Add to context
context['dashboard'] = dashboard
context['dashboard_title'] = dashboard.title
return context
class ExportView(TemplateView):
"""Handle dashboard data export"""
def get(self, request, *args, **kwargs):
export_format = request.GET.get('format', 'csv')
dashboard = EcommerceDashboard(request=request)
if export_format == 'csv':
return self.export_csv(dashboard)
elif export_format == 'json':
return self.export_json(dashboard)
else:
return HttpResponse('Unsupported format', status=400)
def export_csv(self, dashboard):
"""Export dashboard data as CSV"""
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="dashboard_export.csv"'
writer = csv.writer(response)
# Write header
writer.writerow(['Metric', 'Value', 'Timeframe'])
# Write KPI data
cards = dashboard.get_cards()
for card in cards:
writer.writerow([card.title, card.value, dashboard.timeframe_days])
return response
def export_json(self, dashboard):
"""Export dashboard data as JSON"""
data = {
'timeframe_days': dashboard.timeframe_days,
'export_date': timezone.now().isoformat(),
'kpis': []
}
cards = dashboard.get_cards()
for card in cards:
data['kpis'].append({
'title': card.title,
'value': card.value,
'color': card.color,
})
return JsonResponse(data)
部署¶
1. 收集静态文件¶
2. 运行 迁移¶
3. 创建 Superuser¶
4. 测试数据看板¶
- 启动开发服务器:
python manage.py runserver - 导航到:
http://localhost:8000/admin/ - 使用超级用户凭据登录
- 数据看板应出现
5. 生产环境注意事项¶
- 缓存: 对昂贵查询实施缓存
- 性能: 使用索引优化数据库查询
- 安全: 仅限授权用户访问
- 监控: 添加日志记录和监控
- 备份: 定期备份数据看板配置
故障排除¶
常见问题¶
1. 数据看板未显示¶
症状: 管理界面显示默认 Django Admin 而非数据看板。
解决方案: - 检查 DJANGO_ADMIN_DASHBOARDS 设置是否正确 - 验证
分析 应用是否在 INSTALLED_APPS 中 - 确保您正在访问 /admin/ URL -
检查导入路径中的拼写错误
2. 图表未加载¶
症状: 图表显示"Loading..."但从未显示。
解决方案: - 检查浏览器控制台中的 JavaScript 错误 - 验证 图表.js
是否正在加载(Dashboard.media.js) - 确保数据格式符合 图表.js
要求 - 如果加载外部数据,检查 CORS 问题
3. 性能缓慢¶
症状: 数据看板加载缓慢,尤其是在大型数据集时。
解决方案: - 实施查询优化(使用
select_related、prefetch_related) -
在频繁查询的字段上添加数据库索引 - 对昂贵计算实施缓存 -
减少时间范围或限制显示的数据
4. 数据缺失¶
症状: 某些组件显示"No 数据"或错误值。
解决方案: - 检查数据库是否有实际数据 - 验证模型关系是否正确 - 首先在 Django shell 中测试查询 - 在组件方法中添加错误处理
调试技巧¶
# Add debug logging to dashboard
import logging
logger = logging.getLogger(__name__)
class DebugEcommerceDashboard(EcommerceDashboard):
def get_cards(self):
logger.debug(f"Getting cards for timeframe: {self.timeframe_days}")
try:
cards = super().get_cards()
logger.debug(f"Created {len(cards)} cards")
return cards
except Exception as e:
logger.error(f"Error creating cards: {e}", exc_info=True)
# Return fallback cards
return [CardComponent(title="Error", value="N/A", color="danger")]
Check Django logs and browser developer tools for errors.
后续步骤¶
可考虑的增强功能¶
- 实时更新: WebSocket 集成实现实时数据
- 高级筛选: 多维筛选能力
- 预测分析: 机器学习集成进行预测
- 自定义可视化: D3.js 或其他可视化库
- 移动应用: React Native 或 Flutter 移动数据看板
- 警报系统: 针对关键指标的电子邮件/短信通知
- 基于角色的视图: 不同用户角色的不同数据看板
进一步学习¶
结论¶
这个数据看板展示了 Django Admin Dashboards 的强大功能和灵活性。通过此示例,您已学会如何:
- 为特定业务需求设计数据看板布局
- 实现各种组件类型(卡片、图表、表格)
- 使用 Django ORM 高效处理数据
- 添加交互式功能和自定义
- 部署和维护生产数据看板
相同的原则可应用于为任何领域构建数据看板:医疗分析、财务报告、物联网监控或项目管理。
Remember to start 简单, iterate 基于 user feedback, and always optimize for performance and usability.