URL 控制¶
Django Admin Dashboards 提供了强大的 URL 参数控制功能,允许您在不更改代码的情况下修改数据看板行为。本指南涵盖了所有可用的 URL 参数以及如何有效地使用它们。
目录¶
URL 参数基础¶
什么是 URL 参数?¶
URL 参数(也称为查询字符串)是附加在 URL 后的键值对,用于控制数据看板行为:
http://localhost:8000/admin/?_dark_mode_on=true&_hide_others_in_fullscreen=true
↑ parameter 1 ↑ parameter 2
参数格式¶
- 前缀: 数据看板参数使用下划线前缀 (
_) 以避免冲突 - 值: 可以是
true、false、1、0、yes、no或特定字符串 - 多个: 使用
&分隔参数 - 编码: 对特殊字符使用正确的 URL 编码
基础 使用 示例¶
# Basic dark mode control
/admin/?_dark_mode_on=true
# Multiple parameters
/admin/?_dark_mode_on=true&_hide_others_in_fullscreen=true
# Different value formats
/admin/?_dark_mode_on=1 # true
/admin/?_dark_mode_on=yes # true
/admin/?_dark_mode_on=false # false
/admin/?_dark_mode_on=0 # false
/admin/?_dark_mode_on=no # false
深色模式 控制¶
_dark_mode_on 参数¶
控制数据看板的深色模式行为:
# Force dark mode
/admin/?_dark_mode_on=true
# Force light mode
/admin/?_dark_mode_on=false
# Follow Django admin theme (default)
/admin/?_dark_mode_on=auto
深色模式工作原理¶
- URL 参数检测: JavaScript 从 URL 读取
_dark_mode_on - LocalStorage 更新: 在 localStorage 中设置
主题 - DOM 属性: 更新
<HTML>元素上的数据-主题 - CSS 应用: 应用深色模式样式
实现细节¶
// Simplified implementation
function handleDarkModeParam() {
const param = getUrlParameter('_dark_mode_on');
if (param === 'true') {
localStorage.setItem('theme', 'dark');
document.documentElement.setAttribute('data-theme', 'dark');
} else if (param === 'false') {
localStorage.setItem('theme', 'light');
document.documentElement.setAttribute('data-theme', 'light');
} else if (param === 'auto') {
localStorage.setItem('theme', 'auto');
// Let Django's theme.js handle auto detection
}
}
深色模式 Persistence¶
# Dark mode persists across sessions via localStorage
# To clear dark mode override, use:
/admin/?_dark_mode_on=auto
# Or manually clear localStorage:
localStorage.removeItem('theme');
深色模式与 Django Admin 集成¶
# Django Admin's theme.js handles:
# - System preference detection
# - Theme toggle button
# - localStorage management
# When _dark_mode_on=auto:
# 1. Dashboard parameter sets localStorage to 'auto'
# 2. Django's theme.js reads localStorage
# 3. Theme.js applies appropriate theme
# 4. Theme toggle button remains functional
全屏控制¶
Fullscreen 功能 要求¶
全屏控制需要数据看板类配置:
class MyDashboard(Dashboard):
# Enable hide in fullscreen feature
hide_others_in_fullscreen = True
# Enable force hide feature
force_hide_others = False
_hide_others_in_fullscreen 参数¶
仅在浏览器全屏模式下隐藏周围元素:
# Enable hide in fullscreen
/admin/?_hide_others_in_fullscreen=true
# Disable hide in fullscreen
/admin/?_hide_others_in_fullscreen=false
工作原理¶
// Simplified implementation
function handleFullscreenHide() {
const param = getUrlParameter('_hide_others_in_fullscreen');
if (param === 'true') {
// Listen for fullscreen changes
document.addEventListener('fullscreenchange', updateFullscreenClass);
function updateFullscreenClass() {
if (isFullScreen()) {
document.documentElement.classList.add('dashboard-fullscreen-mode');
} else {
document.documentElement.classList.remove('dashboard-fullscreen-mode');
}
}
}
}
_force_hide_others 参数¶
无论全屏状态如何,始终隐藏周围元素:
# Always hide surrounding elements
/admin/?_force_hide_others=true
# Show surrounding elements
/admin/?_force_hide_others=false
工作原理¶
// Simplified implementation
function handleForceHide() {
const param = getUrlParameter('_force_hide_others');
if (param === 'true') {
document.body.classList.add('dashboard-force-hide-others');
}
}
全屏 CSS 类¶
/* When in fullscreen mode with hide enabled */
.dashboard-fullscreen-mode #header,
.dashboard-fullscreen-mode #footer,
.dashboard-fullscreen-mode .sidebar {
display: none !important;
}
/* When force hide is enabled */
.dashboard-force-hide-others #header,
.dashboard-force-hide-others #footer,
.dashboard-force-hide-others .sidebar {
display: none !important;
}
全屏浏览器支持¶
// Cross-browser fullscreen detection
function isFullScreen() {
return document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement;
}
// Cross-browser event listeners
document.addEventListener('fullscreenchange', handler);
document.addEventListener('webkitfullscreenchange', handler);
document.addEventListener('mozfullscreenchange', handler);
document.addEventListener('MSFullscreenChange', handler);
数据看板特定控制¶
自定义 URL 参数¶
数据看板可以定义自己的 URL 参数:
class CustomDashboard(Dashboard):
def get_context_data(self, request):
context = super().get_context_data(request)
# Read custom URL parameters
custom_param = request.GET.get('_custom_param', 'default')
context['custom_value'] = custom_param
return context
使用:
动态参数处理¶
class DynamicDashboard(Dashboard):
def get_layout(self):
layout = Layout(columns=12)
# Get URL parameters
view_mode = self.request.GET.get('_view', 'standard')
refresh_rate = self.request.GET.get('_refresh', '30')
# Apply parameters to components
if view_mode == 'minimal':
layout = self.get_minimal_layout()
elif view_mode == 'detailed':
layout = self.get_detailed_layout()
else:
layout = self.get_standard_layout()
# Set refresh rate
layout.refresh_rate = int(refresh_rate)
return layout
使用:
# Minimal view
/admin/?_view=minimal
# Detailed view with 60-second refresh
/admin/?_view=detailed&_refresh=60
参数验证¶
class ValidatedDashboard(Dashboard):
def validate_parameters(self, request):
"""Validate URL parameters"""
params = {}
errors = []
# Validate view parameter
view = request.GET.get('_view', 'standard')
if view not in ['standard', 'minimal', 'detailed']:
errors.append(f"Invalid view: {view}")
view = 'standard'
params['view'] = view
# Validate refresh parameter
refresh = request.GET.get('_refresh', '30')
try:
refresh = int(refresh)
if refresh < 5 or refresh > 300:
errors.append(f"Refresh rate out of range: {refresh}")
refresh = 30
except ValueError:
errors.append(f"Invalid refresh rate: {refresh}")
refresh = 30
params['refresh'] = refresh
return params, errors
def get_layout(self):
params, errors = self.validate_parameters(self.request)
# Show errors if any
if errors:
return self.get_error_layout(errors)
# Use validated parameters
return self.build_layout(params['view'], params['refresh'])
参数组合¶
组合多个参数¶
# Dark mode + fullscreen hide
/admin/?_dark_mode_on=true&_hide_others_in_fullscreen=true
# Light mode + force hide
/admin/?_dark_mode_on=false&_force_hide_others=true
# Auto theme + custom view
/admin/?_dark_mode_on=auto&_view=detailed&_refresh=60
参数优先级¶
- URL 参数 (最高优先级)
- 数据看板类设置 (中等优先级)
- Django Admin 设置 (最低优先级)
# Example precedence:
# 1. URL: /admin/?_dark_mode_on=true
# 2. Dashboard: hide_others_in_fullscreen = True
# 3. Django: Default theme settings
参数冲突¶
当参数冲突时,最具体的参数获胜:
# Conflicting: dark_mode_on=true vs dark_mode_on=false
# Result: Last parameter wins
/admin/?_dark_mode_on=true&_dark_mode_on=false # Uses false
# Solution: Use only one value
/admin/?_dark_mode_on=true
参数分组¶
将相关参数分组以便组织:
# Theme parameters
/admin/?theme_mode=dark&theme_accent=blue
# Display parameters
/admin/?display_view=fullscreen&display_refresh=30
# Data parameters
/admin/?data_range=last_30_days&data_filter=active
JavaScript API¶
读取 URL 参数¶
// Utility function to get URL parameters
function getUrlParameter(name) {
name = name.replace(/[\[\]]/g, '\\$&');
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
const results = regex.exec(window.location.href);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
// Usage
const darkMode = getUrlParameter('_dark_mode_on');
const fullscreenHide = getUrlParameter('_hide_others_in_fullscreen');
更新 URL 参数¶
// Update URL parameters without page reload
function updateUrlParameter(param, value) {
const url = new URL(window.location);
if (value === null || value === '') {
url.searchParams.delete(param);
} else {
url.searchParams.set(param, value);
}
// Update URL without reload
window.history.replaceState({}, '', url);
// Dispatch custom event
window.dispatchEvent(new CustomEvent('urlparameterschanged', {
detail: { param, value }
}));
}
// Usage
updateUrlParameter('_dark_mode_on', 'true');
参数变更事件¶
// Listen for parameter changes
window.addEventListener('urlparameterschanged', function(event) {
const { param, value } = event.detail;
switch (param) {
case '_dark_mode_on':
handleDarkModeChange(value);
break;
case '_hide_others_in_fullscreen':
handleFullscreenHideChange(value);
break;
// ... handle other parameters
}
});
// Manual parameter change detection
function detectParameterChanges() {
let currentParams = new URLSearchParams(window.location.search);
setInterval(() => {
const newParams = new URLSearchParams(window.location.search);
// Check for changes
for (const [key, value] of newParams.entries()) {
if (currentParams.get(key) !== value) {
// Parameter changed
window.dispatchEvent(new CustomEvent('parameterchanged', {
detail: { key, value, oldValue: currentParams.get(key) }
}));
}
}
currentParams = newParams;
}, 1000); // Check every second
}
参数持久化¶
// Save parameters to localStorage
function saveParametersToStorage() {
const params = new URLSearchParams(window.location.search);
const paramObj = {};
for (const [key, value] of params.entries()) {
if (key.startsWith('_')) { // Only save dashboard parameters
paramObj[key] = value;
}
}
localStorage.setItem('dashboard_params', JSON.stringify(paramObj));
}
// Load parameters from localStorage
function loadParametersFromStorage() {
const saved = localStorage.getItem('dashboard_params');
if (saved) {
const params = JSON.parse(saved);
const url = new URL(window.location);
// Apply saved parameters
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
window.history.replaceState({}, '', url);
return params;
}
return {};
}
安全考虑¶
参数验证¶
# Server-side validation
class SecureDashboard(Dashboard):
def sanitize_parameters(self, request):
"""Sanitize and validate URL parameters"""
params = {}
# Dark mode parameter
dark_mode = request.GET.get('_dark_mode_on', 'auto')
if dark_mode not in ['true', 'false', 'auto', '1', '0', 'yes', 'no']:
dark_mode = 'auto'
params['dark_mode'] = dark_mode
# Fullscreen parameter (boolean)
fullscreen_hide = request.GET.get('_hide_others_in_fullscreen', '')
params['fullscreen_hide'] = fullscreen_hide.lower() in ['true', '1', 'yes']
# Numeric parameter with bounds
try:
refresh = int(request.GET.get('_refresh', '30'))
refresh = max(5, min(300, refresh)) # Clamp between 5 and 300
except ValueError:
refresh = 30
params['refresh'] = refresh
# String parameter with allowlist
view = request.GET.get('_view', 'standard')
if view not in ['standard', 'minimal', 'detailed', 'print']:
view = 'standard'
params['view'] = view
return params
XSS 防护¶
// Safe parameter handling
function getSafeParameter(name) {
const value = getUrlParameter(name);
if (value === null || value === '') {
return null;
}
// Sanitize value
return sanitizeHtml(value, {
allowedTags: [], // No HTML tags
allowedAttributes: {}, // No attributes
});
}
// Usage
const safeParam = getSafeParameter('_custom_param');
CSRF 防护¶
# For parameters that modify data, include CSRF token
class CSRFProtectedDashboard(Dashboard):
def get_context_data(self, request):
context = super().get_context_data(request);
# Include CSRF token for AJAX requests
from django.middleware.csrf import get_token
context['csrf_token'] = get_token(request);
return context
// Include CSRF token in AJAX requests
function updateDashboardWithCSRF(params) {
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
return fetch('/api/dashboard/update/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify(params),
});
}
速率限制¶
# Rate limit parameter changes
from django.core.cache import cache
from django.http import HttpResponseForbidden
class RateLimitedDashboard(Dashboard):
def dispatch(self, request, *args, **kwargs):
# Check rate limit for parameter changes
user_ip = request.META.get('REMOTE_ADDR')
cache_key = f"param_changes_{user_ip}"
changes = cache.get(cache_key, 0)
if changes > 10: # Limit to 10 changes per minute
return HttpResponseForbidden("Too many parameter changes")
# Increment counter
cache.set(cache_key, changes + 1, timeout=60)
return super().dispatch(request, *args, **kwargs)
URL 生成工具¶
Django URL 生成¶
# Generate URLs with parameters in templates
{% url 'admin:index' %}?_dark_mode_on=true&_hide_others_in_fullscreen=true
# Generate URLs in Python
from django.urls import reverse
from urllib.parse import urlencode
def generate_dashboard_url(params):
base_url = reverse('admin:index')
query_string = urlencode(params)
return f"{base_url}?{query_string}"
# Usage
url = generate_dashboard_url({
'_dark_mode_on': 'true',
'_hide_others_in_fullscreen': 'true',
'_view': 'detailed',
})
JavaScript URL 生成¶
// Generate URL with parameters
function generateDashboardUrl(params) {
const url = new URL(window.location.origin + '/admin/');
Object.entries(params).forEach(([key, value]) => {
if (value !== null && value !== undefined && value !== '') {
url.searchParams.set(key, value);
}
});
return url.toString();
}
// Usage
const url = generateDashboardUrl({
'_dark_mode_on': 'true',
'_hide_others_in_fullscreen': 'true',
'_refresh': '60'
});
// Navigate to URL
window.location.href = url;
参数预设¶
# Define parameter presets
DASHBOARD_PRESETS = {
'presentation': {
'_dark_mode_on': 'true',
'_hide_others_in_fullscreen': 'true',
'_view': 'minimal',
},
'analysis': {
'_dark_mode_on': 'false',
'_view': 'detailed',
'_refresh': '10',
},
'print': {
'_dark_mode_on': 'false',
'_view': 'print',
},
}
# Generate preset URLs
def get_preset_url(preset_name):
params = DASHBOARD_PRESETS.get(preset_name, {})
return generate_dashboard_url(params)
# Usage in template
<a href="{% url 'admin:index' %}?{{ DASHBOARD_PRESETS.presentation|urlencode }}">
Presentation Mode
</a>
可书签 URL¶
// Create bookmarkable dashboard URLs
function createBookmarkableUrl() {
const params = {
'_dark_mode_on': getUrlParameter('_dark_mode_on') || 'auto',
'_hide_others_in_fullscreen': getUrlParameter('_hide_others_in_fullscreen') || 'false',
'_force_hide_others': getUrlParameter('_force_hide_others') || 'false',
'_view': getUrlParameter('_view') || 'standard',
'_refresh': getUrlParameter('_refresh') || '30',
};
// Clean up empty parameters
Object.keys(params).forEach(key => {
if (!params[key] || params[key] === 'false' || params[key] === 'auto') {
delete params[key];
}
});
return generateDashboardUrl(params);
}
// Add bookmark button to dashboard
function addBookmarkButton() {
const button = document.createElement('button');
button.textContent = '🔖 Bookmark This View';
button.className = 'bookmark-button';
button.addEventListener('click', () => {
const url = createBookmarkableUrl();
// Copy to clipboard
navigator.clipboard.writeText(url).then(() => {
alert('Dashboard URL copied to clipboard!');
});
});
document.querySelector('.dashboard').appendChild(button);
}
QR 码生成¶
# Generate QR codes for dashboard URLs
import qrcode
from io import BytesIO
import base64
def generate_dashboard_qr_code(params):
url = generate_dashboard_url(params)
# Create QR code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
# Create image
img = qr.make_image(fill_color="black", back_color="white")
# Convert to base64
buffer = BytesIO()
img.save(buffer, format="PNG")
buffer.seek(0)
return base64.b64encode(buffer.read()).decode()
使用 in template:
最佳实践¶
1. 使用描述性参数名称¶
2. 提供默认值¶
# Always provide sensible defaults
params = {
'_dark_mode_on': request.GET.get('_dark_mode_on', 'auto'),
'_view': request.GET.get('_view', 'standard'),
'_refresh': request.GET.get('_refresh', '30'),
}
3. 验证和清理¶
# Never trust user input
def sanitize_parameter(value, param_type='string'):
if param_type == 'boolean':
return value.lower() in ['true', '1', 'yes']
elif param_type == 'integer':
try:
return int(value)
except ValueError:
return 0
else:
# Escape HTML for string parameters
import html
return html.escape(value)
4. 文档化参数¶
class WellDocumentedDashboard(Dashboard):
"""
支持 URL 参数的数据看板。
可用参数:
- _dark_mode_on: true/false/auto (default: auto)
- _hide_others_in_fullscreen: true/false (default: false)
- _force_hide_others: true/false (default: false)
- _view: standard/minimal/detailed (default: standard)
- _refresh: number in seconds (default: 30, min: 5, max: 300)
"""
# ... implementation
5. 测试参数组合¶
# Test various parameter combinations
TEST_CASES = [
{'_dark_mode_on': 'true', '_hide_others_in_fullscreen': 'true'},
{'_dark_mode_on': 'false', '_force_hide_others': 'true'},
{'_dark_mode_on': 'auto', '_view': 'detailed', '_refresh': '60'},
# Edge cases
{'_dark_mode_on': 'invalid', '_refresh': '9999'},
{'_dark_mode_on': 'true', '_dark_mode_on': 'false'}, # Conflict
]
故障排除¶
常见问题¶
1. 参数无效¶
- 检查参数名称(区分大小写,下划线前缀)
- 验证数据看板类是否支持该参数
- 检查 JavaScript 控制台是否有错误
2. 参数冲突¶
- 移除重复参数
- 每个参数仅使用一个值
- 清除浏览器缓存和 localStorage
3. 性能问题¶
- 限制活动参数数量
- 为快速变化实现防抖
- 对昂贵操作使用缓存
4. 书签问题¶
- 确保参数已正确 URL 编码
- 使用
createBookmarkableUrl()保持格式一致 - 在不同浏览器中测试书签
调试参数¶
// Debug parameter handling
function debugParameters() {
console.group('Dashboard Parameters');
// List all parameters
const params = new URLSearchParams(window.location.search);
console.log('URL Parameters:', Object.fromEntries(params));
// Check localStorage
const savedParams = localStorage.getItem('dashboard_params');
console.log('Saved Parameters:', savedParams ? JSON.parse(savedParams) : 'None');
// Check current state
console.log('Dark Mode:', document.documentElement.getAttribute('data-theme'));
console.log('Fullscreen Class:', document.documentElement.classList.contains('dashboard-fullscreen-mode'));
console.log('Force Hide Class:', document.body.classList.contains('dashboard-force-hide-others'));
console.groupEnd();
}
// Run debug on page load
document.addEventListener('DOMContentLoaded', debugParameters);