跳转至

数据看板示例

本指南将带领您从头开始为电子商务应用程序创建一个数据看板。您将学习如何设计、实现和部署一个生产就绪的数据看板,用于显示销售分析、库存状态和客户洞察。

目录

概述

我们将构建什么

数据看板,包含:

  • 销售指标: 收入、订单数、平均订单价值
  • 库存状态: 低库存警报、类别分布
  • 客户洞察: 新客户、客户位置
  • 产品性能: 热门产品、销售趋势
  • 交互式功能: 日期筛选、导出功能

数据看板预览

    ┌─────────────────────────────────────────────────────────────────────┐
                         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)

数据看板设计

需求分析

编码之前,定义数据看板需要显示的内容:

  1. 业务关键绩效指标 (KPI):

  2. 总收入

  3. 订单数量

  4. 平均订单价值

  5. 转化率

  6. 新客户

  7. 运营指标:

  8. 按类别划分的库存状态

  9. 低库存警报

  10. 订单状态分布

  11. 趋势分析:

  12. 随时间变化的销售趋势

  13. 热销产品

  14. 客户获取趋势

  15. 最近活动:

  16. 最近订单

  17. 新客户

  18. 库存变化

布局规划

使用12列网格设计布局:

  1. 行 1: 5 个 卡片(收入、订单、平均订单、新客户、转化率)
  2. 行 2: 销售趋势图表(8列)+ 库存状态(4列)
  3. 行 3: 热门产品图表(8列)+ 客户地图/表格(4列)
  4. 行 4: 最近订单表格(8列)+ 低库存警报(4列)

数据来源

识别数据来源:

  • Sales 数据: Order and OrderItem models
  • 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. 收集静态文件

    python manage.py collectstatic

2. 运行 迁移

    python manage.py makemigrations analytics
    python manage.py migrate

3. 创建 Superuser

    python manage.py createsuperuser

4. 测试数据看板

  1. 启动开发服务器:python manage.py runserver
  2. 导航到:http://localhost:8000/admin/
  3. 使用超级用户凭据登录
  4. 数据看板应出现

5. 生产环境注意事项

  • 缓存: 对昂贵查询实施缓存
  • 性能: 使用索引优化数据库查询
  • 安全: 仅限授权用户访问
  • 监控: 添加日志记录和监控
  • 备份: 定期备份数据看板配置

故障排除

常见问题

1. 数据看板未显示

症状: 管理界面显示默认 Django Admin 而非数据看板。

解决方案: - 检查 DJANGO_ADMIN_DASHBOARDS 设置是否正确 - 验证 分析 应用是否在 INSTALLED_APPS 中 - 确保您正在访问 /admin/ URL - 检查导入路径中的拼写错误

2. 图表未加载

症状: 图表显示"Loading..."但从未显示。

解决方案: - 检查浏览器控制台中的 JavaScript 错误 - 验证 图表.js 是否正在加载(Dashboard.media.js) - 确保数据格式符合 图表.js 要求 - 如果加载外部数据,检查 CORS 问题

3. 性能缓慢

症状: 数据看板加载缓慢,尤其是在大型数据集时。

解决方案: - 实施查询优化(使用 select_relatedprefetch_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.

后续步骤

可考虑的增强功能

  1. 实时更新: WebSocket 集成实现实时数据
  2. 高级筛选: 多维筛选能力
  3. 预测分析: 机器学习集成进行预测
  4. 自定义可视化: D3.js 或其他可视化库
  5. 移动应用: React Native 或 Flutter 移动数据看板
  6. 警报系统: 针对关键指标的电子邮件/短信通知
  7. 基于角色的视图: 不同用户角色的不同数据看板

进一步学习

结论

这个数据看板展示了 Django Admin Dashboards 的强大功能和灵活性。通过此示例,您已学会如何:

  1. 为特定业务需求设计数据看板布局
  2. 实现各种组件类型(卡片、图表、表格)
  3. 使用 Django ORM 高效处理数据
  4. 添加交互式功能和自定义
  5. 部署和维护生产数据看板

相同的原则可应用于为任何领域构建数据看板:医疗分析、财务报告、物联网监控或项目管理。

Remember to start 简单, iterate 基于 user feedback, and always optimize for performance and usability.