认证数据看板示例¶
认证数据看板是一个综合性示例,展示了 Django Admin Dashboards 在用户认证数据方面的能力。此示例演示了如何创建一个包含 KPI、图表、表格和高级功能的生产就绪数据看板。
目录¶
概述¶
什么是认证数据看板?¶
认证数据看板是一个内置数据看板,提供 Django 认证系统的洞察:
- 用户统计:总用户数、启用/禁用状态、用户角色
- 注册趋势:用户随时间增长情况
- 登录活动:用户参与模式
- 管理操作:管理员日志条目和更改
- 最近活动:最新用户注册和登录
主要特点¶
- 生产就绪:包含错误处理、数据验证和性能优化
- 多语言:支持 Django 翻译系统的国际化
- 响应式设计:适用于桌面、平板和移动设备
- 功能丰富:演示所有组件类型和布局模式
- 可扩展:易于根据特定需求进行自定义和扩展
- 可点击的卡片数字:卡片上的数字可以作为链接,直接跳转到相应的 Django Admin 列表页,并自动应用过滤条件
数据看板预览¶
┌─────────────────────────────────────────────────────────────────────┐
│ Home │
├─────────────┬─────────────┬─────────────┬─────────────┬─────────────┤
│ User Status │ User Roles │ Recent Act. │ Silent Users│ Groups │
│ 100/25/125 │ 80/15/5 │ 85 │ 15 │ 8 │
├─────────────┴─────────────┴─────────────┴─────────────┴─────────────┤
│ Admin Logs: 1,240 │
├─────────────────────────────────────────────────────────────────────┤
│ User Registration Trend │ User Role Distribution │
│ [Line Chart] │ [Pie Chart] │
├─────────────────────────────────────────────────────────────────────┤
│ User Login & Admin Actions │
│ [Bar Chart] │
├─────────────────────────────────────────────────────────────────────┤
│ Recently Registered Users │ Recently Logged In Users │
│ [Table] │ [Table] │
└─────────────────────────────────────────────────────────────────────┘
功能¶
核心功能¶
-
多值 卡片
-
用户状态:启用/禁用/总计数量
-
用户角色:员工/管理员/超级管理员
-
基于时间的指标:最近活跃(30天)和沉默(90天)用户
-
交互式图表
-
用户注册趋势(折线图)
-
用户角色分布(饼图)
-
用户登录和管理操作(条形图)
-
数据表格
-
最近注册用户(最近10个)
-
最近登录用户(最近10个)
-
高级数据看板功能
-
全屏模式支持
-
暗色模式兼容性
-
URL 参数控制
-
响应式设计
技术功能¶
- 数据库优化:使用注解和聚合的高效查询
- 错误处理:对缺失数据的优雅降级
- 缓存:智能数据缓存以提高性能
- 国际化:完整的翻译支持
- 无障碍访问:语义化 HTML 和 ARIA 属性
数据看板结构¶
类定义¶
from django_admin_dashboards.base import Dashboard, CardComponent, ChartComponent, Layout, TableComponent
class AuthAppDashboard(Dashboard):
"""Auth app specific dashboard (used for both admin index and auth app list)"""
title = _("首页") # "Home" in Chinese
show_dashboard_title = True
show_admin_title = False
hide_others_in_fullscreen = True
force_hide_others = False
布局结构¶
数据看板使用结构化的 12 列网格布局:
- 第 1 行:两个多值 卡片(每列6列)
- 第 2 行:四个单值 卡片(每列3列)
- 第 3 行:两个并排的图表(每列6列)
- 第 4 行:一个全宽图表(12列)
- 第 5 行:两个并排的表格(每列6列)
组件清单¶
| 组件 | 类型 | 数据源 | 用途 |
|---|---|---|---|
| User Status Card | CardComponent |
User.objects |
用户活动统计 |
| User Roles Card | CardComponent |
User.objects |
角色分布 |
| Recent Active Card | CardComponent |
User.objects |
30天活动 |
| Silent Users Card | CardComponent |
User.objects |
沉默用户(90天) |
| Groups Card | CardComponent |
Group.objects |
组数量 |
| Admin Logs Card | CardComponent |
LogEntry.objects |
管理活动 |
| Registration Chart | ChartComponent |
User.objects |
增长趋势 |
| Role Distribution Chart | ChartComponent |
User.objects |
角色百分比 |
| Login Actions Chart | ChartComponent |
User.objects, LogEntry |
活动比较 |
| Recent Users Table | TableComponent |
User.objects |
最新注册 |
| Recent Logins Table | TableComponent |
User.objects |
最近登录 |
实现步骤¶
步骤 1:数据看板设置¶
# Import required modules
from datetime import datetime, timedelta
from django.db.models import Count
from django.contrib.auth.models import User, Group
from django.contrib.admin.models import LogEntry
from django.utils import timezone
from django.utils.translation import gettext_lazy as _, gettext
from django_admin_dashboards.base import Dashboard, CardComponent, ChartComponent, Layout, TableComponent
class AuthAppDashboard(Dashboard):
"""Auth app specific dashboard"""
# Dashboard configuration
title = _("首页") # Translated title
show_dashboard_title = True
show_admin_title = False
# Fullscreen controls
hide_others_in_fullscreen = True # Enable feature
force_hide_others = False # Disable force hide
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()
# Arrange cards
# First two cards (multi-value) get 6 columns each
layout.add_row([(cards[0], 6), (cards[1], 6)], height="auto")
# Remaining 4 cards in a row with 3 columns each
layout.add_row([
(cards[2], 3),
(cards[3], 3),
(cards[4], 3),
(cards[5], 3),
], height="auto")
# Arrange charts
layout.add_row([(charts[0], 6), (charts[1], 6)], height="400px")
layout.add_row([(charts[2], 12)], height="400px")
# Arrange tables
layout.add_row([(tables[0], 6), (tables[1], 6)], height="auto")
return layout
步骤 2:卡片实现¶
def get_cards(self):
"""Create card components with user statistics"""
# Calculate user statistics
total_users = User.objects.count()
active_users = User.objects.filter(is_active=True).count()
disabled_users = User.objects.filter(is_active=False).count()
# User role breakdown
staff_users = User.objects.filter(is_staff=True, is_superuser=False).count()
superusers = User.objects.filter(is_superuser=True).count()
normal_users = User.objects.filter(is_staff=False, is_superuser=False).count()
# Group count
group_count = Group.objects.count()
# Log entries count
log_entries = LogEntry.objects.count()
# Time-based user activity
thirty_days_ago = timezone.now() - timedelta(days=30)
active_recent_users = User.objects.filter(
last_login__gte=thirty_days_ago, is_active=True
).count()
ninety_days_ago = timezone.now() - timedelta(days=90)
silent_users = User.objects.filter(
is_active=True, last_login__lt=ninety_days_ago
).count()
# Create card components
return [
# Card 1: User Status (multi-value)
CardComponent(
title=gettext("User Status"),
values=[active_users, disabled_users, total_users],
value_labels=[gettext("Active"), gettext("Disabled"), gettext("Total")],
color="primary",
icon_class="ri-user-2-line",
),
# Card 2: User Roles (multi-value)
CardComponent(
title=gettext("User Roles"),
values=[normal_users, staff_users, superusers],
value_labels=[
gettext("Employee"),
gettext("Admin"),
gettext("Super Admin"),
],
color="success",
icon_class="ri-group-line",
),
# Card 3: Recent Active Users
CardComponent(
title=gettext("Recent Active (30d)"),
value=active_recent_users,
color="success",
icon_class="ri-time-line",
),
# Card 4: Silent Users
CardComponent(
title=gettext("Silent Users (90d)"),
value=silent_users,
color="warning",
icon_class="ri-user-unfollow-line",
),
# Card 5: Groups
CardComponent(
title=gettext("Groups"),
value=group_count,
color="info",
icon_class="ri-group-line",
),
# Card 6: Admin Logs
CardComponent(
title=gettext("Admin Logs"),
value=log_entries,
color="secondary",
icon_class="ri-history-line",
),
]
步骤 3:图表实现¶
def get_charts(self):
"""Create chart components with user data visualizations"""
# Chart 1: User Registration Trend (Line Chart)
registration_data = self.get_registration_trend_data()
registration_chart = ChartComponent(
title=gettext("User Registration Trend"),
chart_type="line",
data=registration_data,
options={
"responsive": True,
"plugins": {
"legend": {"position": "top"},
"title": {
"display": True,
"text": gettext("New Users per Day (Last 30 Days)")
}
},
"scales": {
"y": {"beginAtZero": True}
}
},
height=400
)
# Chart 2: User Role Distribution (Pie Chart)
role_data = self.get_role_distribution_data()
role_chart = ChartComponent(
title=gettext("User Role Distribution"),
chart_type="pie",
data=role_data,
options={
"responsive": True,
"plugins": {
"legend": {"position": "right"},
"title": {
"display": True,
"text": gettext("User Roles by Percentage")
}
}
},
height=400
)
# Chart 3: User Login & Admin Actions (Bar Chart)
activity_data = self.get_user_activity_data()
activity_chart = ChartComponent(
title=gettext("User Login & Admin Actions"),
chart_type="bar",
data=activity_data,
options={
"responsive": True,
"plugins": {
"legend": {"position": "top"},
"title": {
"display": True,
"text": gettext("Daily Activity (Last 30 Days)")
}
},
"scales": {
"x": {"stacked": True},
"y": {"stacked": True, "beginAtZero": True}
}
},
height=400
)
return [registration_chart, role_chart, activity_chart]
def get_registration_trend_data(self):
"""Get data for user registration trend chart"""
thirty_days_ago = timezone.now() - timedelta(days=30)
# Query daily user registrations
user_registrations = (
User.objects.filter(date_joined__gte=thirty_days_ago)
.extra({"date": "date(date_joined)"})
.values("date")
.annotate(count=Count("id"))
.order_by("date")
)
# Prepare chart data
dates = []
counts = []
for item in user_registrations:
date_value = item["date"]
if hasattr(date_value, "strftime"):
dates.append(date_value.strftime("%Y-%m-%d"))
else:
dates.append(str(date_value))
counts.append(item["count"])
# Fill missing days with zero
if not dates:
dates = [
(timezone.now() - timedelta(days=i)).strftime("%Y-%m-%d")
for i in range(29, -1, -1)
]
counts = [0] * 30
return {
"labels": dates,
"datasets": [{
"label": gettext("New Users"),
"data": counts,
"borderColor": "rgb(75, 192, 192)",
"backgroundColor": "rgba(75, 192, 192, 0.2)",
"tension": 0.1
}]
}
步骤 4:表格实现¶
def get_tables(self):
"""Create table components with recent user data"""
# Table 1: Recently Registered Users
recent_users = User.objects.all().order_by('-date_joined')[:10]
recent_users_table = TableComponent(
title=gettext("Recently Registered Users"),
columns=[
{"name": gettext("Username"), "key": "username"},
{"name": gettext("Email"), "key": "email"},
{"name": gettext("Date Joined"), "key": "date_joined"},
{"name": gettext("Status"), "key": "status", "type": "badge"},
],
data=[
{
"username": user.username,
"email": user.email,
"date_joined": user.date_joined.strftime("%Y-%m-%d %H:%M"),
"status": "active" if user.is_active else "inactive"
}
for user in recent_users
]
)
# Table 2: Recently Logged In Users
recent_logins = User.objects.filter(
last_login__isnull=False
).order_by('-last_login')[:10]
recent_logins_table = TableComponent(
title=gettext("Recently Logged In Users"),
columns=[
{"name": gettext("Username"), "key": "username"},
{"name": gettext("Last Login"), "key": "last_login"},
{"name": gettext("Login Count"), "key": "login_count"},
{"name": gettext("Days Since"), "key": "days_since"},
],
data=[
{
"username": user.username,
"last_login": user.last_login.strftime("%Y-%m-%d %H:%M"),
"login_count": getattr(user, 'login_count', 'N/A'),
"days_since": (timezone.now() - user.last_login).days
if user.last_login else "Never"
}
for user in recent_logins
]
)
return [recent_users_table, recent_logins_table]
步骤 5:上下文数据和请求处理¶
def get_context_data(self, request, **kwargs):
"""Add dashboard-specific context data"""
context = super().get_context_data(request, **kwargs)
# Add request to context for template use
context['current_user'] = request.user
# Add dashboard configuration
context['dashboard_config'] = {
'supports_fullscreen': self.hide_others_in_fullscreen,
'supports_dark_mode': True,
'last_updated': timezone.now(),
}
# Add any additional context needed by components
return context
配置¶
设置配置¶
要使用认证数据看板,请将其添加到 Django 设置中:
# settings.py
INSTALLED_APPS = [
# ... other apps ...
'django_admin_dashboards',
'django.contrib.admin',
# ... other apps ...
]
# Dashboard configuration
DJANGO_ADMIN_DASHBOARDS = {
# Admin index dashboard
"admin:index": "django_admin_dashboards.contrib.auth.dashboard.AuthAppDashboard",
# App-specific dashboards
"admin:app_list": {
"auth": "django_admin_dashboards.contrib.auth.dashboard.AuthAppDashboard",
},
}
URL 配置¶
数据看板自动与 Django 管理 URL 集成:
- 管理首页:
/admin/(使用 AuthAppDashboard) - 认证应用:
/admin/auth/(使用 AuthAppDashboard) - 带参数:
/admin/?_dark_mode_on=true&_hide_others_in_fullscreen=true
模板集成¶
数据看板适用于默认管理模板。对于自定义模板:
{% extends "admin/base_site.html" %}
{% load dashboard_tags %}
{% block extrastyle %}
{{ block.super }}
{{ dashboard.media.css }}
{% endblock %}
{% block extrahead %}
{{ block.super }}
{{ dashboard.media.js }}
{% endblock %}
{% block content %}
{% dashboard %}
{% endblock %}
使用示例¶
基本用法¶
-
访问数据看板:
-
查看认证应用数据看板:
使用 URL 参数的高级用法¶
-
演示模式(全屏 + 暗色模式):
-
强制隐藏模式(始终隐藏界面):
-
浅色模式(强制浅色主题):
-
自动主题(遵循系统偏好):
编程式用法¶
# In views.py or other Python code
from django_admin_dashboards.contrib.auth.dashboard import AuthAppDashboard
# Create dashboard instance
dashboard = AuthAppDashboard(request=request)
# Get layout
layout = dashboard.get_layout()
# Get context for template rendering
context = dashboard.get_context_data(request)
# Access components
cards = dashboard.get_cards()
charts = dashboard.get_charts()
tables = dashboard.get_tables()
自定义视图集成¶
# views.py
from django.views.generic import TemplateView
from django_admin_dashboards.contrib.auth.dashboard import AuthAppDashboard
class CustomDashboardView(TemplateView):
template_name = 'custom_dashboard.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Create dashboard
dashboard = AuthAppDashboard(request=self.request)
# Add to context
context['dashboard'] = dashboard
context['dashboard_title'] = dashboard.title
return context
自定义¶
扩展数据看板¶
创建扩展认证数据看板的数据看板:
# myapp/dashboards.py
from django_admin_dashboards.contrib.auth.dashboard import AuthAppDashboard
from django_admin_dashboards.base import CardComponent
class ExtendedAuthDashboard(AuthAppDashboard):
"""Extended auth dashboard with additional features"""
title = "Enhanced User Dashboard"
def get_cards(self):
# Get original cards
cards = super().get_cards()
# Add custom card
custom_card = CardComponent(
title="Custom Metric",
value=42,
color="primary",
icon_class="ri-star-line"
)
# Insert at position 2 (third card)
cards.insert(2, custom_card)
return cards
def get_tables(self):
# Get original tables
tables = super().get_tables()
# Add custom table
from .models import CustomModel
custom_data = CustomModel.objects.all()[:5]
custom_table = TableComponent(
title="Custom Data",
columns=["ID", "Name", "Value"],
data=[
{"ID": item.id, "Name": item.name, "Value": item.value}
for item in custom_data
]
)
tables.append(custom_table)
return tables
自定义布局¶
class CustomLayoutAuthDashboard(AuthAppDashboard):
"""Auth dashboard with custom layout"""
def get_layout(self):
layout = super().get_layout()
# Remove the last row (tables)
if layout.rows:
layout.rows = layout.rows[:-1]
# Add custom component
custom_component = self.create_custom_component()
layout.add_row([(custom_component, 12)])
return layout
def create_custom_component(self):
"""Create custom component for dashboard"""
# Implementation depends on your needs
pass
自定义样式¶
添加自定义 CSS 以覆盖数据看板样式:
/* static/css/custom_dashboard.css */
/* Override card colors */
.dashboard .card-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.dashboard .card-success {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
/* Custom table styles */
.dashboard table.custom-table {
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Dark mode customizations */
[data-theme="dark"] .dashboard .card {
border: 1px solid #374151;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
在数据看板中注册自定义 CSS:
class StyledAuthDashboard(AuthAppDashboard):
"""Auth dashboard with custom styling"""
class Media:
css = {
"all": (
"django_admin_dashboards/css/dashboard.css",
"remixicon/remixicon.css",
"css/custom_dashboard.css", # Custom CSS
)
}
js = ("django_admin_dashboards/js/chart.umd.js",)
最佳实践¶
1. 优化数据库查询¶
认证数据看板演示了几种优化技术:
def get_optimized_user_stats(self):
"""Optimized query using aggregation"""
from django.db.models import Count, Q
# Single query for multiple counts
stats = User.objects.aggregate(
total=Count('id'),
active=Count('id', filter=Q(is_active=True)),
staff=Count('id', filter=Q(is_staff=True, is_superuser=False)),
superusers=Count('id', filter=Q(is_superuser=True)),
)
return stats
2. 优雅处理缺失数据¶
def get_chart_data_safely(self):
"""Get chart data with error handling"""
try:
data = self.fetch_chart_data()
except (DatabaseError, ConnectionError) as e:
# Log error but don't crash dashboard
logger.error(f"Failed to fetch chart data: {e}")
# Return empty but valid data structure
data = {
"labels": [],
"datasets": [{
"label": "Data Unavailable",
"data": [],
"borderColor": "#ccc",
}]
}
return data
3. 缓存耗时操作¶
from django.core.cache import cache
class CachedAuthDashboard(AuthAppDashboard):
"""Auth dashboard with caching"""
def get_cards(self):
cache_key = f"dashboard_cards_{self.request.user.pk}"
cards = cache.get(cache_key)
if not cards:
# Calculate cards (expensive operation)
cards = super().get_cards()
# Cache for 5 minutes
cache.set(cache_key, cards, timeout=300)
return cards
4. 实施访问控制¶
class SecureAuthDashboard(AuthAppDashboard):
"""Auth dashboard with access control"""
def get_layout(self):
# Check user permissions
if not self.request.user.has_perm('auth.view_user'):
# Return minimal layout for unauthorized users
return self.get_public_layout()
# Return full layout for authorized users
return super().get_layout()
def get_public_layout(self):
"""Minimal layout for public viewing"""
layout = Layout(columns=12)
public_card = CardComponent(
title="Authentication Dashboard",
value="Access restricted",
color="secondary"
)
layout.add_component(public_card)
return layout
5. 测试数据看板¶
# tests.py
from django.test import TestCase
from django.test.client import RequestFactory
from django_admin_dashboards.contrib.auth.dashboard import AuthAppDashboard
class AuthDashboardTests(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.dashboard = AuthAppDashboard()
def test_dashboard_creation(self):
"""Test dashboard can be instantiated"""
self.assertIsNotNone(self.dashboard)
self.assertEqual(str(self.dashboard.title), "首页")
def test_cards(self):
"""Test cards are created"""
cards = self.dashboard.get_cards()
self.assertGreaterEqual(len(cards), 5)
# Check multi-value card
multi_value_card = cards[0]
self.assertTrue(hasattr(multi_value_card, 'values'))
self.assertEqual(len(multi_value_card.values), 3)
def test_dashboard_rendering(self):
"""Test dashboard renders via admin view"""
from django.urls import reverse
from django.contrib.auth.models import User
# Create superuser and log in
superuser = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='adminpass'
)
client = self.client
client.force_login(superuser)
# Access admin index
response = client.get(reverse('admin:index'))
self.assertEqual(response.status_code, 200)
# Check dashboard elements
self.assertContains(response, 'dashboard')
self.assertContains(response, '首页')
故障排除¶
常见问题¶
-
数据看板未显示
-
检查
DJANGO_ADMIN_DASHBOARDS设置是否配置正确 -
验证
django_admin_dashboards是否在INSTALLED_APPS中 -
确保访问的是
/admin/URL -
图表未加载
-
检查 JavaScript 控制台是否有错误
-
验证 Chart.js 是否已加载(
dashboard.media.js) -
确保数据格式符合 Chart.js 要求
-
表格未显示数据
-
验证数据库是否有用户数据
-
检查列键是否与数据字典键匹配
-
确保用户有权限查看数据
-
全屏/暗色模式无效
-
验证 URL 参数是否正确
-
检查数据看板类是否设置了
hide_others_in_fullscreen = True -
确保 JavaScript 未被阻止
调试技巧¶
# Add debug information to dashboard
class DebugAuthDashboard(AuthAppDashboard):
def get_context_data(self, request, **kwargs):
context = super().get_context_data(request, **kwargs)
# Add debug info
context['debug_info'] = {
'request_path': request.path,
'user': request.user.username,
'parameters': dict(request.GET),
'dashboard_class': self.__class__.__name__,
}
return context
检查浏览器控制台中的 JavaScript 错误和网络选项卡中的资源加载失败。