跳转至

创建数据看板

学习如何为您的 Django Admin 界面创建数据看板。本指南涵盖从数据看板创建到高级功能的所有内容。

目录

数据看板基础

什么是数据看板?

Django Admin Dashboards 中的数据看板是一个扩展了基础 Dashboard 类的 Python 类。它定义:

  1. 布局: 组件在页面上的排列方式
  2. 组件: 要显示的数据可视化(卡片、图表、表格)
  3. 配置: 显示选项和行为控制
  4. 数据源: 数据来源(模型、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

后续步骤

资源