组件¶
基础使用¶
from django_admin_dashboards.base import TableComponent
table = TableComponent(
title="Recent Orders",
columns=["Order ID", "Customer", "Amount", "Date", "Status"],
data=[
["#1001", "John Doe", "$125.50", "2024-01-15", "Completed"],
["#1002", "Jane Smith", "$89.99", "2024-01-14", "Processing"],
["#1003", "Bob Johnson", "$245.00", "2024-01-13", "Shipped"],
]
)
所有参数¶
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
title |
string |
必需 | 表格标题 |
columns |
list |
[] |
列标题 |
data |
list |
[] |
行数据 (列表的列表) |
table_id |
string |
None |
自定义表格ID (为空时自动生成) |
show_header |
bool |
True |
显示列标题 |
striped |
bool |
True |
使用斑马条纹 |
hover |
bool |
True |
悬停时高亮行 |
bordered |
bool |
True |
添加表格边框 |
small |
bool |
False |
使用紧凑表格样式 |
responsive |
bool |
True |
使表格响应式 |
empty_message |
string |
"No data available" |
数据为空时显示的消息 |
带自定义样式的表格¶
TableComponent(
title="User List",
columns=["Username", "Email", "Role", "Joined", "Status"],
data=[
["john_doe", "john@example.com", "Admin", "2023-01-15", "Active"],
["jane_smith", "jane@example.com", "User", "2023-02-20", "Active"],
["bob_johnson", "bob@example.com", "Editor", "2023-03-10", "Inactive"],
],
striped=True, # Zebra striping
hover=True, # Row hover effect
bordered=True, # Table borders
small=False, # Normal table size
responsive=True, # Responsive table
empty_message="No users found"
)
动态 表格 数据¶
from django.contrib.auth.models import User
from django.db.models import Count
from datetime import datetime, timedelta
from django.utils import timezone
def get_recent_users_table(self):
"""Generate table with recent user data"""
recent_users = User.objects.select_related('profile').order_by('-date_joined')[:10]
table_data = []
for user in recent_users:
# Determine user role
if user.is_superuser:
role = "Superuser"
elif user.is_staff:
role = "Staff"
else:
role = "User"
# Format date
joined_date = user.date_joined.strftime('%Y-%m-%d %H:%M')
# Determine status
if user.is_active:
status = "Active"
else:
status = "Inactive"
table_data.append([
user.username,
user.email,
role,
joined_date,
status,
])
return TableComponent(
title="Recent Users (Last 10)",
columns=["Username", "Email", "Role", "Joined", "Status"],
data=table_data,
striped=True,
hover=True
)
带操作的表格¶
def get_user_table_with_actions(self):
"""Table with action buttons"""
users = User.objects.filter(is_active=True)[:5]
table_data = []
for user in users:
# Create action buttons HTML
actions = f'''
<div class="btn-group" role="group">
<a href="/admin/auth/user/{user.id}/change/" class="btn btn-sm btn-primary">
<i class="ri-edit-line"></i> Edit
</a>
<a href="/admin/auth/user/{user.id}/delete/" class="btn btn-sm btn-danger">
<i class="ri-delete-bin-line"></i> Delete
</a>
</div>
'''
table_data.append([
user.username,
user.email,
user.get_full_name() or "-",
user.date_joined.strftime('%Y-%m-%d'),
actions,
])
return TableComponent(
title="Active Users",
columns=["Username", "Email", "Full Name", "Joined", "Actions"],
data=table_data,
# Note: HTML in data needs to be marked safe in template
)
排序和分页¶
While 表格组件 doesn\'t have 内置 sorting and pagination, you can implement it:
def get_paginated_table(self, request):
"""Table with pagination support"""
page = int(request.GET.get('page', 1))
page_size = 10
# Get total count
total_users = User.objects.count()
# Calculate pagination
start = (page - 1) * page_size
end = start + page_size
# Get paginated data
users = User.objects.all()[start:end]
# Generate table data
table_data = [
[user.username, user.email, user.date_joined.strftime('%Y-%m-%d')]
for user in users
]
table = TableComponent(
title=f"Users (Page {page})",
columns=["Username", "Email", "Joined"],
data=table_data,
)
# Add pagination info to component
table.total_pages = (total_users + page_size - 1) // page_size
table.current_page = page
table.page_size = page_size
return table
过滤器组件¶
用于数据看板数据过滤的交互式过滤器。采用现代化的下拉面板设计,节省空间并提供良好的用户体验。
UI特性: - 触发按钮:浅灰色按钮显示过滤器数量和状态 - 下拉面板:点击按钮展开包含所有过滤器的下拉面板 - Select2集成:所有选择框自动启用Select2,支持搜索功能 - 过滤器计数:按钮上显示当前激活的过滤器数量 - 响应式设计:自适应桌面和移动设备显示
基础使用¶
from django_admin_dashboards.base import FilterComponent
filters = FilterComponent(
title="Dashboard Filters",
filters=[
{
"name": "date_range",
"label": "Date Range",
"type": "date_range",
"default": "last_30_days",
},
{
"name": "status",
"label": "Status",
"type": "select",
"options": [
{"value": "all", "label": "All"},
{"value": "active", "label": "Active"},
{"value": "inactive", "label": "Inactive"},
],
"default": "all",
},
],
)
过滤器类型¶
选择过滤器¶
{
"name": "category",
"label": "Category",
"type": "select",
"options": [
{"value": "all", "label": "All Categories"},
{"value": "electronics", "label": "Electronics"},
{"value": "clothing", "label": "Clothing"},
{"value": "books", "label": "Books"},
],
"default": "all",
"multiple": False, # Single selection
"searchable": True, # Enable search (Select2自动启用)
}
日期范围过滤器¶
{
"name": "date_range",
"label": "Date Range",
"type": "date_range",
"default": "last_30_days",
"presets": [
{"value": "all_time", "label": "All time"},
{"value": "today", "label": "Today"},
{"value": "yesterday", "label": "Yesterday"},
{"value": "last_7_days", "label": "Last 7 Days"},
{"value": "last_30_days", "label": "Last 30 Days"},
{"value": "this_month", "label": "This Month"},
{"value": "last_month", "label": "Last Month"},
{"value": "this_year", "label": "This Year"},
{"value": "custom", "label": "Custom Range"},
],
"format": "YYYY-MM-DD", # Date format
}
多选过滤器¶
{
"name": "tags",
"label": "Tags",
"type": "multiselect",
"options": [
{"value": "featured", "label": "Featured"},
{"value": "new", "label": "New"},
{"value": "sale", "label": "On Sale"},
{"value": "bestseller", "label": "Bestseller"},
],
"default": ["featured", "new"], # Default selections
"searchable": True,
"placeholder": "Select tags...",
}
文本输入过滤器¶
{
"name": "search",
"label": "Search",
"type": "text",
"placeholder": "Enter search term...",
"default": "",
"debounce": 300, # Delay in ms before triggering filter
}
数字范围过滤器¶
{
"name": "price_range",
"label": "Price Range",
"type": "number_range",
"min_value": 0,
"max_value": 10000,
"step": 10,
"suffix": "$", # Display suffix
}
复选框过滤器¶
{
"name": "options",
"label": "Options",
"type": "checkbox",
"options": [
{"value": "featured", "label": "Featured Only", "default": False},
{"value": "in_stock", "label": "In Stock Only", "default": True},
{"value": "free_shipping", "label": "Free Shipping", "default": False},
],
}
完整过滤器示例¶
FilterComponent(
title="Advanced Filters",
filters=[
{
"name": "date_range",
"label": "Date Range",
"type": "date_range",
"default": "last_30_days",
"presets": [
{"value": "today", "label": "Today"},
{"value": "last_7_days", "label": "Last 7 Days"},
{"value": "last_30_days", "label": "Last 30 Days"},
{"value": "this_quarter", "label": "This Quarter"},
],
},
{
"name": "status",
"label": "Status",
"type": "select",
"options": [
{"value": "all", "label": "All Statuses"},
{"value": "pending", "label": "Pending"},
{"value": "approved", "label": "Approved"},
{"value": "rejected", "label": "Rejected"},
{"value": "completed", "label": "Completed"},
],
"default": "all",
"searchable": True,
},
{
"name": "categories",
"label": "Categories",
"type": "multi_select",
"options": [
{"value": "electronics", "label": "Electronics"},
{"value": "clothing", "label": "Clothing"},
{"value": "home", "label": "Home & Garden"},
{"value": "sports", "label": "Sports"},
{"value": "books", "label": "Books & Media"},
],
"default": [],
"searchable": True,
"placeholder": "Select categories...",
},
{
"name": "price_range",
"label": "Price Range ($)",
"type": "number_range",
"min": 0,
"max": 5000,
"step": 50,
"default_min": 0,
"default_max": 1000,
},
{
"name": "features",
"label": "Features",
"type": "checkbox",
"options": [
{"value": "featured", "label": "Featured Only", "default": False},
{"value": "new", "label": "New Arrivals", "default": False},
{"value": "discounted", "label": "Discounted", "default": True},
],
},
],
)
动态过滤器选项¶
def get_dynamic_filters(self):
"""Generate filters with dynamic options from database"""
from django.db.models import Count
# Get categories from database
categories = Product.objects.values('category').annotate(
count=Count('id')
).order_by('category')
category_options = [
{"value": "all", "label": "All Categories"}
]
category_options.extend([
{"value": cat['category'], "label": f"{cat['category']} ({cat['count']})"}
for cat in categories if cat['category']
])
# Get statuses from database
statuses = Order.objects.values('status').annotate(
count=Count('id')
).distinct()
status_options = [
{"value": "all", "label": "All Statuses"}
]
status_options.extend([
{"value": status['status'], "label": f"{status['status']} ({status['count']})"}
for status in statuses
])
return FilterComponent(
title="Dynamic Filters",
filters=[
{
"name": "category",
"label": "Category",
"type": "select",
"options": category_options,
"default": "all",
},
{
"name": "status",
"label": "Order Status",
"type": "select",
"options": status_options,
"default": "all",
},
{
"name": "date_range",
"label": "Order Date",
"type": "date_range",
"default": "last_30_days",
},
],
)
将过滤器应用于数据¶
def get_filtered_data(self, request):
"""Apply filters to database queries"""
# Get filter values from request
category = request.GET.get('category', 'all')
status = request.GET.get('status', 'all')
date_range = request.GET.get('date_range', 'last_30_days')
# Start with base queryset
queryset = Order.objects.all()
# Apply category filter
if category != 'all':
queryset = queryset.filter(product__category=category)
# Apply status filter
if status != 'all':
queryset = queryset.filter(status=status)
# Apply date range filter
if date_range == 'last_30_days':
thirty_days_ago = timezone.now() - timedelta(days=30)
queryset = queryset.filter(created_at__gte=thirty_days_ago)
elif date_range == 'last_7_days':
seven_days_ago = timezone.now() - timedelta(days=7)
queryset = queryset.filter(created_at__gte=seven_days_ago)
# ... handle other date ranges
return queryset
自定义 Components¶
为您特定的功能创建您自己的自定义组件。
创建自定义组件¶
# myapp/components.py
from django_admin_dashboards.base import Component
class ProgressBarComponent(Component):
"""Custom progress bar component"""
component_type = "progress_bar"
template_name = "admin/components/progress_bar.html"
def __init__(self, title, value, max_value=100, color="primary", **kwargs):
super().__init__(**kwargs)
self.title = title
self.value = value
self.max_value = max_value
self.color = color
self.percentage = (value / max_value * 100) if max_value > 0 else 0
def get_context_data(self, request):
context = super().get_context_data(request)
context.update({
"percentage": self.percentage,
"display_value": f"{self.value}/{self.max_value}",
})
return context
自定义组件模板¶
<div class="progress-bar-component">
<div class="progress-bar-header">
<h4>{{ component.title }}</h4>
<span class="progress-value">{{ component.display_value }}</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar" style="width: {{ percentage }}%; background-color: var(--color-{{ component.color }});"></div>
</div>
<div class="progress-bar-footer">
<span class="progress-percentage">{{ percentage|floatformat:1 }}%</span>
</div>
</div>
使用自定义组件¶
from myapp.components import ProgressBarComponent
class MyDashboard(Dashboard):
def get_layout(self):
layout = Layout(columns=12)
layout.add_row([
(ProgressBarComponent(
title="Project Completion",
value=75,
max_value=100,
color="success"
), 6),
(ProgressBarComponent(
title="Budget Usage",
value=45000,
max_value=50000,
color="warning"
), 6),
], height="auto")
return layout
带 JavaScript 的组件¶
class InteractiveMapComponent(Component):
"""Interactive map component with JavaScript"""
component_type = "interactive_map"
template_name = "admin/components/interactive_map.html"
def __init__(self, title, data_points, center=None, zoom=10, **kwargs):
super().__init__(**kwargs)
self.title = title
self.data_points = data_points # List of {lat, lng, title, value}
self.center = center or {"lat": 0, "lng": 0}
self.zoom = zoom
self.map_id = f"map_{id(self)}"
def get_context_data(self, request):
context = super().get_context_data(request)
context.update({
"map_id": self.map_id,
"data_points_json": json.dumps(self.data_points),
"center_json": json.dumps(self.center),
"zoom": self.zoom,
})
return context
class Media:
css = {
"all": ("css/leaflet.css",)
}
js = ("js/leaflet.js", "js/map-component.js")
组件样式¶
CSS 类和自定义¶
每个组件都有用于样式的特定 CSS 类:
卡片组件 Classes¶
.card- 主要卡片容器.card-primary、.card-success等 - 颜色变体.card-icon- 图标容器.card-content- 内容区域.card-title- 卡片标题.card-value- 主要值.card-change- 变化指示器.card-multi-value- 多值容器.card-value-item- 单个值项.card-value-label- 值标签
图表组件 Classes¶
.图表-container- 主要图表容器.图表-wrapper- 图表画布包装器canvas- Chart.js 画布元素
表格组件 Classes¶
.table-container- 表格包装器.dashboard-table- 表格元素.table-responsive- 响应式包装器thead、tbody、tr、th、td- 标准表格元素
过滤器组件 Classes¶
.dashboard-filters- 过滤器容器.filter-group- 单个过滤器组.filter-label- 过滤器标签.filter-control- 过滤器输入/选择
自定义 CSS 样式¶
将自定义 CSS 添加到您的数据看板:
class StyledDashboard(Dashboard):
class Media:
css = {
"all": (
"django_admin_dashboards/css/dashboard.css",
"remixicon/remixicon.css",
"myapp/css/custom-styles.css", # 您的自定义 CSS
)
}
自定义 CSS example:
/* myapp/static/myapp/css/custom-styles.css */
/* Custom card styling */
.card.custom-theme {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.card.custom-theme .card-title {
color: rgba(255,255,255,0.9);
font-weight: 600;
}
.card.custom-theme .card-value {
color: white;
font-size: 2.5rem;
font-weight: 700;
}
/* Custom table styling */
.dashboard-table.custom-table {
border-collapse: separate;
border-spacing: 0;
border-radius: 8px;
overflow: hidden;
}
.dashboard-table.custom-table thead {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.dashboard-table.custom-table th {
border: none;
padding: 1rem;
font-weight: 600;
}
.dashboard-table.custom-table tbody tr:hover {
background-color: rgba(102, 126, 234, 0.1);
}
/* Custom chart container */
.chart-container.custom-chart {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 1.5rem;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.chart-container.custom-chart h3 {
color: #333;
margin-bottom: 1rem;
font-weight: 600;
border-bottom: 2px solid #667eea;
padding-bottom: 0.5rem;
}
响应式设计¶
组件默认是响应式的。对于自定义响应式行为:
/* 响应式卡片布局 */
@media (max-width: 768px)
.card-value {
font-size: 1.8rem !important;
}
.card-multi-value {
flex-direction: column;
}
.card-value-separator {
display: none;
}
}
/* Responsive table */
@media (max-width: 768px)
.dashboard-table {
font-size: 0.9rem;
}
.dashboard-table th,
.dashboard-table td {
padding: 0.5rem;
}
}
/* Responsive charts */
@media (max-width: 768px)
.chart-wrapper {
height: 250px !important;
}
}
性能优化¶
组件级缓存¶
from django.core.cache import cache
from django.utils.functional import cached_property
class OptimizedDashboard(Dashboard):
@cached_property
def cached_data(self):
"""缓存昂贵的数据计算"""
cache_key = f"dashboard_data_{self.__class__.__name__}"
data = cache.get(cache_key)
if not data:
data = self.calculate_expensive_data()
cache.set(cache_key, data, timeout=300) # 5 minutes
return data
def get_cards(self):
"""使用缓存数据用于卡片"""
data = self.cached_data
return [
CardComponent(
title="Total Revenue",
value=f"${data['revenue']:,.2f}",
color="success",
icon_class="ri-money-dollar-circle-line"
),
# ... more cards using cached data
]
懒加载组件¶
class LazyDashboard(Dashboard):
def get_layout(self):
layout = Layout(columns=12)
# Immediate components
layout.add_row([
(self.get_immediate_cards(), 12)
], height="auto")
# Lazy-loaded components
layout.add_row([
(self.get_lazy_chart(), 12)
], height="400px")
return layout
def get_lazy_chart(self):
"""异步加载数据的图表"""
chart = ChartComponent(
title="Large Dataset Chart",
chart_type="line",
data={}, # Empty initial data
options={
"responsive": True,
"plugins": {
"legend": {"position": "top"},
}
}
)
# Add lazy loading attribute
chart.lazy_load = True
chart.data_url = "/api/large-dataset/"
return chart
数据库查询优化¶
def get_optimized_user_cards(self):
"""为用户卡片优化的数据库查询"""
from django.db.models import Count, Avg, Sum
from django.db.models.functions import TruncMonth
# Single query for all metrics
stats = User.objects.aggregate(
total=Count('id'),
active=Count('id', filter=Q(is_active=True)),
staff=Count('id', filter=Q(is_staff=True)),
superusers=Count('id', filter=Q(is_superuser=True)),
)
# Another optimized query for recent data
recent_stats = User.objects.filter(
date_joined__gte=timezone.now() - timedelta(days=30)
).aggregate(
new_users=Count('id'),
avg_daily=Count('id') / 30, # Average per day
)
return [
CardComponent(
title="Total Users",
value=stats['total'],
color="primary",
icon_class="ri-user-line"
),
CardComponent(
title="Active Users",
value=stats['active'],
color="success",
icon_class="ri-user-heart-line"
),
CardComponent(
title="New Users (30d)",
value=recent_stats['new_users'],
change=f"+{recent_stats['avg_daily']:.1f}/day",
trend="up",
color="info",
icon_class="ri-user-add-line"
),
]
组件配方¶
快速统计卡片¶
def quick_stats_card(title, value, icon, color="primary"):
"""快速统计卡片辅助函数"""
return CardComponent(
title=title,
value=value,
color=color,
icon_class=icon,
description="刚刚更新",
footer_text="点击查看详情",
link_url=f"/admin/stats/?metric={title.lower().replace(' ', '_')}"
)
趋势指示器卡片¶
def trend_card(title, current_value, previous_value, icon, color="primary"):
"""带有趋势指示器的卡片"""
if previous_value == 0:
change_percent = 100 if current_value > 0 else 0
else:
change_percent = ((current_value - previous_value) / previous_value) * 100
trend = "up" if change_percent > 0 else "down" if change_percent < 0 else "neutral"
change_text = f"{change_percent:+.1f}%"
return CardComponent(
title=title,
value=current_value,
change=change_text,
trend=trend,
color=color,
icon_class=icon
)
进度卡片¶
def progress_card(title, current, target, icon, color="primary"):
"""显示目标进度的卡片"""
percentage = (current / target * 100) if target > 0 else 0
return CardComponent(
title=title,
value=f"{current}/{target}",
change=f"{percentage:.1f}%",
trend="up" if percentage >= 100 else "neutral",
color="success" if percentage >= 100 else "warning" if percentage >= 75 else "primary",
icon_class=icon
)
对比图表¶
def comparison_chart(title, current_data, previous_data, labels):
"""比较当前周期与先前周期的图表"""
return ChartComponent(
title=title,
chart_type="bar",
data={
"labels": labels,
"datasets": [
{
"label": "当前周期",
"data": current_data,
"backgroundColor": "rgba(75, 192, 192, 0.6)",
"borderColor": "rgb(75, 192, 192)",
"borderWidth": 1,
},
{
"label": "先前周期",
"data": previous_data,
"backgroundColor": "rgba(255, 99, 132, 0.6)",
"borderColor": "rgb(255, 99, 132)",
"borderWidth": 1,
}
]
},
options={
"responsive": True,
"plugins": {
"legend": {"position": "top"},
},
"scales": {
"y": {"beginAtZero": True}
}
}
)
后续步骤¶
组件故障排除¶
常见问题¶
1. 组件未渲染¶
- 检查组件是否正确实例化
- 验证自定义组件的模板是否存在
- 检查浏览器控制台中的 JavaScript 错误
2. 数据未显示¶
- 验证数据是否为组件的正确格式
- 检查 None 或空值
- 确保数据库查询返回结果
3. 性能问题¶
- 使用
select_related和prefetch_related优化数据库查询 - 为昂贵的计算实现缓存
- 对大型数据集使用懒加载
4. 样式问题¶
- 检查 CSS 选择器是否正确
- 验证自定义 CSS 是否加载
- 检查与 Django Admin 样式的 CSS 冲突
调试组件¶
# 添加调试输出到组件
class DebugComponent(Component):
def get_context_data(self, request):
context = super().get_context_data(request)
print(f"Component: {self.component_type}", file=sys.stderr)
print(f"Request: {request}", file=sys.stderr)
print(f"Context keys: {list(context.keys())}", file=sys.stderr)
return context