跳转至

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

参数格式

  • 前缀: 数据看板参数使用下划线前缀 (_) 以避免冲突
  • : 可以是 truefalse10yesno 或特定字符串
  • 多个: 使用 & 分隔参数
  • 编码: 对特殊字符使用正确的 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

深色模式工作原理

  1. URL 参数检测: JavaScript 从 URL 读取 _dark_mode_on
  2. LocalStorage 更新: 在 localStorage 中设置 主题
  3. DOM 属性: 更新 <HTML> 元素上的 数据-主题
  4. 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

使用:

    /admin/?_custom_param=special_value

动态参数处理

    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

参数优先级

  1. URL 参数 (最高优先级)
  2. 数据看板类设置 (中等优先级)
  3. 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:

    <img src="data:image/png;base64,{{ qr_code }}" alt="Dashboard QR Code">

最佳实践

1. 使用描述性参数名称

    # Good

    _dark_mode_on
    _hide_others_in_fullscreen
    _data_refresh_interval

    # Avoid

    dm
    hide
    refresh

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);

后续步骤