[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"similar-otto-torino--django-baton":3,"tool-otto-torino--django-baton":62},[4,18,26,36,46,54],{"id":5,"name":6,"github_repo":7,"description_zh":8,"stars":9,"difficulty_score":10,"last_commit_at":11,"category_tags":12,"status":17},4358,"openclaw","openclaw\u002Fopenclaw","OpenClaw 是一款专为个人打造的本地化 AI 助手，旨在让你在自己的设备上拥有完全可控的智能伙伴。它打破了传统 AI 助手局限于特定网页或应用的束缚，能够直接接入你日常使用的各类通讯渠道，包括微信、WhatsApp、Telegram、Discord、iMessage 等数十种平台。无论你在哪个聊天软件中发送消息，OpenClaw 都能即时响应，甚至支持在 macOS、iOS 和 Android 设备上进行语音交互，并提供实时的画布渲染功能供你操控。\n\n这款工具主要解决了用户对数据隐私、响应速度以及“始终在线”体验的需求。通过将 AI 部署在本地，用户无需依赖云端服务即可享受快速、私密的智能辅助，真正实现了“你的数据，你做主”。其独特的技术亮点在于强大的网关架构，将控制平面与核心助手分离，确保跨平台通信的流畅性与扩展性。\n\nOpenClaw 非常适合希望构建个性化工作流的技术爱好者、开发者，以及注重隐私保护且不愿被单一生态绑定的普通用户。只要具备基础的终端操作能力（支持 macOS、Linux 及 Windows WSL2），即可通过简单的命令行引导完成部署。如果你渴望拥有一个懂你",349277,3,"2026-04-06T06:32:30",[13,14,15,16],"Agent","开发框架","图像","数据工具","ready",{"id":19,"name":20,"github_repo":21,"description_zh":22,"stars":23,"difficulty_score":10,"last_commit_at":24,"category_tags":25,"status":17},3808,"stable-diffusion-webui","AUTOMATIC1111\u002Fstable-diffusion-webui","stable-diffusion-webui 是一个基于 Gradio 构建的网页版操作界面，旨在让用户能够轻松地在本地运行和使用强大的 Stable Diffusion 图像生成模型。它解决了原始模型依赖命令行、操作门槛高且功能分散的痛点，将复杂的 AI 绘图流程整合进一个直观易用的图形化平台。\n\n无论是希望快速上手的普通创作者、需要精细控制画面细节的设计师，还是想要深入探索模型潜力的开发者与研究人员，都能从中获益。其核心亮点在于极高的功能丰富度：不仅支持文生图、图生图、局部重绘（Inpainting）和外绘（Outpainting）等基础模式，还独创了注意力机制调整、提示词矩阵、负向提示词以及“高清修复”等高级功能。此外，它内置了 GFPGAN 和 CodeFormer 等人脸修复工具，支持多种神经网络放大算法，并允许用户通过插件系统无限扩展能力。即使是显存有限的设备，stable-diffusion-webui 也提供了相应的优化选项，让高质量的 AI 艺术创作变得触手可及。",162132,"2026-04-05T11:01:52",[14,15,13],{"id":27,"name":28,"github_repo":29,"description_zh":30,"stars":31,"difficulty_score":32,"last_commit_at":33,"category_tags":34,"status":17},1381,"everything-claude-code","affaan-m\u002Feverything-claude-code","everything-claude-code 是一套专为 AI 编程助手（如 Claude Code、Codex、Cursor 等）打造的高性能优化系统。它不仅仅是一组配置文件，而是一个经过长期实战打磨的完整框架，旨在解决 AI 代理在实际开发中面临的效率低下、记忆丢失、安全隐患及缺乏持续学习能力等核心痛点。\n\n通过引入技能模块化、直觉增强、记忆持久化机制以及内置的安全扫描功能，everything-claude-code 能显著提升 AI 在复杂任务中的表现，帮助开发者构建更稳定、更智能的生产级 AI 代理。其独特的“研究优先”开发理念和针对 Token 消耗的优化策略，使得模型响应更快、成本更低，同时有效防御潜在的攻击向量。\n\n这套工具特别适合软件开发者、AI 研究人员以及希望深度定制 AI 工作流的技术团队使用。无论您是在构建大型代码库，还是需要 AI 协助进行安全审计与自动化测试，everything-claude-code 都能提供强大的底层支持。作为一个曾荣获 Anthropic 黑客大奖的开源项目，它融合了多语言支持与丰富的实战钩子（hooks），让 AI 真正成长为懂上",160411,2,"2026-04-18T23:33:24",[14,13,35],"语言模型",{"id":37,"name":38,"github_repo":39,"description_zh":40,"stars":41,"difficulty_score":42,"last_commit_at":43,"category_tags":44,"status":17},8272,"opencode","anomalyco\u002Fopencode","OpenCode 是一款开源的 AI 编程助手（Coding Agent），旨在像一位智能搭档一样融入您的开发流程。它不仅仅是一个代码补全插件，而是一个能够理解项目上下文、自主规划任务并执行复杂编码操作的智能体。无论是生成全新功能、重构现有代码，还是排查难以定位的 Bug，OpenCode 都能通过自然语言交互高效完成，显著减少开发者在重复性劳动和上下文切换上的时间消耗。\n\n这款工具专为软件开发者、工程师及技术研究人员设计，特别适合希望利用大模型能力来提升编码效率、加速原型开发或处理遗留代码维护的专业人群。其核心亮点在于完全开源的架构，这意味着用户可以审查代码逻辑、自定义行为策略，甚至私有化部署以保障数据安全，彻底打破了传统闭源 AI 助手的“黑盒”限制。\n\n在技术体验上，OpenCode 提供了灵活的终端界面（Terminal UI）和正在测试中的桌面应用程序，支持 macOS、Windows 及 Linux 全平台。它兼容多种包管理工具，安装便捷，并能无缝集成到现有的开发环境中。无论您是追求极致控制权的资深极客，还是渴望提升产出的独立开发者，OpenCode 都提供了一个透明、可信",144296,1,"2026-04-16T14:50:03",[13,45],"插件",{"id":47,"name":48,"github_repo":49,"description_zh":50,"stars":51,"difficulty_score":32,"last_commit_at":52,"category_tags":53,"status":17},2271,"ComfyUI","Comfy-Org\u002FComfyUI","ComfyUI 是一款功能强大且高度模块化的视觉 AI 引擎，专为设计和执行复杂的 Stable Diffusion 图像生成流程而打造。它摒弃了传统的代码编写模式，采用直观的节点式流程图界面，让用户通过连接不同的功能模块即可构建个性化的生成管线。\n\n这一设计巧妙解决了高级 AI 绘图工作流配置复杂、灵活性不足的痛点。用户无需具备编程背景，也能自由组合模型、调整参数并实时预览效果，轻松实现从基础文生图到多步骤高清修复等各类复杂任务。ComfyUI 拥有极佳的兼容性，不仅支持 Windows、macOS 和 Linux 全平台，还广泛适配 NVIDIA、AMD、Intel 及苹果 Silicon 等多种硬件架构，并率先支持 SDXL、Flux、SD3 等前沿模型。\n\n无论是希望深入探索算法潜力的研究人员和开发者，还是追求极致创作自由度的设计师与资深 AI 绘画爱好者，ComfyUI 都能提供强大的支持。其独特的模块化架构允许社区不断扩展新功能，使其成为当前最灵活、生态最丰富的开源扩散模型工具之一，帮助用户将创意高效转化为现实。",109154,"2026-04-18T11:18:24",[14,15,13],{"id":55,"name":56,"github_repo":57,"description_zh":58,"stars":59,"difficulty_score":32,"last_commit_at":60,"category_tags":61,"status":17},6121,"gemini-cli","google-gemini\u002Fgemini-cli","gemini-cli 是一款由谷歌推出的开源 AI 命令行工具，它将强大的 Gemini 大模型能力直接集成到用户的终端环境中。对于习惯在命令行工作的开发者而言，它提供了一条从输入提示词到获取模型响应的最短路径，无需切换窗口即可享受智能辅助。\n\n这款工具主要解决了开发过程中频繁上下文切换的痛点，让用户能在熟悉的终端界面内直接完成代码理解、生成、调试以及自动化运维任务。无论是查询大型代码库、根据草图生成应用，还是执行复杂的 Git 操作，gemini-cli 都能通过自然语言指令高效处理。\n\n它特别适合广大软件工程师、DevOps 人员及技术研究人员使用。其核心亮点包括支持高达 100 万 token 的超长上下文窗口，具备出色的逻辑推理能力；内置 Google 搜索、文件操作及 Shell 命令执行等实用工具；更独特的是，它支持 MCP（模型上下文协议），允许用户灵活扩展自定义集成，连接如图像生成等外部能力。此外，个人谷歌账号即可享受免费的额度支持，且项目基于 Apache 2.0 协议完全开源，是提升终端工作效率的理想助手。",100752,"2026-04-10T01:20:03",[45,13,15,14],{"id":63,"github_repo":64,"name":65,"description_en":66,"description_zh":67,"ai_summary_zh":68,"readme_en":69,"readme_zh":70,"quickstart_zh":71,"use_case_zh":72,"hero_image_url":73,"owner_login":74,"owner_name":75,"owner_avatar_url":76,"owner_bio":77,"owner_company":78,"owner_location":78,"owner_email":79,"owner_twitter":78,"owner_website":80,"owner_url":81,"languages":82,"stars":107,"forks":108,"last_commit_at":109,"license":110,"difficulty_score":32,"env_os":111,"env_gpu":112,"env_ram":113,"env_deps":114,"category_tags":123,"github_topics":78,"view_count":32,"oss_zip_url":78,"oss_zip_packed_at":78,"status":17,"created_at":124,"updated_at":125,"faqs":126,"releases":162},9405,"otto-torino\u002Fdjango-baton","django-baton","A cool, modern and responsive django admin application based on bootstrap 5 that brings AI to the Django admin - https:\u002F\u002Fbaton.sqrt64.it\u002F","django-baton 是一款专为 Django 框架打造的现代化后台管理界面增强工具。它基于 Bootstrap 5 和 Google Material Symbols 构建，旨在将 Django 原生略显单调的管理后台升级为美观、响应式且功能强大的操作面板，完美适配各类终端设备。\n\n该工具主要解决了开发者在定制后台界面时耗时费力、原生功能有限以及缺乏智能化辅助的痛点。通过集成前沿的 AI 能力，django-baton 让后台管理变得更加智能高效：它支持自动文本翻译、内容摘要生成、写作纠错，甚至能利用 DALL·E 3 直接生成图片或借助计算机视觉自动为图像添加描述标签。此外，它还提供了丰富的主题定制方案和灵活的菜单配置，大幅降低了 UI 个性化的门槛。\n\ndjango-baton 非常适合正在使用 Django 进行开发的工程师、全栈开发者以及需要快速交付高质量后台系统的技术团队。对于希望在不重写核心逻辑的前提下，显著提升管理后台用户体验并融入 AI 工作流的项目而言，这是一个极具价值的选择。其独特的 AI 深度集成特性，让传统的 CMS 管理界面具备了现代智能应用的雏形，帮助","django-baton 是一款专为 Django 框架打造的现代化后台管理界面增强工具。它基于 Bootstrap 5 和 Google Material Symbols 构建，旨在将 Django 原生略显单调的管理后台升级为美观、响应式且功能强大的操作面板，完美适配各类终端设备。\n\n该工具主要解决了开发者在定制后台界面时耗时费力、原生功能有限以及缺乏智能化辅助的痛点。通过集成前沿的 AI 能力，django-baton 让后台管理变得更加智能高效：它支持自动文本翻译、内容摘要生成、写作纠错，甚至能利用 DALL·E 3 直接生成图片或借助计算机视觉自动为图像添加描述标签。此外，它还提供了丰富的主题定制方案和灵活的菜单配置，大幅降低了 UI 个性化的门槛。\n\ndjango-baton 非常适合正在使用 Django 进行开发的工程师、全栈开发者以及需要快速交付高质量后台系统的技术团队。对于希望在不重写核心逻辑的前提下，显著提升管理后台用户体验并融入 AI 工作流的项目而言，这是一个极具价值的选择。其独特的 AI 深度集成特性，让传统的 CMS 管理界面具备了现代智能应用的雏形，帮助开发者更专注于业务逻辑而非界面打磨。","# Django Baton\n\n[![PyPI version](https:\u002F\u002Fimg.shields.io\u002Fpypi\u002Fv\u002Fdjango-baton.svg?label=version&color=blue)](https:\u002F\u002Fpypi.org\u002Fproject\u002Fdjango-baton\u002F)\n[![Build status](https:\u002F\u002Fapp.travis-ci.com\u002Fotto-torino\u002Fdjango-baton.svg?token=fp5hqwJQgwHKLpsjsZ3L&branch=master)](https:\u002F\u002Ftravis-ci.com\u002Fgithub\u002Fotto-torino\u002Fdjango-baton)\n[![Documentation Status](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_13d664e1afd7.png)](https:\u002F\u002Fdjango-baton.readthedocs.io\u002Fen\u002Flatest\u002F?badge=latest)\n[![License](https:\u002F\u002Fimg.shields.io\u002Fpypi\u002Fl\u002Fdjango-baton)](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fblob\u002Fmaster\u002FLICENSE.txt)\n[![Downloads](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_5d2411c20278.png)](https:\u002F\u002Fpepy.tech\u002Fproject\u002Fdjango-baton)\n\n**A cool, modern, responsive, and AI-enhanced Django admin interface, built on Bootstrap 5 and Material Symbols.**\n\n[**📖 Documentation**](https:\u002F\u002Fdjango-baton.readthedocs.io\u002F) &nbsp;&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp; [**🚀 Live Demo**](https:\u002F\u002Fdjango-baton.sqrt64.it\u002F) &nbsp;&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp; [Report Bug](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues) &nbsp;&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp; [Request Feature](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fdiscussions)\n\n---\n\nDjango Baton transforms the standard Django admin into a powerful, intuitive, and visually appealing interface. Built with Bootstrap 5 and Google Material Symbols, it offers full responsiveness and integrates cutting-edge AI functionalities directly into your admin panel.\n\n✨ **Try the Live Demo!** ✨\n\nExperience the features of Django Baton firsthand. Login with user `demo` and password `demo`.\n[**https:\u002F\u002Fdjango-baton.sqrt64.it\u002F**](https:\u002F\u002Fdjango-baton.sqrt64.it\u002F)\n\n---\n\n## 📣 What's New?\n\n* **Baton 5.x:** Features a complete visual redesign and migrates from FontAwesome to Google Material Symbols for a sleek, modern icon set, see the [migration guide](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fwiki\u002FMigrate-from-v4-to-v5).\n* **Baton 4.2.1:** Integrates computer vision capabilities within the `BatonAiImageField`, includes various minor styling improvements, and incorporates several community pull requests.\n* **Baton 4.2.0:** Introduced computer vision for automatic generation of `alt` attributes for images.\n* **Baton 4.0.\\*:** Unleashed a suite of powerful AI functionalities!\n  * Automatic translations (integrates with `django-modeltranslation`).\n  * Text summarization for content creation.\n  * Text corrections for improved writing.\n  * Image generation using DALL·E 3.\n  * This version also introduced robust theme support, making customization easier than ever. Most theme changes no longer require recompiling the JavaScript application.\n\n> **🎨 Explore Themes!**\n> Discover ready-to-use themes and get inspiration from the `django-baton-themes` repository:\n> [**github.com\u002Fotto-torino\u002Fdjango-baton-themes**](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton-themes)\n\n---\n\n![Django Baton AI Features Showcase](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_530d914cddd9.gif)\n*An example of Baton's AI capabilities in action.*\n\n## 📋 Table of Contents\n\n* [Key Features](#key-features)\n* [Installation](#installation)\n* [Configuration](#configuration)\n  * [AI Configuration](#ai-configuration)\n  * [Menu Configuration](#menu-configuration)\n  * [Search Field Configuration](#search-field-configuration)\n* [Baton AI In-Depth](#baton-ai-in-depth)\n* [Page Detection](#page-detection)\n* [Signals](#signals)\n* [JS Utilities](#js-utilities)\n* [JS Translations](#js-translations)\n* [List Filters](#list-filters)\n* [Changelist Includes](#changelist-includes)\n* [Changelist Filters Includes](#changelist-filters-includes)\n* [Changelist Row Attributes](#changelist-row-attributes)\n* [Form Tabs](#form-tabs)\n* [Form Includes](#form-includes)\n* [Collapsable Stacked Inlines](#collapsable-stacked-inlines)\n* [Themes & Customization](#themes-customization)\n* [Tests](#tests)\n* [Development](#development)\n* [Contributing](#contributing)\n* [Star History](#star-history)\n\n## ⭐ \u003Ca name=\"key-features\">Key Features\u003C\u002Fa>\n\n> **Compatibility Notes:**\n>\n> * For **Django >= 5.x**: Use Baton >= 5.0\n> * For **5.x > Django >= 2.1**: Use Baton == 4.x\n> * For **older Django versions (1.x)**: Use `django-baton==1.13.2`\n\nBaton is designed with a core principle: **minimize overriding Django templates**. Styling is primarily achieved through CSS, with JavaScript used for dynamic functionalities.\n\n* **Modern Stack:** Built with Bootstrap 5 and Google Material Symbols.\n* **Fully Responsive:** Adapts seamlessly to all screen sizes.\n* **🧠 AI Powered:**\n  * Automatic translations (integrates with `django-modeltranslation`).\n  * Text summarization and correction.\n  * Image vision (description generation for `alt` text).\n  * Image generation (e.g., DALL·E 3).\n  * *(Requires a Baton subscription key for AI features).*\n* **Customizable Menu:** Flexible, dict-configurable sidebar navigation.\n* **🎨 Theme Support:** Easily customize the look and feel.\n* **Enhanced Search:** Configurable global search field with autocomplete.\n* **Advanced List Filters:** Includes text input, dropdown, and multiple-choice filter options.\n* **Improved Forms:**\n  * Out-of-the-box tabbed interface for fieldsets and inlines.\n  * Fixed submit row for better usability on long forms.\n  * Collapsable entries for stacked inlines.\n  * Lazy loading for uploaded images and image previews.\n* **Flexible Includes:** Easily inject custom templates into changelist and change form pages.\n* **Dynamic Row Attributes:** Add custom HTML attributes (classes, data-attributes, titles) to changelist rows or cells.\n* **User Experience Enhancements:**\n  * Optional modal display for changelist filters.\n  * Optional \"form mode\" for changelist filters (apply multiple filters at once).\n  * Confirmation for unsaved changes.\n  * Loading indicator for multipart form uploads.\n  * Toast notifications for admin messages.\n  * Gravatar support.\n* **Developer Friendly:** Customization via CSS variables or by recompiling the provided JS application for deeper changes.\n* **Translations:** Includes Italian (IT) and Farsi (FA) translations.\n\n**Frontend Technologies:**\nBaton leverages Bootstrap 5 for styling and responsiveness, Google Material Symbols for icons, and jQuery for DOM manipulations. All assets are compiled into a single JavaScript file for optimized delivery.\n\n## 🛠️ \u003Ca name=\"installation\">Installation\u003C\u002Fa>\n\n1. **Install via pip:**\n\n    ```bash\n    pip install django-baton\n    ```\n\n    Alternatively, to use the latest development version, clone the repository into your project:\n\n    ```bash\n    git clone [https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton.git](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton.git)\n    ```\n\n2. **Add to `INSTALLED_APPS`:**\n    In your project's `settings.py`, add `baton` **before** `django.contrib.admin` and `baton.autodiscover` at the **very end** of the list:\n\n    ```python\n    # settings.py\n    INSTALLED_APPS = [\n        # ... other apps ...\n        'baton',  # Must be before django.contrib.admin\n        'django.contrib.admin',\n        # ... other apps ...\n        'baton.autodiscover', # Must be the last app\n    ]\n    ```\n\n3. **Run Migrations:**\n\n    ```bash\n    python manage.py migrate\n    ```\n\n4. **Update URLs:**\n    Replace `django.contrib.admin` with `baton.autodiscover.admin` in your project's main `urls.py` file and include Baton's URLs:\n\n    ```python\n    # urls.py\n    # from django.contrib import admin # Remove or comment out this line\n    from baton.autodiscover import admin # Import Baton's admin\n    from django.urls import path, include\n\n    urlpatterns = [\n        path('admin\u002F', admin.site.urls),\n        path('baton\u002F', include('baton.urls')),\n        # ... your other url patterns ...\n    ]\n    ```\n\n### Why two entries in `INSTALLED_APPS`?\n\n* `baton`: Needs to be placed *before* `django.contrib.admin` because it overrides some of Django's default admin templates and resets CSS.\n* `baton.autodiscover`: This module must be the *last* app in `INSTALLED_APPS`. Baton uses a custom `AdminSite` class to allow Django-style customization of variables like `site_header` and `index_title` (instead of overriding templates). A custom `AdminSite` normally requires manual registration of all your apps. The `baton.autodiscover` module cleverly automates this by registering all apps that were already registered with Django's default `AdminSite`, ensuring all your models appear in the Baton admin. For this to work, all other apps must have already been processed.\n\n## ⚙️ \u003Ca name=\"configuration\">Configuration\u003C\u002Fa>\n\nDefine the `BATON` dictionary in your `settings.py` to customize various aspects of the admin interface.\n\n```python\n# settings.py\nfrom baton.ai import AIModels # If using AI features\n\nBATON = {\n    'SITE_HEADER': 'Baton Administration',\n    'SITE_TITLE': 'Baton Admin',\n    'INDEX_TITLE': 'Site Administration Dashboard',\n    'SUPPORT_HREF': 'https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues',\n    'COPYRIGHT': 'copyright © 2025 \u003Ca href=\"https:\u002F\u002Fwww.otto.to.it\">Otto srl\u003C\u002Fa>', # HTML is safe\n    'POWERED_BY': '\u003Ca href=\"https:\u002F\u002Fwww.otto.to.it\">Otto srl\u003C\u002Fa>', # HTML is safe\n    'CONFIRM_UNSAVED_CHANGES': True,\n    'SHOW_MULTIPART_UPLOADING': True,\n    'ENABLE_IMAGES_PREVIEW': True,\n    'CHANGELIST_FILTERS_IN_MODAL': False,\n    'CHANGELIST_FILTERS_ALWAYS_OPEN': False,\n    'CHANGELIST_FILTERS_FORM': False,\n    'CHANGEFORM_FIXED_SUBMIT_ROW': True,\n    'COLLAPSABLE_USER_AREA': True,\n    'MENU_ALWAYS_COLLAPSED': False,\n    'MENU_TITLE': 'Main Menu',\n    'MESSAGES_TOASTS': False, # True for all, or e.g. ['warning', 'error']\n    'GRAVATAR_DEFAULT_IMG': 'retro',\n    'GRAVATAR_ENABLED': True,\n    'LOGIN_SPLASH': '\u002Fstatic\u002Fcore\u002Fimg\u002Flogin-splash.png', # Path to your login splash image\n    'FORCE_THEME': None, # 'light' or 'dark', or None to allow user toggle\n    'BATON_CLIENT_ID': 'your_client_id_for_ai_features',\n    'BATON_CLIENT_SECRET': 'your_client_secret_for_ai_features',\n    'IMAGE_PREVIEW_WIDTH': 200,\n    'AI': {\n        # \"MODELS\": \"myapp.utils.get_ai_models_config\", # Path to a function\n        \"IMAGES_MODEL\": AIModels.BATON_DALL_E_3,\n        \"VISION_MODEL\": AIModels.BATON_GPT_4O_MINI,\n        \"SUMMARIZATIONS_MODEL\": AIModels.BATON_GPT_4O_MINI,\n        \"TRANSLATIONS_MODEL\": AIModels.BATON_GPT_4O,\n        'ENABLE_TRANSLATIONS': True,\n        'ENABLE_CORRECTIONS': True,\n        'CORRECTION_SELECTORS': [\n            \"textarea\",\n            \"input[type=text]:not(.vDateField):not([name=username]):not([name*=subject_location])\"\n        ],\n        \"CORRECTIONS_MODEL\": AIModels.BATON_GPT_3_5_TURBO,\n    },\n    'MENU': (\n        { 'type': 'title', 'label': 'Main Navigation', 'apps': ('auth', ), 'icon': 'apps'},\n        {\n            'type': 'app',\n            'name': 'auth',\n            'label': 'Authentication',\n            'icon': 'lock',\n            'models': (\n                { 'name': 'user', 'label': 'Users', 'icon': 'group' },\n                { 'name': 'group', 'label': 'Groups', 'icon': 'verified_user' },\n            )\n        },\n        { 'type': 'title', 'label': 'Content Management', 'apps': ('flatpages', ), 'icon': 'web_stories' },\n        { 'type': 'model', 'label': 'Static Pages', 'name': 'flatpage', 'app': 'flatpages', 'icon': 'article' },\n        { 'type': 'free', 'label': 'Custom Link', 'url': 'https:\u002F\u002Fwww.google.com', 'icon': 'link', 'perms': ('flatpages.add_flatpage', 'auth.change_user') },\n        {\n            'type': 'free',\n            'label': 'Nested Menu',\n            'icon': 'menu_open',\n            'default_open': True,\n            'children': [\n                { 'type': 'model', 'label': 'A Model', 'name': 'mymodelname', 'app': 'myapp', 'icon': 'settings' },\n                { 'type': 'free', 'label': 'Another Link', 'url': 'https:\u002F\u002Fwww.example.com', 'icon': 'public' },\n            ]\n        },\n    )\n}\n```\n\n**Detailed Configuration Options:**\n\n* `SITE_HEADER`, `COPYRIGHT`, `POWERED_BY`: Safe for HTML content.\n* `SUPPORT_HREF`: URL for a support link.\n* `CONFIRM_UNSAVED_CHANGES` (Default: `True`): Prompts if leaving a dirty form. (*Note: Relies on jQuery `serialize()`, may not detect all changes.*)\n* `SHOW_MULTIPART_UPLOADING` (Default: `True`): Shows spinner on multipart form submission.\n* `ENABLE_IMAGES_PREVIEW` (Default: `True`): Displays image previews. Customize with `.baton-image-preview` CSS.\n* `CHANGELIST_FILTERS_IN_MODAL` (Default: `False`): If `True`, filters are in a modal.\n* `CHANGELIST_FILTERS_ALWAYS_OPEN` (Default: `False`): If `True` (and modal filters `False`), filters are open by default.\n* `CHANGELIST_FILTERS_FORM` (Default: `False`): If `True`, treats filters as a form.\n* `CHANGEFORM_FIXED_SUBMIT_ROW` (Default: `True`): Fixes submit row at the bottom.\n* `COLLAPSABLE_USER_AREA`: If `True`, user area in sidebar is initially collapsed. (Check docs for default).\n* `MENU_ALWAYS_COLLAPSED` (Default: `False`): If `True`, menu is collapsed by default.\n* `MENU_TITLE` (Default: `'Menu'`): Sidebar menu title.\n* `MESSAGES_TOASTS` (Default: `False`): Use toasts for admin messages (`True` for all, or list like `['warning', 'error']`).\n* `GRAVATAR_DEFAULT_IMG` (Default: `'retro'`): Fallback Gravatar image.\n* `GRAVATAR_ENABLED` (Default: `True`): Show user Gravatar.\n* `LOGIN_SPLASH`: Path to login page background image.\n* `FORCE_THEME` (Default: `None`): `'light'` or `'dark'` to force theme.\n* `BATON_CLIENT_ID`, `BATON_CLIENT_SECRET`: Subscription keys for AI features from [baton.sqrt64.it](https:\u002F\u002Fbaton.sqrt64.it).\n* `IMAGE_PREVIEW_WIDTH` (Default: `200`): Width (px) for `BatonAiImageField` preview.\n\n### \u003Ca name=\"ai-configuration\">AI Configuration\u003C\u002Fa>\n\nDjango Baton integrates AI to assist with content creation and management.\n\n**Available Models (in `baton.ai.AIModels`):**\n\n* `BATON_GPT_3_5_TURBO`, `BATON_GPT_4_TURBO`, `BATON_GPT_4O`: For translations, summarizations, corrections.\n* `BATON_GPT_4O_MINI`: Default for non-image text tasks and image vision.\n* `BATON_DALL_E_3`: Default for image generation.\n\n**Configuration:**\nSet preferred models in `BATON['AI']`:\n\n```python\n\"AI\": {\n    \"IMAGES_MODEL\": AIModels.BATON_DALL_E_3,\n    \"VISION_MODEL\": AIModels.BATON_GPT_4O_MINI,\n    # ... etc.\n}\n```\n\nOr use a function path via `\"MODELS\": \"myapp.utils.get_ai_models_config\"`.\n\n**Translations:**\nRequires `django-modeltranslation`. Enable and set model:\n\n```python\n'BATON_CLIENT_ID': 'your_client_id',\n'BATON_CLIENT_SECRET': 'your_client_secret',\n'AI': {\n    'ENABLE_TRANSLATIONS': True,\n    'TRANSLATIONS_MODEL': AIModels.BATON_GPT_4O,\n    # ...\n},\n```\n\n> **Note:** Review AI translations. Long text translations may require increased server timeouts.\n\n**Corrections:**\n\n```python\n'AI': {\n    'ENABLE_CORRECTIONS': True,\n    'CORRECTIONS_MODEL': AIModels.BATON_GPT_4O,\n    'CORRECTION_SELECTORS': [\n        \"textarea\",\n        \"input[type=text]:not(.vDateField):not([name=username]):not([name*=subject_location])\"\n    ],\n    # ...\n},\n```\n\nAn icon appears near fields matching selectors for corrections. Ctrl + Left Click also triggers.\n![AI Corrections Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_aebba2ec9eea.png)\n\n**Summarizations, Image Vision & Generation:**\nDetailed in the [Baton AI In-Depth](#baton-ai-in-depth) section.\n\n### \u003Ca name=\"menu-configuration\">Menu Configuration\u003C\u002Fa>\n\nCustomize the sidebar via `BATON['MENU']`.\n\n**Item Types:**\n\n* `title`: Section header.\n  * `label`, `apps` (optional), `perms` (optional), `children` (optional), `default_open` (optional), `icon` (optional Material Symbol).\n* `app`: Links to a Django app.\n  * `name` (lowercase app label), `label` (optional), `icon` (optional), `models` (optional tuple to customize model list), `default_open` (optional).\n* `model`: Links to a model's changelist.\n  * `name` (lowercase model name), `app` (lowercase app label), `label` (optional), `icon` (optional).\n* `free`: Custom link.\n  * `label`, `url`, `icon` (optional), `perms` (optional), `re` (optional regex for active highlighting), `children` (optional), `default_open` (optional).\n\n> Children of items that themselves have children are ignored.\n\n### \u003Ca name=\"search-field-configuration\">Search Field Configuration\u003C\u002Fa>\n\nAdd an autocomplete search field to the sidebar .\n\n![Search Field Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_597219e491fc.png)\n\n```python\n'SEARCH_FIELD': {\n    'label': 'Search contents...', # Placeholder\n    'url': '\u002Fapi\u002Fadmin_search\u002F',   # Your search API endpoint\n}\n```\n\nYour API at `url` receives a `text` GET parameter and should return JSON:\n\n```json\n{\n    \"length\": 1,\n    \"data\": [\n        { \"label\": \"Search Result Label\", \"url\": \"\u002Fadmin\u002Fpath\u002Fto\u002Fitem\u002F\", \"icon\": \"search\" }\n    ]\n}\n```\n\nExample Django view for the search API:\n\n```python\n# views.py\nfrom django.http import JsonResponse\nfrom django.contrib.admin.views.decorators import staff_member_required\n# from myapp.models import YourModel # Your model\n\n@staff_member_required\ndef admin_search_api(request):\n    text = request.GET.get('text', None)\n    response_data = []\n    # Implement your search logic here\n    # Example:\n    # if text:\n    #     items = YourModel.objects.filter(title__icontains=text)[:10]\n    #     for item in items:\n    #         response_data.append({\n    #             'label': str(item),\n    #             'url': f'\u002Fadmin\u002Fmyapp\u002Fyourmodel\u002F{item.id}\u002Fchange\u002F', # Adjust URL\n    #             'icon': 'article', # Material Symbol name\n    #         })\n    return JsonResponse({'length': len(response_data), 'data': response_data})\n```\n\n## 🤖 \u003Ca name=\"baton-ai-in-depth\">Baton AI In-Depth\u003C\u002Fa>\n\nAI features require `BATON_CLIENT_ID` and `BATON_CLIENT_SECRET`. See [AI Configuration](#ai-configuration) for model selection.\n\nBaton uses `baton.sqrt64.it` APIs to generate responses, but the endpoints base path can be changed with the setting:\n\n```python\nBATON = {\n    # ...\n    \"BATON_AI_API_BASE_PATH\": \"http:\u002F\u002Flocalhost:1323\",\n    # ...\n}\n```\n\nIn such case you should implement your own endpoints for the AI features.\n\n### Automatic Translations\n\nIf `ENABLE_TRANSLATIONS` is `True` and `django-modeltranslation` is used, a \"Translate\" button appears on forms with translatable fields. Supports default fields and CKEditor. See [AI Hooks](#ai-hooks) for other editors.\n\n### Corrections\n\nIf `ENABLE_CORRECTIONS` is `True`, an icon near text fields (matching `CORRECTION_SELECTORS`) and CKEditor fields triggers AI correction. Differences are shown in a modal.\n\n### Text Summarization\n\nDefine `baton_summarize_fields` in your `ModelAdmin`:\n\n```python\n# admin.py\nclass MyModelAdmin(admin.ModelAdmin):\n    # ...\n    baton_summarize_fields = {\n        \"source_field_name_it\": [{ # e.g., 'body_it'\n            \"target\": \"target_field_name_it\", # e.g., 'summary_it'\n            \"words\": 140, # Approximate\n            \"useBulletedList\": True,\n            \"language\": \"it\", # Optional, defaults to Django's current language\n        },\n        # ... more targets for the same source field ...\n        ],\n    }\n```\n\nButtons appear near the source field to generate summaries for target fields. Parameters (`words`, `useBulletedList`) can be edited in the UI. Supports default fields and CKEditor. See [AI Hooks](#ai-hooks).\n\n### Image Generation\n\nUse `BatonAiImageField` in your model:\n\n```python\n# models.py\nfrom baton.fields import BatonAiImageField\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\n\nclass MyMediaModel(models.Model):\n    ai_generated_image = BatonAiImageField(\n        verbose_name=_(\"AI Generated Image\"),\n        upload_to=\"ai_images\u002F\",\n        subject_location_field='image_subject_location', # Optional: for subject focus\n        alt_field=\"image_alt_text\" # Optional: for AI-generated alt text (see Image Vision)\n    )\n    image_subject_location = models.CharField(max_length=7, default=\"50,50\", blank=True)\n    image_alt_text = models.CharField(max_length=255, blank=True)\n```\n\nA button near the field opens a modal to generate images from text prompts.\nAlternatively, for standard `ImageField`s, add generation capability with JavaScript:\n\n```html\n\u003Cscript>\n    Baton.AI.addImageGeneration('{{ widget.name }}'); \u002F\u002F widget.name of the ImageField\n\u003C\u002Fscript>\n```\n\nIntegrates `django-subject-imagefield` features for subject location. Configure preview width via `IMAGE_PREVIEW_WIDTH` in `BATON` settings.\n\n### Image Vision\n\nGenerate `alt` text for images.\n\n1. **Using `BatonAiImageField`**:\n    Set `alt_field`, `alt_chars` (optional), `alt_language` (optional) attributes on the field. Works primarily for images within inlines.\n\n    ```python\n    # models.py\n    image = BatonAiImageField(upload_to=\"news\u002F\", alt_field=\"image_alt_text\", alt_chars=100)\n    image_alt_text = models.CharField(max_length=150, blank=True)\n    ```\n\n2. **Using `ModelAdmin` configuration**:\n    Define `baton_vision_fields` in your `ModelAdmin`:\n\n    ```python\n    # admin.py\n    class MyModelAdmin(admin.ModelAdmin):\n        # ...\n        baton_vision_fields = {\n            #id_form-0-image\": [{ \u002F\u002F CSS selector for the image field (can target inlines)\n            \"#id_image_field_name\": [{ \u002F\u002F Key must be a CSS selector targeting the image input or its preview\n                \"target\": \"name_of_alt_text_field\", \u002F\u002F Name of a CharField in the same model\u002Fform\n                \"chars\": 80,                            \u002F\u002F Optional: max characters (default 100)\n                \"language\": \"en\",                       \u002F\u002F Optional: language for description\n            }],\n        }\n    ```\n\n    A button appears near the specified image field. Clicking it populates the `target` field with the AI-generated description.\n\n### Stats Widget\n\nDisplay a widget showing AI feature usage statistics on your admin dashboard. Add the following to your admin index template (typically `admin\u002Findex.html` that you override):\n\n```django\n{% load baton_tags %}\n\n{% baton_ai_stats %}\n```\n\n![Baton AI Stats Widget](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_8c9fb5d0b35d.png)\n\n### AI Hooks\n\nDjango Baton's AI features interact with form fields to get and set values. Native HTML inputs, textareas, and fields managed by `django-ckeditor` are supported by default. To add support for other WYSIWYG editors or custom input widgets, you need to define JavaScript hooks.\n\nPlace these hook definitions in your `admin\u002Fbase_site.html` template, **before** the `{% static 'baton\u002Fjs_snippets\u002Finit_baton.js' %}` script tag:\n\n```html\n\u003Cscript src=\"{% static 'baton\u002Fapp\u002Fdist\u002Fbaton.min.js' %}\">\u003C\u002Fscript> {# Ensure Baton's main JS is loaded first #}\n\u003Cscript>\n(function () {\n    \u002F\u002F Hook to get a list of all field IDs managed by your custom editor.\n    \u002F\u002F Should return an array of strings (field IDs).\n    Baton.AI.getEditorFieldsHook = function () {\n        \u002F\u002F Example for a hypothetical 'MyEditor':\n        \u002F\u002F if (window.MyEditor && typeof window.MyEditor.getAllInstanceIds === 'function') {\n        \u002F\u002F   return window.MyEditor.getAllInstanceIds();\n        \u002F\u002F }\n        return []; \u002F\u002F Implement for your specific editor\n    };\n\n    \u002F\u002F Hook to get the content of a specific editor instance by its field ID.\n    \u002F\u002F Should return the string content or null\u002Fundefined if fieldId is not an editor field.\n    Baton.AI.getEditorFieldValueHook = function (fieldId) {\n        \u002F\u002F Example for 'MyEditor':\n        \u002F\u002F if (window.MyEditor && typeof window.MyEditor.getInstance === 'function') {\n        \u002F\u002F   const editorInstance = window.MyEditor.getInstance(fieldId);\n        \u002F\u002F   return editorInstance ? editorInstance.getContent() : null;\n        \u002F\u002F }\n        return null; \u002F\u002F Implement for your specific editor\n    };\n\n    \u002F\u002F Hook to set the content of a specific editor instance.\n    \u002F\u002F Should return true if the fieldId corresponds to an editor and value was set, false otherwise.\n    Baton.AI.setEditorFieldValueHook = function (fieldId, value) {\n        \u002F\u002F Example for 'MyEditor':\n        \u002F\u002F if (window.MyEditor && typeof window.MyEditor.getInstance === 'function') {\n        \u002F\u002F   const editorInstance = window.MyEditor.getInstance(fieldId);\n        \u002F\u002F   if (editorInstance) {\n        \u002F\u002F     editorInstance.setContent(value);\n        \u002F\u002F     return true;\n        \u002F\u002F   }\n        \u002F\u002F }\n        return false; \u002F\u002F Implement for your specific editor\n    };\n\n    \u002F\u002F Hook to display a \"correct\" icon (checkmark) near an editor field.\n    \u002F\u002F `iconElement` is a DOM element (the icon) provided by Baton.\n    \u002F\u002F Should return true if successful, false otherwise.\n    Baton.AI.setEditorFieldCorrectHook = function (fieldId, iconElement) {\n        \u002F\u002F Example for 'MyEditor':\n        \u002F\u002F if (window.MyEditor && typeof window.MyEditor.getInstance === 'function') {\n        \u002F\u002F   const editorInstance = window.MyEditor.getInstance(fieldId);\n        \u002F\u002F   if (editorInstance && editorInstance.getContainer()) {\n        \u002F\u002F     \u002F\u002F Insert iconElement after the editor's container\n        \u002F\u002F     editorInstance.getContainer().parentNode.insertBefore(iconElement, editorInstance.getContainer().nextSibling);\n        \u002F\u002F     return true;\n        \u002F\u002F   }\n        \u002F\u002F }\n        return false; \u002F\u002F Implement for your specific editor\n    };\n})();\n\u003C\u002Fscript>\n\u003Cscript src=\"{% static 'baton\u002Fjs_snippets\u002Finit_baton.js' %}\">\u003C\u002Fscript>\n```\n\n## 📄 \u003Ca name=\"page-detection\">Page Detection\u003C\u002Fa>\n\nBaton identifies current admin page types (e.g., `change_form`, `changelist`) using regex on `location.pathname`. You can customize this for custom URLs. Define `Baton.detectPageHook` in `admin\u002Fbase_site.html` **before** `init_baton.js`:\n\n```html\n{{ conf|json_script:\"baton-config\" }} {# Assuming conf is your BATON settings dict passed to template #}\n\u003Cscript src=\"{% static 'baton\u002Fapp\u002Fdist\u002Fbaton.min.js' %}\">\u003C\u002Fscript>\n\u003Cscript>\n(function () {\n    Baton.detectPageHook = function (defaultDetectFn) {\n        if (\u002Fnewschange\u002F.test(location.pathname)) { \u002F\u002F Example: custom URL part\n            return 'change_form';\n        }\n        return defaultDetectFn(); \u002F\u002F Fallback to Baton's default detection\n    };\n})();\n\u003C\u002Fscript>\n\u003Cscript src=\"{% static 'baton\u002Fjs_snippets\u002Finit_baton.js' %}\">\u003C\u002Fscript>\n```\n\n**Available Page Types:** `dashboard`, `admindocs`, `login`, `logout`, `password_change`, `password_change_success`, `add_form`, `change_form`, `changelist`, `filer`, `default`.\n\n## 📡 \u003Ca name=\"signals\">Signals\u003C\u002Fa>\n\nBaton emits JavaScript events using its dispatcher. Register listeners **before** `Baton.init()`.\n\n```html\n\u003Cscript>\n(function ($) { \u002F\u002F jQuery is available as $ via Baton\n    Baton.Dispatcher.register('onReady', function () { console.log('BATON IS READY'); });\n    Baton.Dispatcher.register('onMenuReady', function () { console.log('BATON MENU IS READY'); });\n    Baton.Dispatcher.register('onNavbarReady', function () { console.log('BATON NAVBAR IS READY'); });\n    Baton.Dispatcher.register('onTabsReady', function () { console.log('BATON TABS ARE READY'); });\n    Baton.Dispatcher.register('onTabChanged', function (evtName, tabData) { console.log('BATON TAB CHANGED', tabData); });\n    Baton.Dispatcher.register('onMenuError', function () { console.error('BATON MENU FAILED TO LOAD'); });\n})(Baton.jQuery); \u002F\u002F Pass Baton's jQuery instance\n\u003C\u002Fscript>\n\u003Cscript src=\"{% static 'baton\u002Fjs_snippets\u002Finit_baton.js' %}\">\u003C\u002Fscript>\n```\n\n**Events:**\n\n* `onReady`: Baton JS fully initialized.\n* `onNavbarReady`: Navbar rendered.\n* `onMenuReady`: Menu rendered (often last, due to async fetch).\n* `onTabsReady`: Form tabs rendered.\n* `onTabChanged`: Active form tab changed.\n* `onMenuError`: Menu content failed to load.\n\n## 🧩 \u003Ca name=\"js-utilities\">JS Utilities\u003C\u002Fa>\n\nBaton exports JS modules for use in your custom admin scripts.\n\n### Dispatcher\n\nA singleton Mediator pattern implementation.\n\n```javascript\n\u002F\u002F Register callback\nBaton.Dispatcher.register('myCustomEvent', function (eventName, eventData) {\n    console.log('Event ' + eventName + ' fired with data: ', eventData);\n});\n\n\u002F\u002F Emit event\nBaton.Dispatcher.emit('myCustomEvent', { message: 'Hello Baton!' });\n```\n\n### Modal\n\nCreate Bootstrap modals programmatically.\n![Modal Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_3e4bde38d620.png)\n\n```javascript\n\u002F\u002F Example Modal Configuration Object:\n\u002F\u002F let config = {\n\u002F\u002F     title: 'My modal title',\n\u002F\u002F     subtitle: 'My subtitle', \u002F\u002F optional\n\u002F\u002F     content: '\u003Cp>my html content\u003C\u002Fp>', \u002F\u002F alternative to url\n\u002F\u002F     url: '\u002Fmy\u002Furl', \u002F\u002F fetches content via AJAX; alternative to content.\n\u002F\u002F     hideFooter: false, \u002F\u002F optional\n\u002F\u002F     showBackBtn: false, \u002F\u002F optional, show a back button\n\u002F\u002F     backBtnCb: function () {}, \u002F\u002F optional, back button click callback\n\u002F\u002F     actionBtnLabel: 'save', \u002F\u002F optional, default 'save'\n\u002F\u002F     actionBtnCb: null, \u002F\u002F optional, action button callback\n\u002F\u002F     onUrlLoaded: function () {}, \u002F\u002F optional, callback after AJAX content loads\n\u002F\u002F     size: 'lg', \u002F\u002F optional: sm, md, lg, xl\n\u002F\u002F     onClose: function () {} \u002F\u002F optional, callback when modal closes\n\u002F\u002F };\n\nlet myModal = new Baton.Modal({\n    title: 'My Modal Title',\n    content: '\u003Cp>Some HTML content for the modal body.\u003C\u002Fp>',\n    size: 'lg' \u002F\u002F Example size\n});\n\nmyModal.open();\n\u002F\u002F myModal.close();\n\u002F\u002F myModal.toggle();\n\u002F\u002F myModal.update({ title: 'New Modal Title', content: '\u003Cp>Updated content here.\u003C\u002Fp>' });\n```\n\n## 🌐 \u003Ca name=\"js-translations\">JS Translations\u003C\u002Fa>\n\nBaton includes `en` and `it` translations for its JS messages. It detects user locale from `\u003Chtml>` tag's `lang` attribute. Add\u002Foverride translations by defining `Baton.translations` **before** `Baton.init()`:\n\n```javascript\n\u002F\u002F Place in admin\u002Fbase_site.html before init_baton.js\nBaton.translations = {\n  \u002F\u002F Default English, override or add other locales\n  en: {\n    unsavedChangesAlert: 'You have some unsaved changes.',\n    uploading: 'Uploading...',\n    filter: 'Filter',\n    close: 'Close',\n    save: 'Save',\n    search: 'Search',\n    cannotCopyToClipboardMessage: 'Cannot copy to clipboard, please do it manually: Ctrl+C, Enter',\n    retrieveDataError: 'There was an error retrieving the data',\n    lightTheme: 'Light theme',\n    darkTheme: 'Dark theme'\n  },\n  it: { \u002F\u002F Example for Italian\n    unsavedChangesAlert: 'Ci sono modifiche non salvate.',\n    uploading: 'Caricamento...',\n    \u002F\u002F ... other Italian translations\n  }\n  \u002F\u002F Add other locales as needed, e.g. 'es': { ... }\n};\n```\n\nBaton defaults to `en` if a translation for the user's locale is not found.\n\n## 📊 \u003Ca name=\"list-filters\">List Filters\u003C\u002Fa>\n\n![List Filters Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_b51438f346f1.png)\n\n### Input Text Filters\n\nCreate text input filters in your `ModelAdmin`. (Adapted from [this article](https:\u002F\u002Fmedium.com\u002F@hakibenita\u002Fhow-to-add-a-text-filter-to-django-admin-5d1db93772d8)).\n\n```python\n# admin.py\nfrom baton.admin import InputFilter\nfrom django.contrib import admin # If not already imported\n\nclass MyModelIdFilter(InputFilter):\n    parameter_name = 'id' # URL query parameter\n    title = 'ID'          # Display title for the filter\n\n    def queryset(self, request, queryset):\n        if self.value() is not None:\n            # Ensure value is treated as expected type, e.g., int for ID\n            try:\n                search_term = int(self.value())\n                return queryset.filter(id=search_term)\n            except ValueError:\n                return queryset.none() # Or handle error appropriately\n        return queryset\n\nclass MyModelAdmin(admin.ModelAdmin):\n    list_display = ('id', 'name', 'other_field') # Example\n    list_filter = (MyModelIdFilter, 'other_field')\n```\n\n### Dropdown Filters\n\nProvides dropdown versions of standard Django admin list filters if a filter has at least 3 options. (Inspired by `django-admin-list-filter-dropdown`).\n\n| Django Admin Filter      | Baton Equivalent            |\n| :----------------------- | :-------------------------- |\n| `SimpleListFilter`       | `SimpleDropdownFilter`      |\n| `AllValuesFieldListFilter` | `DropdownFilter`            |\n| `ChoicesFieldListFilter` | `ChoicesDropdownFilter`     |\n| `RelatedFieldListFilter` | `RelatedDropdownFilter`     |\n| `RelatedOnlyFieldListFilter`| `RelatedOnlyDropdownFilter` |\n\nUsage:\n\n```python\n# admin.py\nfrom baton.admin import DropdownFilter, RelatedDropdownFilter, ChoicesDropdownFilter\n# from myapp.models import MyModel, MyRelatedModel # Your models\n\nclass MyModelAdmin(admin.ModelAdmin):\n    # list_display = ('name', 'char_field', 'choice_field', 'foreign_key_field') # Example\n    list_filter = (\n        ('char_field', DropdownFilter), # For CharField, TextField etc.\n        ('choice_field', ChoicesDropdownFilter), # For fields with choices\n        ('foreign_key_field', RelatedDropdownFilter), # For ForeignKey, ManyToManyField\n    )\n```\n\n### Multiple Choice Filters\n\nFilter on multiple options for a field.\n\n```python\n# admin.py\nfrom baton.admin import MultipleChoiceListFilter\n# from myapp.models import News # Assuming News model with Status choices\n\nclass NewsStatusListFilter(MultipleChoiceListFilter):\n    title = 'Status'\n    parameter_name = 'status__in' # Query parameter for __in lookup\n\n    def lookups(self, request, model_admin):\n        # Example assuming News.Status has .choices attribute\n        # return News.Status.choices\n        return (('draft', 'Draft'), ('published', 'Published'), ('archived', 'Archived')) # Example choices\n\nclass NewsAdmin(admin.ModelAdmin):\n    # list_display = ('title', 'status') # Example\n    list_filter = (NewsStatusListFilter, 'publication_date')\n```\n\n## ➕ \u003Ca name=\"changelist-includes\">Changelist Includes\u003C\u002Fa>\n>\n> Requires browser support for HTML `\u003Ctemplate>` tags.\n\nEmbed custom templates within the changelist page.\n\n```python\n# admin.py\n# from django.contrib import admin # If using @admin.register\n# from myapp.models import News # Your model\n\n# @admin.register(News)\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_cl_includes = [\n        ('myapp\u002Fadmin_includes\u002Fcl_top_banner.html', 'top'),\n        ('myapp\u002Fadmin_includes\u002Fcl_below_table.html', 'below'),\n    ]\n```\n\n![Changelist Includes Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_d588f01a0ea3.png)\n\n**Positions:**\n\n| Position | Description                                     |\n| :------- | :---------------------------------------------- |\n| `top`    | Inside changelist form, at the top.             |\n| `bottom` | Inside changelist form, at the bottom.          |\n| `above`  | Above the entire changelist form.               |\n| `below`  | Below the entire changelist form.               |\n\nChangelist view context variables are available in your included template.\n\n**Object Tools Includes:**\nInject templates into the object tools bar (top right of changelist). Templates are injected inside a `\u003Cul>`.\n\n```python\n# admin.py\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_cl_object_tools_include = ('myapp\u002Fadmin_includes\u002Fcustom_cl_action.html', 'left') # or 'right'\n```\n\n## ☰ \u003Ca name=\"changelist-filters-includes\">Changelist Filters Includes\u003C\u002Fa>\n>\n> Requires browser support for HTML `\u003Ctemplate>` tags.\n\nEmbed custom templates within the changelist filter container.\n\n```python\n# admin.py\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_cl_filters_includes = [\n        ('myapp\u002Fadmin_includes\u002Ffilters_top_custom_filter.html', 'top'),\n        ('myapp\u002Fadmin_includes\u002Ffilters_bottom_info.html', 'bottom'),\n    ]\n```\n\n![Changelist Filters Includes Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_d6de53a6b450.png)\n\n**Positions:**\n\n| Position | Description                                          |\n| :------- | :--------------------------------------------------- |\n| `top`    | Inside filter container, at the top.                 |\n| `bottom` | Inside filter container, at the bottom.              |\n\nChangelist view context variables are available.\n\n## ↔️ \u003Ca name=\"changelist-row-attributes\">Changelist Row Attributes\u003C\u002Fa>\n>\n> Requires browser support for HTML `\u003Ctemplate>` tags.\n\nAdd HTML attributes (classes, `data-*`, `title`, etc.) to elements in the changelist table (rows, cells).\n![Changelist Row Attributes Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_758631c8cebb.png)\n\n1. Define `baton_cl_rows_attributes` method in your `ModelAdmin`. It takes `request` and `cl` (changelist instance) as arguments.\n2. Return a JSON string dictionary. Keys usually match instance IDs. Values are dicts specifying attributes and selectors.\n\n```python\n# admin.py\nimport json\nfrom django.utils.safestring import mark_safe\n# from myapp.models import News # Assuming News model\n\nclass NewsAdmin(admin.ModelAdmin):\n    list_display = ('title', 'get_category_display', 'status') # Use the method name\n\n    def get_category_display(self, instance):\n        # Helper for targeting specific cells if needed by selector\n        if instance.category: # Check if category exists\n            return mark_safe(f'\u003Cspan class=\"category-span-{instance.category.id}\">{instance.category.name}\u003C\u002Fspan>')\n        return \"-\" # Fallback if no category\n    get_category_display.short_description = 'Category'\n    get_category_display.admin_order_field = 'category' # Optional: if you want to allow ordering\n\n    def baton_cl_rows_attributes(self, request, cl):\n        data = {}\n        # Example 1: Add 'table-info' class to rows of news items in category ID 2\n        for news_item in cl.queryset.filter(category__id=2):\n            data[str(news_item.id)] = { # Ensure key is string for JSON\n                'class': 'table-info',\n            }\n\n        # Example 2: More complex - target a specific cell for a specific news item\n        # This example assumes you want to style a cell for a news item with ID=1 and category_id=1\n        try:\n            news_to_style = cl.queryset.get(id=1, category__id=1) # More specific lookup\n            data[f\"customkey_cell_{news_to_style.id}\"] = { # Key can be arbitrary if selector is specific\n                'class': 'table-success font-weight-bold', # Example: bold success\n                'data-category-name': news_to_style.category.name if news_to_style.category else '',\n                'title': f'Special: {news_to_style.title}',\n                # This selector targets the span created by get_category_display\n                # It assumes the changelist renders the output of get_category_display in a cell\n                'selector': f'#result_list tr input[name=_selected_action][value=\"{news_to_style.pk}\"] ~ td .category-span-{news_to_style.category_id}',\n                'getParent': 'td', # Applies attributes to the parent \u003Ctd> of the found span\n            }\n        except cl.model.DoesNotExist: # Or your specific model DoesNotExist\n            pass # Item not found, or doesn't match criteria\n\n        return json.dumps(data)\n```\n\n**Rules for the returned dictionary values:**\n\n* **Keys:** Typically the primary key of the model instance (as a string). If using a custom `selector` that doesn't rely on the instance ID, the key can be any unique string.\n* **`selector`** (optional): CSS selector to find the target element.\n  * Default: `'#result_list tr input[name=_selected_action][value=\"' + key + '\"]'` (targets the checkbox for the row of instance `key`). This works if `actions` are enabled.\n* **`getParent`** (optional):\n  * Default: `'tr'` (attributes are applied to the row).\n  * You can specify another selector (e.g., `'td'`, `'.field-my_field'`) to find a parent of the element matched by `selector`.\n  * Set to `false` (boolean, not string) or an empty string to apply attributes directly to the element matched by `selector`.\n* **Other keys:** Treated as HTML attributes to be added to the target element.\n\n## 📑 \u003Ca name=\"form-tabs\">Form Tabs\u003C\u002Fa>\n\n![Form Tabs Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_141db3f95af7.png)\nOrganize your admin forms with tabs for fieldsets and inlines. Titles are derived automatically.\n\n**Configuration (in `ModelAdmin.fieldsets` or `ModelAdmin.inlines`):**\n\n```python\n# admin.py\n# from myapp.models import Attribute, Feature # Your models\n\n# class AttributeInline(admin.StackedInline):\n#     model = Attribute # Your model\n#     extra = 1\n\n# class FeatureInline(admin.StackedInline):\n#     model = Feature # Your model\n#     extra = 1\n\nclass ItemAdmin(admin.ModelAdmin):\n    # list_display = ('label', 'description', 'main_feature')\n    # inlines = [AttributeInline, FeatureInline] # Order of inlines matters for grouping\n\n    fieldsets = (\n        ('Main Info', { # This fieldset will be the first tab (or part of it)\n            'fields': ('label', 'description'),\n            'classes': ('baton-tabs-init', 'order-0', 'baton-tab-group-main--inline-attribute'),\n            # 'baton-tabs-init': REQUIRED on the first fieldset to enable tabs.\n            # 'order-X': (Optional) Defines the tab order for this fieldset. Default 0.\n            # 'baton-tab-inline-MODELNAME': Creates a tab for the inline 'attribute' (lowercase model name).\n            # 'baton-tab-fs-CUSTOMNAME': Creates a tab for this fieldset (Main Info -> content_tab).\n            # 'baton-tab-group-GROUPNAME--item1type-ITEMNAME--item2type-ITEMNAME': Creates a group tab.\n            #    GROUPNAME is arbitrary. ITEMNAME can be fs-FIELDSETNAME or inline-INLINEMODELNAME.\n            #    Example: 'baton-tab-group-overview--fs-main_info--inline-attribute'\n            #    This creates a group tab named \"Overview\" containing the \"Main Info\" fieldset and the \"Attribute\" inline.\n            'description': 'This is the main information for the item.'\n        }),\n        ('Content Details', {\n            'fields': ('text', ),\n            'classes': ('baton-tab-fs-content', ), # This fieldset becomes a tab named \"Content\"\n            'description': 'Detailed content for the item.'\n        }),\n        ('Technical Specs', {\n            'fields': ('main_feature', ),\n            # This fieldset is part of a group tab defined in the \"Main Info\" fieldset:\n            # e.g. 'baton-tab-group-main--inline-attribute--fs-tech--inline-feature' in 'Main Info' would group this.\n            'classes': ('baton-tab-fs-tech', ),\n            'description': 'Technical specifications and features.'\n        }),\n    )\n```\n\n**Rules for Tab Classes (applied to a fieldset's `classes` tuple):**\n\n* **`baton-tabs-init`**: **Required** on the *first* fieldset definition to activate the tabbing system.\n* **`order-X`**: (Optional, on the first fieldset) Sets the display order of the tab generated by the first fieldset itself. `X` is a number (e.g., `order-0`, `order-1`).\n* **`baton-tab-inline-MODELNAME`**: Creates a separate tab for the inline whose model is `MODELNAME` (lowercase). If you used `related_name` for the inline, use `baton-tab-inline-RELATEDNAME`.\n* **`baton-tab-fs-CUSTOMNAME`**: Creates a separate tab for the fieldset that *also* has the class `tab-fs-CUSTOMNAME`. `CUSTOMNAME` is an arbitrary name you choose.\n* **`baton-tab-group-GROUPNAME--item1type-ITEM1NAME--item2type-ITEM2NAME...`**: Creates a group tab.\n  * `GROUPNAME` is an arbitrary name for your tab.\n  * `itemXtype` is either `fs` (for fieldset) or `inline`.\n  * `ITEMXNAME` is your `CUSTOMNAME` (for fieldsets) or `MODELNAME`\u002F`RELATEDNAME` (for inlines).\n  * Example: `baton-tab-group-overview--fs-main_content--inline-attributes`\n* Fieldsets without a `baton-tab-fs-*` class that are *not* part of a group will be appended to the first tab.\n* To make a fieldset *always visible* (not part of any tab), add the class `tab-fs-none` to that fieldset.\n\n**Other Tab Features:**\n\n* If a form field has an error, the first tab containing that field is automatically opened.\n* Deep link to a tab by adding its hash to the URL (e.g., `#inline-feature`, `#fs-content`, `#group-overview--fs-main_content--inline-attributes`). The hash is derived from the tab class names.\n\n## 📎 \u003Ca name=\"form-includes\">Form Includes\u003C\u002Fa>\n>\n> Requires browser support for HTML `\u003Ctemplate>` tags.\n\nEmbed custom templates near specific fields in change forms.\n\n```python\n# admin.py\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_form_includes = [\n        ('myapp\u002Fadmin_includes\u002Fdatetime_helper.html', 'publication_date', 'top'),\n        ('myapp\u002Fadmin_includes\u002Fcontent_notes.html', 'body_content', 'above'),\n        ('myapp\u002Fadmin_includes\u002Ffield_icon.html', 'title', 'right'),\n    ]\n```\n\n![Form Includes Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_b600d5a2f366.png)\n\n**Positions:**\n\n| Position | Description                                  |\n| :------- | :------------------------------------------- |\n| `top`    | Inside the field's form row, at the top.     |\n| `bottom` | Inside the field's form row, at the bottom.  |\n| `above`  | Above the field's form row.                  |\n| `below`  | Below the field's form row.                  |\n| `right`  | Inline, to the right of the input field.     |\n\nThe `{{ original }}` object (the model instance) is available in your included template. Works with tabs.\n\n**Object Tools Includes:**\nInject templates into the object tools bar (top right of change form). Templates are injected inside a `\u003Cul>`.\n\n```python\n# admin.py\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_form_object_tools_include = ('myapp\u002Fadmin_includes\u002Fcustom_object_action.html', 'left') # or 'right'\n```\n\n![Form Object Tools Includes Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_5301e7a484cd.png)\n\n## 🤏 \u003Ca name=\"collapsable-stacked-inlines\">Collapsable Stacked Inlines\u003C\u002Fa>\n\n![Collapsable Stacked Inlines Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_ebadbd10e970.png)\nMake individual entries in `admin.StackedInline` collapsable.\n\nAdd `collapse-entry` to the inline's `classes`:\n\n```python\n# admin.py\nclass VideoInline(admin.StackedInline):\n    # model = Video # Your model\n    extra = 1\n    classes = ('collapse-entry', ) # Can be combined with Django's 'collapse'\n```\n\nTo have the first entry expanded by default:\n\n```python\n# admin.py\nclass VideoInline(admin.StackedInline):\n    # model = Video # Your model\n    extra = 1\n    classes = ('collapse-entry', 'expand-first')\n```\n\n## 🎨 \u003Ca name=\"themes-customization\">Themes & Customization\u003C\u002Fa>\n\nEasily customize Baton's appearance:\n\n1. **CSS Variables:**\n    Create a `baton\u002Fcss\u002Froot.css` file in one of your app's static directories (ensure this app is listed *before* `baton` in `INSTALLED_APPS`). Override any CSS variables defined in Baton's default [root.css](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Ftree\u002Fmaster\u002Fbaton\u002Fstatic\u002Fbaton\u002Fcss\u002Froot.css).\n    Example:\n\n    ```css\n    \u002F* myapp\u002Fstatic\u002Fbaton\u002Fcss\u002Froot.css *\u002F\n    :root {\n      --bs-primary: #FF6347;\n      --bs-primary-rgb: 255,99,71;\n      --baton-sidebar-active-bg: #FF6347;\n    }\n    ```\n\n2. **Admin Themes:**\n    Create and manage themes directly from the admin site at `\u002Fadmin\u002Fbaton\u002Fbatontheme\u002F`. Only one theme can be active. Its CSS content (which should define CSS variables) will override the `baton\u002Fcss\u002Froot.css` file.\n    > **Caution:** Theme content is marked safe and injected as-is. Be careful.\n    > ✨ Find ready-to-use themes at [django-baton-themes](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton-themes).\n\n3. **Heavy Customization (Recompiling JS App):**\n    For changes to primary\u002Fsecondary Bootstrap colors or extensive modifications, you can recompile Baton's JavaScript application.\n    ![Customization Screenshot](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_6d86d438a145.png)\n    1. Clone `django-baton`.\n    2. Navigate to `django-baton\u002Fbaton\u002Fstatic\u002Fbaton\u002Fapp\u002F`.\n    3. Run `npm install`.\n    4. Edit `src\u002Fstyles\u002F_variables.scss` (and other SCSS\u002FJS files as needed).\n    5. Run `npm run compile`.\n    6. Copy the compiled `dist\u002Fbaton.min.js` to your project: `YOUR_APP\u002Fstatic\u002Fbaton\u002Fapp\u002Fdist\u002F`.\n    7. Ensure `YOUR_APP` is listed *before* `baton` in `INSTALLED_APPS`.\n\n    For live development with automatic recompilation:\n    1. `cd django-baton\u002Fbaton\u002Fstatic\u002Fbaton\u002Fapp\u002F`\n    2. Run `npm run dev:baton` (starts Webpack dev server, usually on `http:\u002F\u002Flocalhost:8080`).\n    3. In your project's `admin\u002Fbase_site.html` (you might need to override it), change the script source to point to the dev server:\n\n        ```html\n        {# \u003Cscript src=\"{% static 'baton\u002Fapp\u002Fdist\u002Fbaton.min.js' %}\">\u003C\u002Fscript> #}\n        \u003Cscript src=\"http:\u002F\u002Flocalhost:8080\u002Fstatic\u002Fbaton\u002Fapp\u002Fdist\u002Fbaton.min.js\">\u003C\u002Fscript>\n        ```\n\n    Now, changes in the JS app will auto-update, just refresh your Django admin page.\n\n## 🧪 \u003Ca name=\"tests\">Tests\u003C\u002Fa>\n\nBaton includes unit and end-to-end (e2e) tests using Selenium. To run e2e tests, ensure the test application (found in the `testapp` directory of the Baton repository) is running on `localhost:8000`.\n\n## 💻 \u003Ca name=\"development\">Development\u003C\u002Fa>\n\nTo contribute or develop locally:\n\n1. **Set up the test app:**\n\n    ```bash\n    cd testapp\n    python3 -m venv .virtualenv\n    source .virtualenv\u002Fbin\u002Factivate # On Windows: .virtualenv\\Scripts\\activate\n    cd app\n    pip install -r requirements.txt\n    python manage.py migrate\n    python manage.py createsuperuser # If needed\n    python manage.py runserver\n    ```\n\n    (Default login after `createsuperuser`: `admin` \u002F `admin`, or as you defined).\n\n2. **Enable live JS recompilation for development:**\n    * In `testapp\u002Fapp\u002Ftemplates\u002Fadmin\u002Fbase_site.html`, switch the script source to Webpack dev server:\n\n        ```html\n        {# \u003Cscript src=\"{% static 'baton\u002Fapp\u002Fdist\u002Fbaton.min.js' %}\">\u003C\u002Fscript> #}\n        \u003Cscript src=\"http:\u002F\u002Flocalhost:8080\u002Fstatic\u002Fbaton\u002Fapp\u002Fdist\u002Fbaton.min.js\">\u003C\u002Fscript>\n        ```\n\n    * In a new terminal, navigate to Baton's frontend app directory and start the dev server:\n\n        ```bash\n        cd \u002Fpath\u002Fto\u002Fyour\u002Fdjango-baton\u002Fbaton\u002Fstatic\u002Fbaton\u002Fapp\u002F\n        npm install\n        npm run dev # For continuous development, watches for changes\n        ```\n\n    Changes to Baton's frontend app will now auto-recompile. Refresh your browser to see them.\n\n### Commands\n\nInstall `invoke` and `sphinx_rtd_theme` in your Python environment for documentation generation:\n\n```bash\npip install invoke sphinx_rtd_theme\n```\n\nTo generate documentation locally (from the root directory of the `django-baton` repository):\n\n```bash\ninvoke docs\n```\n\n## 🤝 \u003Ca name=\"contributing\">Contributing\u003C\u002Fa>\n\nPlease read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. We welcome contributions!\n\n## 🌟 \u003Ca name=\"star-history\">Star History\u003C\u002Fa>\n\n[![Star History Chart](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_ae2007b1a7a1.png)](https:\u002F\u002Fstar-history.com\u002F#otto-torino\u002Fdjango-baton&Date)\n","# Django Baton\n\n[![PyPI version](https:\u002F\u002Fimg.shields.io\u002Fpypi\u002Fv\u002Fdjango-baton.svg?label=version&color=blue)](https:\u002F\u002Fpypi.org\u002Fproject\u002Fdjango-baton\u002F)\n[![Build status](https:\u002F\u002Fapp.travis-ci.com\u002Fotto-torino\u002Fdjango-baton.svg?token=fp5hqwJQgwHKLpsjsZ3L&branch=master)](https:\u002F\u002Ftravis-ci.com\u002Fgithub\u002Fotto-torino\u002Fdjango-baton)\n[![Documentation Status](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_13d664e1afd7.png)](https:\u002F\u002Fdjango-baton.readthedocs.io\u002Fen\u002Flatest\u002F?badge=latest)\n[![License](https:\u002F\u002Fimg.shields.io\u002Fpypi\u002Fl\u002Fdjango-baton)](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fblob\u002Fmaster\u002FLICENSE.txt)\n[![Downloads](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_5d2411c20278.png)](https:\u002F\u002Fpepy.tech\u002Fproject\u002Fdjango-baton)\n\n**一款酷炫、现代、响应式且融合了AI技术的Django管理后台界面，基于Bootstrap 5和Material Symbols构建。**\n\n[**📖 文档**](https:\u002F\u002Fdjango-baton.readthedocs.io\u002F) &nbsp;&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp; [**🚀 在线演示**](https:\u002F\u002Fdjango-baton.sqrt64.it\u002F) &nbsp;&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp; [报告Bug](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues) &nbsp;&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp; [请求功能](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fdiscussions)\n\n---\n\nDjango Baton将标准的Django管理后台转变为一个功能强大、直观且视觉上极具吸引力的界面。它基于Bootstrap 5和Google Material Symbols构建，具备完全的响应式设计，并将前沿的AI功能直接集成到你的管理面板中。\n\n✨ **立即体验在线演示！** ✨\n\n使用用户`demo`和密码`demo`登录，亲身体验Django Baton的各项功能。\n[**https:\u002F\u002Fdjango-baton.sqrt64.it\u002F**](https:\u002F\u002Fdjango-baton.sqrt64.it\u002F)\n\n---\n\n## 📣 最新动态？\n\n* **Baton 5.x:** 进行了全面的视觉重新设计，并从FontAwesome迁移到Google Material Symbols，以提供更简洁现代的图标集，详情请参阅[迁移指南](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fwiki\u002FMigrate-from-v4-to-v5)。\n* **Baton 4.2.1:** 在`BatonAiImageField`中集成了计算机视觉能力，进行了多项细微的样式改进，并合并了社区提交的多个Pull Request。\n* **Baton 4.2.0:** 引入了用于自动生成图片`alt`属性的计算机视觉功能。\n* **Baton 4.0.\\***：推出了一系列强大的AI功能！\n  * 自动翻译（与`django-modeltranslation`集成）。\n  * 文本摘要功能，助力内容创作。\n  * 文本校正功能，提升写作质量。\n  * 使用DALL·E 3生成图像。\n  * 此版本还引入了强大的主题支持，使自定义变得更加容易。大多数主题更改不再需要重新编译JavaScript应用。\n\n> **🎨 探索主题！**\n> 发现即用型主题，并从`django-baton-themes`仓库中获取灵感：\n> [**github.com\u002Fotto-torino\u002Fdjango-baton-themes**](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton-themes)\n\n---\n\n![Django Baton AI功能展示](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_530d914cddd9.gif)\n*Baton的AI功能示例。*\n\n## 📋 目录\n\n* [核心特性](#key-features)\n* [安装](#installation)\n* [配置](#configuration)\n  * [AI配置](#ai-configuration)\n  * [菜单配置](#menu-configuration)\n  * [搜索字段配置](#search-field-configuration)\n* [Baton AI深度解析](#baton-ai-in-depth)\n* [页面检测](#page-detection)\n* [信号](#signals)\n* [JS工具](#js-utilities)\n* [JS翻译](#js-translations)\n* [列表过滤器](#list-filters)\n* [变更列表包含项](#changelist-includes)\n* [变更列表筛选器包含项](#changelist-filters-includes)\n* [变更列表行属性](#changelist-row-attributes)\n* [表单标签页](#form-tabs)\n* [表单包含项](#form-includes)\n* [可折叠堆叠内联](#collapsable-stacked-inlines)\n* [主题与自定义](#themes-customization)\n* [测试](#tests)\n* [开发](#development)\n* [贡献](#contributing)\n* [星标历史](#star-history)\n\n## ⭐ \u003Ca name=\"key-features\">核心特性\u003C\u002Fa>\n\n> **兼容性说明：**\n>\n> * 对于 **Django >= 5.x**: 使用Baton >= 5.0\n> * 对于 **5.x > Django >= 2.1**: 使用Baton == 4.x\n> * 对于 **较旧的Django版本 (1.x)**: 使用 `django-baton==1.13.2`\n\nBaton的设计核心原则是：**尽量减少对Django模板的覆盖**。样式主要通过CSS实现，而JavaScript则用于实现动态功能。\n\n* **现代技术栈：** 基于Bootstrap 5和Google Material Symbols构建。\n* **完全响应式：** 无缝适配各种屏幕尺寸。\n* **🧠 AI赋能：**\n  * 自动翻译（与`django-modeltranslation`集成）。\n  * 文本摘要与校正。\n  * 图像视觉（为`alt`文本生成描述）。\n  * 图像生成（例如DALL·E 3）。\n  * *(AI功能需要Baton订阅密钥)*。\n* **可定制菜单：** 灵活的字典配置侧边栏导航。\n* **🎨 主题支持：** 轻松自定义外观和风格。\n* **增强型搜索：** 可配置的全局搜索字段，支持自动补全。\n* **高级列表过滤器：** 包括文本输入、下拉菜单和多选过滤选项。\n* **改进的表单：**\n  * 开箱即用的字段集和内联表单标签页界面。\n  * 固定提交行，提升长表单的可用性。\n  * 可折叠的堆叠内联条目。\n  * 延迟加载上传图片及图片预览。\n* **灵活的包含机制：** 可轻松将自定义模板注入到变更列表和变更表单页面中。\n* **动态行属性：** 可为变更列表的行或单元格添加自定义HTML属性（类名、data属性、标题等）。\n* **用户体验优化：**\n  * 可选的模态窗口显示变更列表过滤器。\n  * 可选的“表单模式”用于变更列表过滤器（一次应用多个过滤器）。\n  * 未保存更改的确认提示。\n  * 多部分表单上传时的加载指示器。\n  * 用于管理消息的Toast通知。\n  * Gravatar支持。\n* **开发者友好：** 可通过CSS变量进行自定义，或通过重新编译提供的JavaScript应用来实现更深层次的修改。\n* **多语言支持：** 包含意大利语（IT）和波斯语（FA）翻译。\n\n**前端技术：**\nBaton利用Bootstrap 5实现样式和响应式布局，使用Google Material Symbols作为图标库，并借助jQuery进行DOM操作。所有资源被编译成一个单独的JavaScript文件，以实现优化的交付。\n\n## 🛠️ \u003Ca name=\"installation\">安装\u003C\u002Fa>\n\n1. **通过 pip 安装：**\n\n    ```bash\n    pip install django-baton\n    ```\n\n    或者，要使用最新的开发版本，请将仓库克隆到您的项目中：\n\n    ```bash\n    git clone [https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton.git](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton.git)\n    ```\n\n2. **添加到 `INSTALLED_APPS`：**\n    在您项目的 `settings.py` 中，将 `baton` 放在 `django.contrib.admin` 之前，并将 `baton.autodiscover` 放在列表的**最末尾**：\n\n    ```python\n    # settings.py\n    INSTALLED_APPS = [\n        # ... 其他应用 ...\n        'baton',  # 必须放在 django.contrib.admin 之前\n        'django.contrib.admin',\n        # ... 其他应用 ...\n        'baton.autodiscover', # 必须是最后一个应用\n    ]\n    ```\n\n3. **运行迁移：**\n\n    ```bash\n    python manage.py migrate\n    ```\n\n4. **更新 URL 配置：**\n    在您项目的主 `urls.py` 文件中，将 `django.contrib.admin` 替换为 `baton.autodiscover.admin`，并包含 Baton 的 URL 配置：\n\n    ```python\n    # urls.py\n    # from django.contrib import admin # 移除或注释掉这一行\n    from baton.autodiscover import admin # 导入 Baton 的管理后台\n    from django.urls import path, include\n\n    urlpatterns = [\n        path('admin\u002F', admin.site.urls),\n        path('baton\u002F', include('baton.urls')),\n        # ... 您其他的 URL 模式 ...\n    ]\n    ```\n\n### 为什么要在 `INSTALLED_APPS` 中有两个条目？\n\n* `baton`：需要放在 `django.contrib.admin` *之前*，因为它会覆盖 Django 默认的一些管理模板，并重置 CSS 样式。\n* `baton.autodiscover`：这个模块必须是 `INSTALLED_APPS` 中的*最后一个*应用。Baton 使用自定义的 `AdminSite` 类，允许以 Django 风格的方式自定义诸如 `site_header` 和 `index_title` 等变量（而不是直接覆盖模板）。通常情况下，自定义的 `AdminSite` 需要手动注册所有应用。而 `baton.autodiscover` 模块巧妙地自动化了这一过程，它会自动注册所有已经通过 Django 默认 `AdminSite` 注册的应用，从而确保您的所有模型都能出现在 Baton 管理后台中。为了使这一功能正常工作，其他所有应用都必须在此之前已经被处理过。\n\n## ⚙️ \u003Ca name=\"configuration\">配置\u003C\u002Fa>\n\n在您的 `settings.py` 中定义 `BATON` 字典，以自定义管理后台的各个方面。\n\n```python\n\n# settings.py\nfrom baton.ai import AIModels # 如果使用 AI 功能\n\nBATON = {\n    'SITE_HEADER': 'Baton 管理后台',\n    'SITE_TITLE': 'Baton 管理',\n    'INDEX_TITLE': '站点管理仪表板',\n    'SUPPORT_HREF': 'https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues',\n    'COPYRIGHT': '版权 © 2025 \u003Ca href=\"https:\u002F\u002Fwww.otto.to.it\">Otto srl\u003C\u002Fa>', # HTML 是安全的\n    'POWERED_BY': '\u003Ca href=\"https:\u002F\u002Fwww.otto.to.it\">Otto srl\u003C\u002Fa>', # HTML 是安全的\n    'CONFIRM_UNSAVED_CHANGES': True,\n    'SHOW_MULTIPART_UPLOADING': True,\n    'ENABLE_IMAGES_PREVIEW': True,\n    'CHANGELIST_FILTERS_IN_MODAL': False,\n    'CHANGELIST_FILTERS_ALWAYS_OPEN': False,\n    'CHANGELIST_FILTERS_FORM': False,\n    'CHANGEFORM_FIXED_SUBMIT_ROW': True,\n    'COLLAPSABLE_USER_AREA': True,\n    'MENU_ALWAYS_COLLAPSED': False,\n    'MENU_TITLE': '主菜单',\n    'MESSAGES_TOASTS': False, # 对所有消息启用，或例如 ['warning', 'error']\n    'GRAVATAR_DEFAULT_IMG': 'retro',\n    'GRAVATAR_ENABLED': True,\n    'LOGIN_SPLASH': '\u002Fstatic\u002Fcore\u002Fimg\u002Flogin-splash.png', # 登录界面背景图路径\n    'FORCE_THEME': None, # 'light' 或 'dark'，或设置为 None 允许用户切换\n    'BATON_CLIENT_ID': 'your_client_id_for_ai_features',\n    'BATON_CLIENT_SECRET': 'your_client_secret_for_ai_features',\n    'IMAGE_PREVIEW_WIDTH': 200,\n    'AI': {\n        # \"MODELS\": \"myapp.utils.get_ai_models_config\", # 函数路径\n        \"IMAGES_MODEL\": AIModels.BATON_DALL_E_3,\n        \"VISION_MODEL\": AIModels.BATON_GPT_4O_MINI,\n        \"SUMMARIZATIONS_MODEL\": AIModels.BATON_GPT_4O_MINI,\n        \"TRANSLATIONS_MODEL\": AIModels.BATON_GPT_4O,\n        'ENABLE_TRANSLATIONS': True,\n        'ENABLE_CORRECTIONS': True,\n        'CORRECTION_SELECTORS': [\n            \"textarea\",\n            \"input[type=text]:not(.vDateField):not([name=username]):not([name*=subject_location])\"\n        ],\n        \"CORRECTIONS_MODEL\": AIModels.BATON_GPT_3_5_TURBO,\n    },\n    'MENU': (\n        { 'type': 'title', 'label': '主导航', 'apps': ('auth', ), 'icon': 'apps'},\n        {\n            'type': 'app',\n            'name': 'auth',\n            'label': '认证',\n            'icon': 'lock',\n            'models': (\n                { 'name': 'user', 'label': '用户', 'icon': 'group' },\n                { 'name': 'group', 'label': '组', 'icon': 'verified_user' },\n            )\n        },\n        { 'type': 'title', 'label': '内容管理', 'apps': ('flatpages', ), 'icon': 'web_stories' },\n        { 'type': 'model', 'label': '静态页面', 'name': 'flatpage', 'app': 'flatpages', 'icon': 'article' },\n        { 'type': 'free', 'label': '自定义链接', 'url': 'https:\u002F\u002Fwww.google.com', 'icon': 'link', 'perms': ('flatpages.add_flatpage', 'auth.change_user') },\n        {\n            'type': 'free',\n            'label': '嵌套菜单',\n            'icon': 'menu_open',\n            'default_open': True,\n            'children': [\n                { 'type': 'model', 'label': 'A 模型', 'name': 'mymodelname', 'app': 'myapp', 'icon': 'settings' },\n                { 'type': 'free', 'label': '另一个链接', 'url': 'https:\u002F\u002Fwww.example.com', 'icon': 'public' },\n            ]\n        },\n    )\n}\n```\n\n**详细配置选项：**\n\n* `SITE_HEADER`、`COPYRIGHT`、`POWERED_BY`：支持 HTML 内容。\n* `SUPPORT_HREF`：支持链接的 URL。\n* `CONFIRM_UNSAVED_CHANGES`（默认：`True`）：离开未保存表单时提示。(*注意：依赖 jQuery `serialize()`，可能无法检测所有更改。*)\n* `SHOW_MULTIPART_UPLOADING`（默认：`True`）：多部分表单提交时显示加载动画。\n* `ENABLE_IMAGES_PREVIEW`（默认：`True`）：显示图片预览。可通过 `.baton-image-preview` CSS 自定义样式。\n* `CHANGELIST_FILTERS_IN_MODAL`（默认：`False`）：若为 `True`，筛选器将显示在模态框中。\n* `CHANGELIST_FILTERS_ALWAYS_OPEN`（默认：`False`）：若为 `True`（且模态筛选器为 `False`），筛选器将始终处于打开状态。\n* `CHANGELIST_FILTERS_FORM`（默认：`False`）：若为 `True`，筛选器将被视为一个表单。\n* `CHANGEFORM_FIXED_SUBMIT_ROW`（默认：`True`）：将提交行固定在底部。\n* `COLLAPSABLE_USER_AREA`：若为 `True`，侧边栏中的用户区域初始状态为折叠。（请参阅文档以了解默认行为）。\n* `MENU_ALWAYS_COLLAPSED`（默认：`False`）：若为 `True`，菜单默认会折叠。\n* `MENU_TITLE`（默认：`'Menu'`）：侧边栏菜单标题。\n* `MESSAGES_TOASTS`（默认：`False`）：用于管理员消息的通知提示（对所有消息启用，或如 `['warning', 'error']` 列表）。\n* `GRAVATAR_DEFAULT_IMG`（默认：`'retro'`）：Gravatar 的默认头像。\n* `GRAVATAR_ENABLED`（默认：`True`）：显示用户的 Gravatar 头像。\n* `LOGIN_SPLASH`：登录页面背景图的路径。\n* `FORCE_THEME`（默认：`None`）：可强制设置主题为 `'light'` 或 `'dark'`。\n* `BATON_CLIENT_ID`、`BATON_CLIENT_SECRET`：来自 [baton.sqrt64.it](https:\u002F\u002Fbaton.sqrt64.it) 的 AI 功能订阅密钥。\n* `IMAGE_PREVIEW_WIDTH`（默认：`200`）：`BatonAiImageField` 预览的宽度（像素）。\n\n### \u003Ca name=\"ai-configuration\">AI 配置\u003C\u002Fa>\n\nDjango Baton 集成了 AI 功能，以辅助内容创建和管理。\n\n**可用模型（在 `baton.ai.AIModels` 中）：**\n\n* `BATON_GPT_3_5_TURBO`、`BATON_GPT_4_TURBO`、`BATON_GPT_4O`：用于翻译、摘要生成和文本校正。\n* `BATON_GPT_4O_MINI`：默认用于非图像相关的文本任务及图像视觉处理。\n* `BATON_DALL_E_3`：默认用于图像生成。\n\n**配置：**\n在 `BATON['AI']` 中设置首选模型：\n\n```python\n\"AI\": {\n    \"IMAGES_MODEL\": AIModels.BATON_DALL_E_3,\n    \"VISION_MODEL\": AIModels.BATON_GPT_4O_MINI,\n    # ... 等等\n}\n```\n\n或者通过 `\"MODELS\": \"myapp.utils.get_ai_models_config\"` 使用函数路径。\n\n**翻译功能：**\n需要 `django-modeltranslation` 扩展。启用并设置模型：\n\n```python\n'BATON_CLIENT_ID': 'your_client_id',\n'BATON_CLIENT_SECRET': 'your_client_secret',\n'AI': {\n    'ENABLE_TRANSLATIONS': True,\n    'TRANSLATIONS_MODEL': AIModels.BATON_GPT_4O,\n    # ...\n},\n```\n\n> **注意：** 请仔细检查 AI 生成的翻译结果。长文本翻译可能需要增加服务器超时时间。\n\n**文本校正：**\n\n```python\n'AI': {\n    'ENABLE_CORRECTIONS': True,\n    'CORRECTIONS_MODEL': AIModels.BATON_GPT_4O,\n    'CORRECTION_SELECTORS': [\n        \"textarea\",\n        \"input[type=text]:not(.vDateField):not([name=username]):not([name*=subject_location])\"\n    ],\n    # ...\n},\n```\n\n与选择器匹配的字段旁边会出现一个图标，点击即可触发校正。同时按住 Ctrl 键并左键单击也会触发校正。\n![AI 校正截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_aebba2ec9eea.png)\n\n**摘要生成、图像视觉与图像生成：**\n详见 [Baton AI 深度解析](#baton-ai-in-depth) 章节。\n\n### \u003Ca name=\"menu-configuration\">菜单配置\u003C\u002Fa>\n\n通过 `BATON['MENU']` 自定义侧边栏。\n\n**项类型：**\n\n* `title`: 部分标题。\n  * `label`、`apps`（可选）、`perms`（可选）、`children`（可选）、`default_open`（可选）、`icon`（可选 Material Symbol）。\n* `app`: 链接到一个 Django 应用。\n  * `name`（小写应用标签）、`label`（可选）、`icon`（可选）、`models`（可选元组，用于自定义模型列表）、`default_open`（可选）。\n* `model`: 链接到某个模型的更改列表。\n  * `name`（小写模型名）、`app`（小写应用标签）、`label`（可选）、`icon`（可选）。\n* `free`: 自定义链接。\n  * `label`、`url`、`icon`（可选）、`perms`（可选）、`re`（可选用于高亮显示的正则表达式）、`children`（可选）、`default_open`（可选）。\n\n> 具有子项的项的子项将被忽略。\n\n### \u003Ca name=\"search-field-configuration\">搜索字段配置\u003C\u002Fa>\n\n在侧边栏中添加一个自动完成的搜索字段。\n\n![搜索字段截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_597219e491fc.png)\n\n```python\n'SEARCH_FIELD': {\n    'label': '搜索内容...', # 占位符\n    'url': '\u002Fapi\u002Fadmin_search\u002F',   # 您的搜索 API 端点\n}\n```\n\n您在 `url` 处的 API 接收一个 `text` GET 参数，并应返回 JSON：\n\n```json\n{\n    \"length\": 1,\n    \"data\": [\n        { \"label\": \"搜索结果标签\", \"url\": \"\u002Fadmin\u002Fpath\u002Fto\u002Fitem\u002F\", \"icon\": \"search\" }\n    ]\n}\n```\n\n搜索 API 的 Django 视图示例：\n\n```python\n# views.py\nfrom django.http import JsonResponse\nfrom django.contrib.admin.views.decorators import staff_member_required\n# from myapp.models import YourModel # 您的模型\n\n@staff_member_required\ndef admin_search_api(request):\n    text = request.GET.get('text', None)\n    response_data = []\n    # 在此处实现您的搜索逻辑\n    # 示例：\n    # if text:\n    #     items = YourModel.objects.filter(title__icontains=text)[:10]\n    #     for item in items:\n    #         response_data.append({\n    #             'label': str(item),\n    #             'url': f'\u002Fadmin\u002Fmyapp\u002Fyourmodel\u002F{item.id}\u002Fchange\u002F', # 调整 URL\n    #             'icon': 'article', # Material Symbol 名称\n    #         })\n    return JsonResponse({'length': len(response_data), 'data': response_data})\n```\n\n## 🤖 \u003Ca name=\"baton-ai-in-depth\">Baton AI 深度解析\u003C\u002Fa>\n\nAI 功能需要 `BATON_CLIENT_ID` 和 `BATON_CLIENT_SECRET`。有关模型选择，请参阅 [AI 配置](#ai-configuration)。\n\nBaton 使用 `baton.sqrt64.it` 的 API 来生成响应，但可以通过以下设置更改端点的基本路径：\n\n```python\nBATON = {\n    # ...\n    \"BATON_AI_API_BASE_PATH\": \"http:\u002F\u002Flocalhost:1323\",\n    # ...\n}\n```\n\n在这种情况下，您应该为 AI 功能实现自己的端点。\n\n### 自动翻译\n\n如果 `ENABLE_TRANSLATIONS` 设置为 `True` 并且使用了 `django-modeltranslation`，则具有可翻译字段的表单上会显示“翻译”按钮。支持默认字段和 CKEditor。有关其他编辑器，请参阅 [AI 钩子](#ai-hooks)。\n\n### 错误修正\n\n如果 `ENABLE_CORRECTIONS` 设置为 `True`，则文本字段附近（匹配 `CORRECTION_SELECTORS`）以及 CKEditor 字段中的图标会触发 AI 修正。差异会在模态窗口中显示。\n\n### 文本摘要\n\n在您的 `ModelAdmin` 中定义 `baton_summarize_fields`：\n\n```python\n# admin.py\nclass MyModelAdmin(admin.ModelAdmin):\n    # ...\n    baton_summarize_fields = {\n        \"source_field_name_it\": [{ # 例如，'body_it'\n            \"target\": \"target_field_name_it\", # 例如，'summary_it'\n            \"words\": 140, # 大约\n            \"useBulletedList\": True,\n            \"language\": \"it\", # 可选，默认为 Django 当前语言\n        },\n        # ... 同一源字段的更多目标 ...\n        ],\n    }\n```\n\n源字段附近会出现按钮，用于为目标字段生成摘要。参数（`words`、`useBulletedList`）可以在 UI 中编辑。支持默认字段和 CKEditor。有关详细信息，请参阅 [AI 钩子](#ai-hooks)。\n\n### 图片生成\n\n在您的模型中使用 `BatonAiImageField`：\n\n```python\n# models.py\nfrom baton.fields import BatonAiImageField\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\n\nclass MyMediaModel(models.Model):\n    ai_generated_image = BatonAiImageField(\n        verbose_name=_(\"AI 生成的图片\"),\n        upload_to=\"ai_images\u002F\",\n        subject_location_field='image_subject_location', # 可选：用于主体聚焦\n        alt_field=\"image_alt_text\" # 可选：用于 AI 生成的替代文本（参见图像视觉）\n    )\n    image_subject_location = models.CharField(max_length=7, default=\"50,50\", blank=True)\n    image_alt_text = models.CharField(max_length=255, blank=True)\n```\n\n字段附近的按钮会打开一个模态窗口，允许您根据文本提示生成图片。\n或者，对于标准的 `ImageField`，可以使用 JavaScript 添加生成功能：\n\n```html\n\u003Cscript>\n    Baton.AI.addImageGeneration('{{ widget.name }}'); \u002F\u002F ImageField 的 widget.name\n\u003C\u002Fscript>\n```\n\n集成 `django-subject-imagefield` 的主体位置功能。通过 `BATON` 设置中的 `IMAGE_PREVIEW_WIDTH` 配置预览宽度。\n\n### 图像视觉\n\n为图片生成替代文本。\n\n1. **使用 `BatonAiImageField`**：\n    在字段上设置 `alt_field`、`alt_chars`（可选）、`alt_language`（可选）属性。主要适用于内联中的图片。\n\n    ```python\n    # models.py\n    image = BatonAiImageField(upload_to=\"news\u002F\", alt_field=\"image_alt_text\", alt_chars=100)\n    image_alt_text = models.CharField(max_length=150, blank=True)\n    ```\n\n2. **使用 `ModelAdmin` 配置**：\n    在您的 `ModelAdmin` 中定义 `baton_vision_fields`：\n\n    ```python\n    # admin.py\n    class MyModelAdmin(admin.ModelAdmin):\n        # ...\n        baton_vision_fields = {\n            \"#id_form-0-image\": [{ \u002F\u002F CSS 选择器，用于指定图片字段（可 targeting 内联）\n                \"#id_image_field_name\": [{ \u002F\u002F 键必须是 CSS 选择器，指向图片输入或其预览\n                    \"target\": \"name_of_alt_text_field\", \u002F\u002F 同一模型\u002F表单中的 CharField 名称\n                    \"chars\": 80,                            \u002F\u002F 可选：最大字符数（默认 100）\n                    \"language\": \"en\",                       \u002F\u002F 可选：描述的语言\n                }],\n            }\n        }\n    ```\n\n    指定的图片字段附近会出现一个按钮。点击后，`target` 字段将被 AI 生成的描述填充。\n\n### 统计小部件\n\n在您的管理仪表板上显示一个显示 AI 功能使用情况统计的小部件。将以下内容添加到您的管理索引模板（通常是您覆盖的 `admin\u002Findex.html`）：\n\n```django\n{% load baton_tags %}\n\n{% baton_ai_stats %}\n```\n\n![Baton AI 统计小部件](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_8c9fb5d0b35d.png)\n\n### AI 钩子\n\nDjango Baton 的 AI 功能会与表单字段交互，以获取和设置值。默认情况下，原生 HTML 输入框、文本区域以及由 `django-ckeditor` 管理的字段都受支持。若要为其他所见即所得（WYSIWYG）编辑器或自定义输入小部件添加支持，你需要定义 JavaScript 钩子。\n\n将这些钩子定义放置在你的 `admin\u002Fbase_site.html` 模板中，在 `{% static 'baton\u002Fjs_snippets\u002Finit_baton.js' %}` 脚本标签 **之前**：\n\n```html\n\u003Cscript src=\"{% static 'baton\u002Fapp\u002Fdist\u002Fbaton.min.js' %}\">\u003C\u002Fscript> {# 确保先加载 Baton 的主 JS #}\n\u003Cscript>\n(function () {\n    \u002F\u002F 获取由自定义编辑器管理的所有字段 ID 列表的钩子。\n    \u002F\u002F 应返回字符串数组（字段 ID）。\n    Baton.AI.getEditorFieldsHook = function () {\n        \u002F\u002F 例如针对假设的 'MyEditor'：\n        \u002F\u002F if (window.MyEditor && typeof window.MyEditor.getAllInstanceIds === 'function') {\n        \u002F\u002F   return window.MyEditor.getAllInstanceIds();\n        \u002F\u002F }\n        return []; \u002F\u002F 针对你特定的编辑器实现\n    };\n\n    \u002F\u002F 根据字段 ID 获取特定编辑器实例内容的钩子。\n    \u002F\u002F 应返回字符串内容，如果 fieldId 不是编辑器字段，则返回 null\u002Fundefined。\n    Baton.AI.getEditorFieldValueHook = function (fieldId) {\n        \u002F\u002F 例如针对 'MyEditor'：\n        \u002F\u002F if (window.MyEditor && typeof window.MyEditor.getInstance === 'function') {\n        \u002F\u002F   const editorInstance = window.MyEditor.getInstance(fieldId);\n        \u002F\u002F   return editorInstance ? editorInstance.getContent() : null;\n        \u002F\u002F }\n        return null; \u002F\u002F 针对你特定的编辑器实现\n    };\n\n    \u002F\u002F 设置特定编辑器实例内容的钩子。\n    \u002F\u002F 如果 fieldId 对应于编辑器且成功设置了值，则返回 true；否则返回 false。\n    Baton.AI.setEditorFieldValueHook = function (fieldId, value) {\n        \u002F\u002F 例如针对 'MyEditor'：\n        \u002F\u002F if (window.MyEditor && typeof window.MyEditor.getInstance === 'function') {\n        \u002F\u002F   const editorInstance = window.MyEditor.getInstance(fieldId);\n        \u002F\u002F   if (editorInstance) {\n        \u002F\u002F     editorInstance.setContent(value);\n        \u002F\u002F     return true;\n        \u002F\u002F   }\n        \u002F\u002F }\n        return false; \u002F\u002F 针对你特定的编辑器实现\n    };\n\n    \u002F\u002F 在编辑器字段附近显示“正确”图标（对勾）的钩子。\n    \u002F\u002F `iconElement` 是 Baton 提供的 DOM 元素（图标）。\n    \u002F\u002F 如果成功则返回 true，否则返回 false。\n    Baton.AI.setEditorFieldCorrectHook = function (fieldId, iconElement) {\n        \u002F\u002F 例如针对 'MyEditor'：\n        \u002F\u002F if (window.MyEditor && typeof window.MyEditor.getInstance === 'function') {\n        \u002F\u002F   const editorInstance = window.MyEditor.getInstance(fieldId);\n        \u002F\u002F   if (editorInstance && editorInstance.getContainer()) {\n        \u002F\u002F     \u002F\u002F 将 iconElement 插入到编辑器容器之后\n        \u002F\u002F     editorInstance.getContainer().parentNode.insertBefore(iconElement, editorInstance.getContainer().nextSibling);\n        \u002F\u002F     return true;\n        \u002F\u002F   }\n        \u002F\u002F }\n        return false; \u002F\u002F 针对你特定的编辑器实现\n    };\n})();\n\u003C\u002Fscript>\n\u003Cscript src=\"{% static 'baton\u002Fjs_snippets\u002Finit_baton.js' %}\">\u003C\u002Fscript>\n```\n\n## 📄 \u003Ca name=\"page-detection\">页面检测\u003C\u002Fa>\n\nBaton 使用 `location.pathname` 上的正则表达式来识别当前的管理页面类型（例如 `change_form`、`changelist`）。你可以针对自定义 URL 进行自定义。在 `init_baton.js` **之前**，在 `admin\u002Fbase_site.html` 中定义 `Baton.detectPageHook`：\n\n```html\n{{ conf|json_script:\"baton-config\" }} {# 假设 conf 是传递给模板的 BATON 设置字典 #}\n\u003Cscript src=\"{% static 'baton\u002Fapp\u002Fdist\u002Fbaton.min.js' %}\">\u003C\u002Fscript>\n\u003Cscript>\n(function () {\n    Baton.detectPageHook = function (defaultDetectFn) {\n        if (\u002Fnewschange\u002F.test(location.pathname)) { \u002F\u002F 示例：自定义 URL 部分\n            return 'change_form';\n        }\n        return defaultDetectFn(); \u002F\u002F 回退到 Baton 的默认检测\n    };\n})();\n\u003C\u002Fscript>\n\u003Cscript src=\"{% static 'baton\u002Fjs_snippets\u002Finit_baton.js' %}\">\u003C\u002Fscript>\n```\n\n**可用页面类型：** `dashboard`、`admindocs`、`login`、`logout`、`password_change`、`password_change_success`、`add_form`、`change_form`、`changelist`、`filer`、`default`。\n\n## 📡 \u003Ca name=\"signals\">信号\u003C\u002Fa>\n\nBaton 使用其调度器发出 JavaScript 事件。请在 `Baton.init()` **之前**注册监听器。\n\n```html\n\u003Cscript>\n(function ($) { \u002F\u002F 通过 Baton 可以使用 $ 表示 jQuery\n    Baton.Dispatcher.register('onReady', function () { console.log('BATON 已就绪'); });\n    Baton.Dispatcher.register('onMenuReady', function () { console.log('BATON 菜单已就绪'); });\n    Baton.Dispatcher.register('onNavbarReady', function () { console.log('BATON 导航栏已就绪'); });\n    Baton.Dispatcher.register('onTabsReady', function () { console.log('BATON 选项卡已就绪'); });\n    Baton.Dispatcher.register('onTabChanged', function (evtName, tabData) { console.log('BATON 选项卡已更改', tabData); });\n    Baton.Dispatcher.register('onMenuError', function () { console.error('BATON 菜单加载失败'); });\n})(Baton.jQuery); \u002F\u002F 传入 Baton 的 jQuery 实例\n\u003C\u002Fscript>\n\u003Cscript src=\"{% static 'baton\u002Fjs_snippets\u002Finit_baton.js' %}\">\u003C\u002Fscript>\n```\n\n**事件：**\n\n* `onReady`：Baton JS 完全初始化。\n* `onNavbarReady`：导航栏渲染完成。\n* `onMenuReady`：菜单渲染完成（通常最后完成，因为是异步加载）。\n* `onTabsReady`：表单选项卡渲染完成。\n* `onTabChanged`：活动表单选项卡发生变化。\n* `onMenuError`：菜单内容加载失败。\n\n## 🧩 \u003Ca name=\"js-utilities\">JS 工具\u003C\u002Fa>\n\nBaton 导出 JS 模块，供你在自定义管理脚本中使用。\n\n### 调度器\n\n一个单例中介者模式的实现。\n\n```javascript\n\u002F\u002F 注册回调\nBaton.Dispatcher.register('myCustomEvent', function (eventName, eventData) {\n    console.log('事件 ' + eventName + ' 触发，数据为：', eventData);\n});\n\n\u002F\u002F 发布事件\nBaton.Dispatcher.emit('myCustomEvent', { message: '你好，Baton!' });\n```\n\n### 模态框\n\n以编程方式创建 Bootstrap 模态框。\n![模态框截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_3e4bde38d620.png)\n\n```javascript\n\u002F\u002F 模态框配置对象示例：\n\u002F\u002F let config = {\n\u002F\u002F     title: '我的模态框标题',\n\u002F\u002F     subtitle: '我的副标题', \u002F\u002F 可选\n\u002F\u002F     content: '\u003Cp>我的 HTML 内容\u003C\u002Fp>', \u002F\u002F 替代 URL\n\u002F\u002F     url: '\u002Fmy\u002Furl', \u002F\u002F 通过 AJAX 获取内容；替代 content。\n\u002F\u002F     hideFooter: false, \u002F\u002F 可选\n\u002F\u002F     showBackBtn: false, \u002F\u002F 可选，显示返回按钮\n\u002F\u002F     backBtnCb: function () {}, \u002F\u002F 可选，返回按钮点击回调\n\u002F\u002F     actionBtnLabel: '保存', \u002F\u002F 可选，默认为 '保存'\n\u002F\u002F     actionBtnCb: null, \u002F\u002F 可选，操作按钮回调\n\u002F\u002F     onUrlLoaded: function () {}, \u002F\u002F 可选，AJAX 内容加载后的回调\n\u002F\u002F     size: 'lg', \u002F\u002F 可选：sm、md、lg、xl\n\u002F\u002F     onClose: function () {} \u002F\u002F 可选，模态框关闭时的回调\n\u002F\u002F };\n\nlet myModal = new Baton.Modal({\n    title: '我的模态框标题',\n    content: '\u003Cp>模态框主体的一些 HTML 内容。\u003C\u002Fp>',\n    size: 'lg' \u002F\u002F 示例尺寸\n});\n\nmyModal.open();\n\u002F\u002F myModal.close();\n\u002F\u002F myModal.toggle();\n\u002F\u002F myModal.update({ title: '新的模态框标题', content: '\u003Cp>这里是更新的内容。\u003C\u002Fp>' });\n```\n\n## 🌐 \u003Ca name=\"js-translations\">JS 翻译\u003C\u002Fa>\n\nBaton 包含其 JS 消息的 `en` 和 `it` 翻译版本。它会根据 `\u003Chtml>` 标签的 `lang` 属性检测用户的语言环境。通过在调用 `Baton.init()` **之前**定义 `Baton.translations` 来添加或覆盖翻译：\n\n```javascript\n\u002F\u002F 放在 admin\u002Fbase_site.html 中，在 init_baton.js 之前\nBaton.translations = {\n  \u002F\u002F 默认英文，可覆盖或添加其他语言\n  en: {\n    unsavedChangesAlert: '您有一些未保存的更改。',\n    uploading: '上传中...',\n    filter: '筛选',\n    close: '关闭',\n    save: '保存',\n    search: '搜索',\n    cannotCopyToClipboardMessage: '无法复制到剪贴板，请手动操作：Ctrl+C，Enter',\n    retrieveDataError: '获取数据时出错',\n    lightTheme: '浅色主题',\n    darkTheme: '深色主题'\n  },\n  it: { \u002F\u002F 意大利语示例\n    unsavedChangesAlert: 'Ci sono modifiche non salvate.',\n    uploading: 'Caricamento...',\n    \u002F\u002F ... 其他意大利语翻译\n  }\n  \u002F\u002F 根据需要添加其他语言，例如 'es': { ... }\n};\n```\n\n如果找不到用户语言环境的翻译，Baton 将默认使用 `en`。\n\n## 📊 \u003Ca name=\"list-filters\">列表筛选器\u003C\u002Fa>\n\n![列表筛选器截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_b51438f346f1.png)\n\n### 文本输入筛选器\n\n在您的 `ModelAdmin` 中创建文本输入筛选器。（改编自[这篇文章](https:\u002F\u002Fmedium.com\u002F@hakibenita\u002Fhow-to-add-a-text-filter-to-django-admin-5d1db93772d8)）。\n\n```python\n# admin.py\nfrom baton.admin import InputFilter\nfrom django.contrib import admin # 如果尚未导入\n\nclass MyModelIdFilter(InputFilter):\n    parameter_name = 'id' # URL 查询参数\n    title = 'ID'          # 筛选器的显示标题\n\n    def queryset(self, request, queryset):\n        if self.value() is not None:\n            # 确保值被当作预期类型处理，例如 ID 应为整数\n            try:\n                search_term = int(self.value())\n                return queryset.filter(id=search_term)\n            except ValueError:\n                return queryset.none() # 或者适当地处理错误\n        return queryset\n\nclass MyModelAdmin(admin.ModelAdmin):\n    list_display = ('id', 'name', 'other_field') # 示例\n    list_filter = (MyModelIdFilter, 'other_field')\n```\n\n### 下拉菜单筛选器\n\n如果筛选器至少有 3 个选项，则提供标准 Django 管理界面列表筛选器的下拉菜单版本。（灵感来自 `django-admin-list-filter-dropdown`）。\n\n| Django 管理筛选器      | Baton 对应版本            |\n| :----------------------- | :-------------------------- |\n| `SimpleListFilter`       | `SimpleDropdownFilter`      |\n| `AllValuesFieldListFilter` | `DropdownFilter`            |\n| `ChoicesFieldListFilter` | `ChoicesDropdownFilter`     |\n| `RelatedFieldListFilter` | `RelatedDropdownFilter`     |\n| `RelatedOnlyFieldListFilter`| `RelatedOnlyDropdownFilter` |\n\n使用方法：\n\n```python\n# admin.py\nfrom baton.admin import DropdownFilter、RelatedDropdownFilter、ChoicesDropdownFilter\n# from myapp.models import MyModel、MyRelatedModel # 您的模型\n\nclass MyModelAdmin(admin.ModelAdmin):\n    # list_display = ('name', 'char_field', 'choice_field', 'foreign_key_field') # 示例\n    list_filter = (\n        ('char_field', DropdownFilter), # 对于 CharField、TextField 等\n        ('choice_field', ChoicesDropdownFilter), # 对于有选项的字段\n        ('foreign_key_field', RelatedDropdownFilter), # 对于 ForeignKey、ManyToManyField\n    )\n```\n\n### 多选筛选器\n\n对某个字段的多个选项进行筛选。\n\n```python\n# admin.py\nfrom baton.admin import MultipleChoiceListFilter\n# from myapp.models import News # 假设 News 模型有 Status 选项\n\nclass NewsStatusListFilter(MultipleChoiceListFilter):\n    title = '状态'\n    parameter_name = 'status__in' # 用于 __in 查找的查询参数\n\n    def lookups(self, request, model_admin):\n        # 示例，假设 News.Status 有 .choices 属性\n        # return News.Status.choices\n        return (('draft', '草稿'), ('published', '已发布'), ('archived', '已归档')) # 示例选项\n\nclass NewsAdmin(admin.ModelAdmin):\n    # list_display = ('title', 'status') # 示例\n    list_filter = (NewsStatusListFilter, 'publication_date')\n```\n\n## ➕ \u003Ca name=\"changelist-includes\">更改列表包含\u003C\u002Fa>\n>\n> 需要浏览器支持 HTML `\u003Ctemplate>` 标签。\n\n将自定义模板嵌入到更改列表页面中。\n\n```python\n# admin.py\n# from django.contrib import admin # 如果使用 @admin.register\n# from myapp.models import News # 您的模型\n\n# @admin.register(News)\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_cl_includes = [\n        ('myapp\u002Fadmin_includes\u002Fcl_top_banner.html', 'top'),\n        ('myapp\u002Fadmin_includes\u002Fcl_below_table.html', 'below'),\n    ]\n```\n\n![更改列表包含截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_d588f01a0ea3.png)\n\n**位置：**\n\n| 位置 | 描述                                     |\n| :------- | :---------------------------------------------- |\n| `top`    | 在更改列表表单内部，顶部。             |\n| `bottom` | 在更改列表表单内部，底部。          |\n| `above`  | 在整个更改列表表单上方。               |\n| `below`  | 在整个更改列表表单下方。               |\n\n更改列表视图的上下文变量可在您包含的模板中使用。\n\n**对象工具栏包含：**\n将模板注入到对象工具栏（更改列表右上角）。模板会被注入到一个 `\u003Cul>` 中。\n\n```python\n# admin.py\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_cl_object_tools_include = ('myapp\u002Fadmin_includes\u002Fcustom_cl_action.html', 'left') # 或 'right'\n```\n\n## ☰ \u003Ca name=\"changelist-filters-includes\">更改列表筛选器包含\u003C\u002Fa>\n>\n> 需要浏览器支持 HTML `\u003Ctemplate>` 标签。\n\n将自定义模板嵌入到更改列表筛选器容器中。\n\n```python\n# admin.py\nfrom baton.admin import FilterInclude\n# from myapp.models import MyModel # 您的模型\n\nclass MyModelAdmin(admin.ModelAdmin):\n    # ...\n    baton_cl_filters_includes = [\n        ('myapp\u002Fadmin_includes\u002Ffilter_top_banner.html', 'top'),\n        ('myapp\u002Fadmin_includes\u002Ffilter_bottom_banner.html', 'bottom'),\n    ]\n```\n\n![更改列表筛选器包含截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_d6de53a6b450.png)\n\n**位置：**\n\n| 位置 | 描述                                     |\n| :------- | :---------------------------------------------- |\n| `top`    | 在筛选器容器内部，顶部。             |\n| `bottom` | 在筛选器容器内部，底部。          |\n| `above`  | 在整个筛选器容器上方。               |\n| `below`  | 在整个筛选器容器下方。               |\n\n更改列表视图的上下文变量可在您包含的模板中使用。\n\n# admin.py\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_cl_filters_includes = [\n        ('myapp\u002Fadmin_includes\u002Ffilters_top_custom_filter.html', 'top'),\n        ('myapp\u002Fadmin_includes\u002Ffilters_bottom_info.html', 'bottom'),\n    ]\n```\n\n![更改列表筛选器包含截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_d6de53a6b450.png)\n\n**位置：**\n\n| 位置 | 描述                                          |\n| :------- | :--------------------------------------------------- |\n| `top`    | 在筛选器容器内，位于顶部。                 |\n| `bottom` | 在筛选器容器内，位于底部。              |\n\n更改列表视图的上下文变量可用。\n\n## ↔️ \u003Ca name=\"changelist-row-attributes\">更改列表行属性\u003C\u002Fa>\n>\n> 需要浏览器支持 HTML `\u003Ctemplate>` 标签。\n\n为更改列表表格中的元素（行、单元格）添加 HTML 属性（类、`data-*`、`title` 等）。\n![更改列表行属性截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_758631c8cebb.png)\n\n1. 在您的 `ModelAdmin` 中定义 `baton_cl_rows_attributes` 方法。它接受 `request` 和 `cl`（更改列表实例）作为参数。\n2. 返回一个 JSON 字符串字典。键通常与实例 ID 匹配。值是指定属性和选择器的字典。\n\n```python\n# admin.py\nimport json\nfrom django.utils.safestring import mark_safe\n# from myapp.models import News # 假设 News 模型\n\nclass NewsAdmin(admin.ModelAdmin):\n    list_display = ('title', 'get_category_display', 'status') # 使用方法名\n\n    def get_category_display(self, instance):\n        # 如果需要通过选择器定位特定单元格，可以使用此辅助函数\n        if instance.category: # 检查类别是否存在\n            return mark_safe(f'\u003Cspan class=\"category-span-{instance.category.id}\">{instance.category.name}\u003C\u002Fspan>')\n        return \"-\" # 如果没有类别，则显示“-”\n    get_category_display.short_description = '类别'\n    get_category_display.admin_order_field = 'category' # 可选：如果希望允许排序\n\n    def baton_cl_rows_attributes(self, request, cl):\n        data = {}\n        # 示例 1：为类别 ID 为 2 的新闻条目行添加 'table-info' 类\n        for news_item in cl.queryset.filter(category__id=2):\n            data[str(news_item.id)] = { # 确保键为字符串以便转换为 JSON\n                'class': 'table-info',\n            }\n\n        # 示例 2：更复杂的情况——为目标新闻条目的特定单元格设置样式\n        # 此示例假设您希望为 ID 为 1 且 category_id 为 1 的新闻条目单元格设置样式\n        try:\n            news_to_style = cl.queryset.get(id=1, category__id=1) # 更具体的查询\n            data[f\"customkey_cell_{news_to_style.id}\"] = { # 如果选择器足够具体，键可以是任意字符串\n                'class': 'table-success font-weight-bold', # 示例：成功背景色加粗字体\n                'data-category-name': news_to_style.category.name if news_to_style.category else '',\n                'title': f'特别：{news_to_style.title}',\n                # 此选择器用于定位由 get_category_display 创建的 span 元素\n                # 它假定更改列表会在单元格中渲染 get_category_display 的输出\n                'selector': f'#result_list tr input[name=_selected_action][value=\"{news_to_style.pk}\"] ~ td .category-span-{news_to_style.category_id}',\n                'getParent': 'td', # 将属性应用于找到的 span 元素的父级 \u003Ctd>\n            }\n        except cl.model.DoesNotExist: # 或者您特定模型的 DoesNotExist 异常\n            pass # 未找到该条目，或不符合条件\n\n        return json.dumps(data)\n```\n\n**返回字典值的规则：**\n\n* **键：** 通常是模型实例的主键（以字符串形式）。如果使用不依赖实例 ID 的自定义 `selector`，则键可以是任何唯一字符串。\n* **`selector`**（可选）：用于查找目标元素的 CSS 选择器。\n  * 默认值：`'#result_list tr input[name=_selected_action][value=\"' + key + '\"]'`（指向实例 `key` 所在行的复选框）。此设置适用于启用“操作”功能时。\n* **`getParent`**（可选）：\n  * 默认值：`'tr'`（属性将应用于整行）。\n  * 您也可以指定其他选择器（例如 `'td'`、`'.field-my_field'`），以找到由 `selector` 匹配元素的父级。\n  * 设置为 `false`（布尔值，而非字符串）或空字符串，表示直接将属性应用于由 `selector` 匹配的元素。\n* **其他键：** 被视为要添加到目标元素的 HTML 属性。\n\n## 📑 \u003Ca name=\"form-tabs\">表单选项卡\u003C\u002Fa>\n\n![表单选项卡截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_141db3f95af7.png)\n使用选项卡组织管理后台表单中的字段集和内联表单。标题会自动派生。\n\n**配置（在 `ModelAdmin.fieldsets` 或 `ModelAdmin.inlines` 中）：**\n\n```python\n# admin.py\n# from myapp.models import Attribute, Feature # 您的模型\n\n# class AttributeInline(admin.StackedInline):\n#     model = Attribute # 您的模型\n#     extra = 1\n\n# class FeatureInline(admin.StackedInline):\n#     model = Feature # 您的模型\n\n#     extra = 1\n\nclass ItemAdmin(admin.ModelAdmin):\n    # list_display = ('label', 'description', 'main_feature')\n    # inlines = [AttributeInline, FeatureInline] # 内联的顺序对分组很重要\n\n    fieldsets = (\n        ('主要信息', { # 这个字段集将成为第一个标签页（或其一部分）\n            'fields': ('label', 'description'),\n            'classes': ('baton-tabs-init', 'order-0', 'baton-tab-group-main--inline-attribute'),\n            # 'baton-tabs-init'：必须在第一个字段集中使用，以启用标签页功能。\n            # 'order-X'：（可选）定义该字段集的标签页顺序。默认为0。\n            # 'baton-tab-inline-MODELNAME'：为名为`MODELNAME`（小写模型名）的内联创建一个标签页。\n            # 'baton-tab-fs-CUSTOMNAME'：为该字段集创建一个标签页（例如，“Main Info” -> “content_tab”）。\n            # 'baton-tab-group-GROUPNAME--item1type-ITEMNAME--item2type-ITEMNAME'：创建一个分组标签页。\n            #    GROUPNAME 是任意名称。ITEMNAME 可以是 fs-字段集名称 或 inline-内联模型名称。\n            #    例如：“baton-tab-group-overview--fs-main_info--inline-attribute”\n            #    这将创建一个名为“Overview”的分组标签页，包含“Main Info”字段集和“Attribute”内联。\n            'description': '这是物品的主要信息。'\n        }),\n        ('内容详情', {\n            'fields': ('text', ),\n            'classes': ('baton-tab-fs-content', ), # 该字段集将成为名为“Content”的标签页\n            'description': '物品的详细内容。'\n        }),\n        ('技术规格', {\n            'fields': ('main_feature', ),\n            # 该字段集属于“Main Info”字段集中定义的一个分组标签页：\n            # 例如，“Main Info”中的‘baton-tab-group-main--inline-attribute--fs-tech--inline-feature’会将其归入该分组。\n            'classes': ('baton-tab-fs-tech', ),\n            'description': '技术规格和特性。'\n        }),\n    )\n```\n\n**标签页类别的规则（应用于字段集的 `classes` 元组）：**\n\n* **`baton-tabs-init`**：必须在*第一个*字段集定义中使用，以激活标签页系统。\n* **`order-X`**：（可选，在第一个字段集中使用）设置由第一个字段集生成的标签页的显示顺序。`X` 是一个数字（如 `order-0`、`order-1`）。\n* **`baton-tab-inline-MODELNAME`**：为模型名为 `MODELNAME`（小写）的内联创建一个单独的标签页。如果内联使用了 `related_name`，则应使用 `baton-tab-inline-RELATEDNAME`。\n* **`baton-tab-fs-CUSTOMNAME`**：为同时具有 `tab-fs-CUSTOMNAME` 类别的字段集创建一个单独的标签页。`CUSTOMNAME` 是您选择的任意名称。\n* **`baton-tab-group-GROUPNAME--item1type-ITEM1NAME--item2type-ITEM2NAME...`**：创建一个分组标签页。\n  * `GROUPNAME` 是您为标签页选择的任意名称。\n  * `itemXtype` 可以是 `fs`（用于字段集）或 `inline`（用于内联）。\n  * `ITEMXNAME` 是您的 `CUSTOMNAME`（用于字段集）或 `MODELNAME`\u002F`RELATEDNAME`（用于内联）。\n  * 例如：`baton-tab-group-overview--fs-main_content--inline-attributes`\n* 没有 `baton-tab-fs-*` 类别且不属于任何分组的字段集，将被附加到第一个标签页上。\n* 若要使某个字段集*始终可见*（不隶属于任何标签页），请为其添加 `tab-fs-none` 类别。\n\n**其他标签页功能：**\n\n* 如果表单字段出现错误，包含该字段的第一个标签页将自动打开。\n* 通过在 URL 中添加标签页的哈希值来实现深度链接（如 `#inline-feature`、`#fs-content`、`#group-overview--fs-main_content--inline-attributes`）。哈希值源自标签页类别的名称。\n\n## 📎 \u003Ca name=\"form-includes\">表单包含\u003C\u002Fa>\n>\n> 需要浏览器支持 HTML `\u003Ctemplate>` 标签。\n\n在更改表单中特定字段附近嵌入自定义模板。\n\n```python\n# admin.py\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_form_includes = [\n        ('myapp\u002Fadmin_includes\u002Fdatetime_helper.html', 'publication_date', 'top'),\n        ('myapp\u002Fadmin_includes\u002Fcontent_notes.html', 'body_content', 'above'),\n        ('myapp\u002Fadmin_includes\u002Ffield_icon.html', 'title', 'right'),\n    ]\n```\n\n![表单包含截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_b600d5a2f366.png)\n\n**位置：**\n\n| 位置 | 描述                                  |\n| :------- | :------------------------------------------- |\n| `top`    | 在字段所在行的顶部。     |\n| `bottom` | 在字段所在行的底部。  |\n| `above`  | 在字段所在行的上方。                  |\n| `below`  | 在字段所在行的下方。                  |\n| `right`  | 在输入框右侧的行内位置。     |\n\n您的包含模板中可以访问 `{{ original }}` 对象（模型实例）。此功能与标签页兼容。\n\n**对象工具包含：**\n将模板注入到对象工具栏中（更改表单右上角）。模板会被插入到 `\u003Cul>` 标签内。\n\n```python\n# admin.py\nclass NewsAdmin(admin.ModelAdmin):\n    # ...\n    baton_form_object_tools_include = ('myapp\u002Fadmin_includes\u002Fcustom_object_action.html', 'left') # 或 'right'\n```\n\n![对象工具包含截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_5301e7a484cd.png)\n\n## 🤏 \u003Ca name=\"collapsable-stacked-inlines\">可折叠堆叠式内联\u003C\u002Fa>\n\n![可折叠堆叠式内联截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_ebadbd10e970.png)\n使 `admin.StackedInline` 中的每个条目都可以折叠。\n\n在内联的 `classes` 中添加 `collapse-entry`：\n\n```python\n# admin.py\nclass VideoInline(admin.StackedInline):\n    # model = Video # 您的模型\n    extra = 1\n    classes = ('collapse-entry', ) # 可以与 Django 的 'collapse' 类结合使用\n```\n\n若要使第一条目默认展开：\n\n```python\n# admin.py\nclass VideoInline(admin.StackedInline):\n    # model = Video # 您的模型\n    extra = 1\n    classes = ('collapse-entry', 'expand-first')\n```\n\n## 🎨 \u003Ca name=\"themes-customization\">主题与自定义\u003C\u002Fa>\n\n轻松自定义 Baton 的外观：\n\n1. **CSS 变量：**\n    在你的应用的某个静态目录中创建一个 `baton\u002Fcss\u002Froot.css` 文件（确保该应用在 `INSTALLED_APPS` 中 *位于* `baton` 之前）。覆盖 Baton 默认 [root.css](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Ftree\u002Fmaster\u002Fbaton\u002Fstatic\u002Fbaton\u002Fcss\u002Froot.css) 中定义的任何 CSS 变量。\n    示例：\n\n    ```css\n    \u002F* myapp\u002Fstatic\u002Fbaton\u002Fcss\u002Froot.css *\u002F\n    :root {\n      --bs-primary: #FF6347;\n      --bs-primary-rgb: 255,99,71;\n      --baton-sidebar-active-bg: #FF6347;\n    }\n    ```\n\n2. **管理界面主题：**\n    直接从管理站点的 `\u002Fadmin\u002Fbaton\u002Fbatontheme\u002F` 创建和管理主题。每次只能激活一个主题。其 CSS 内容（应定义 CSS 变量）将覆盖 `baton\u002Fcss\u002Froot.css` 文件。\n    > **注意：** 主题内容被标记为安全，并原样注入。请务必小心。\n    > ✨ 你可以在 [django-baton-themes](https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton-themes) 找到现成的主题。\n\n3. **深度自定义（重新编译 JS 应用程序）：**\n    如果需要更改 Bootstrap 的主色或辅色，或者进行大量修改，你可以重新编译 Baton 的 JavaScript 应用程序。\n    ![自定义截图](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_6d86d438a145.png)\n    1. 克隆 `django-baton`。\n    2. 导航到 `django-baton\u002Fbaton\u002Fstatic\u002Fbaton\u002Fapp\u002F`。\n    3. 运行 `npm install`。\n    4. 编辑 `src\u002Fstyles\u002F_variables.scss`（以及根据需要编辑其他 SCSS\u002FJS 文件）。\n    5. 运行 `npm run compile`。\n    6. 将编译后的 `dist\u002Fbaton.min.js` 复制到你的项目中：`YOUR_APP\u002Fstatic\u002Fbaton\u002Fapp\u002Fdist\u002F`。\n    7. 确保 `YOUR_APP` 在 `INSTALLED_APPS` 中 *位于* `baton` 之前。\n\n    对于支持自动重新编译的实时开发：\n    1. `cd django-baton\u002Fbaton\u002Fstatic\u002Fbaton\u002Fapp\u002F`\n    2. 运行 `npm run dev:baton`（启动 Webpack 开发服务器，默认在 `http:\u002F\u002Flocalhost:8080`）。\n    3. 在你项目的 `admin\u002Fbase_site.html` 中（可能需要覆盖），将脚本源更改为指向开发服务器：\n\n        ```html\n        {# \u003Cscript src=\"{% static 'baton\u002Fapp\u002Fdist\u002Fbaton.min.js' %}\">\u003C\u002Fscript> #}\n        \u003Cscript src=\"http:\u002F\u002Flocalhost:8080\u002Fstatic\u002Fbaton\u002Fapp\u002Fdist\u002Fbaton.min.js\">\u003C\u002Fscript>\n        ```\n\n    现在，JS 应用程序中的更改会自动更新，只需刷新你的 Django 管理页面即可。\n\n## 🧪 \u003Ca name=\"tests\">测试\u003C\u002Fa>\n\nBaton 包含使用 Selenium 的单元测试和端到端测试。要运行端到端测试，请确保测试应用（位于 Baton 仓库的 `testapp` 目录中）正在 `localhost:8000` 上运行。\n\n## 💻 \u003Ca name=\"development\">开发\u003C\u002Fa>\n\n要参与贡献或进行本地开发：\n\n1. **设置测试应用：**\n\n    ```bash\n    cd testapp\n    python3 -m venv .virtualenv\n    source .virtualenv\u002Fbin\u002Factivate # Windows 上：.virtualenv\\\\Scripts\\\\activate\n    cd app\n    pip install -r requirements.txt\n    python manage.py migrate\n    python manage.py createsuperuser # 如需\n    python manage.py runserver\n    ```\n\n    （`createsuperuser` 后的默认登录信息：`admin` \u002F `admin`，或你所设定的用户名和密码）。\n\n2. **启用开发时的实时 JS 重新编译：**\n    * 在 `testapp\u002Fapp\u002Ftemplates\u002Fadmin\u002Fbase_site.html` 中，将脚本源切换为 Webpack 开发服务器：\n\n        ```html\n        {# \u003Cscript src=\"{% static 'baton\u002Fapp\u002Fdist\u002Fbaton.min.js' %}\">\u003C\u002Fscript> #}\n        \u003Cscript src=\"http:\u002F\u002Flocalhost:8080\u002Fstatic\u002Fbaton\u002Fapp\u002Fdist\u002Fbaton.min.js\">\u003C\u002Fscript>\n        ```\n\n    * 在一个新的终端中，导航到 Baton 的前端应用目录并启动开发服务器：\n\n        ```bash\n        cd \u002Fpath\u002Fto\u002Fyour\u002Fdjango-baton\u002Fbaton\u002Fstatic\u002Fbaton\u002Fapp\u002F\n        npm install\n        npm run dev # 用于持续开发，监听文件变化\n        ```\n\n    现在，对 Baton 前端应用的更改将会自动重新编译。刷新浏览器即可看到效果。\n\n### 命令\n\n在你的 Python 环境中安装 `invoke` 和 `sphinx_rtd_theme` 以生成文档：\n\n```bash\npip install invoke sphinx_rtd_theme\n```\n\n要在本地生成文档（从 `django-baton` 仓库的根目录）：\n\n```bash\ninvoke docs\n```\n\n## 🤝 \u003Ca name=\"contributing\">贡献\u003C\u002Fa>\n\n请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)，了解我们的行为准则以及提交拉取请求的流程。我们欢迎所有贡献！\n\n## 🌟 \u003Ca name=\"star-history\">星标历史\u003C\u002Fa>\n\n[![星标历史图表](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_readme_ae2007b1a7a1.png)](https:\u002F\u002Fstar-history.com\u002F#otto-torino\u002Fdjango-baton&Date)","# Django Baton 快速上手指南\n\nDjango Baton 是一个基于 Bootstrap 5 和 Google Material Symbols 构建的现代化、响应式且具备 AI 增强功能的 Django Admin 界面。它能将标准的 Django 后台转变为直观、美观且功能强大的管理面板。\n\n## 环境准备\n\n在开始之前，请确保你的开发环境满足以下要求：\n\n*   **Python**: 3.8 或更高版本\n*   **Django 版本兼容性**:\n    *   Django >= 5.x: 需使用 Baton >= 5.0\n    *   2.1 \u003C= Django \u003C 5.x: 需使用 Baton == 4.x\n    *   Django 1.x (旧版): 需使用 `django-baton==1.13.2`\n*   **前置依赖**: 无特殊系统级依赖，Baton 前端资源（Bootstrap 5, jQuery, Material Symbols）已打包在内。\n\n> **提示**：国内开发者建议使用国内镜像源加速安装。\n\n## 安装步骤\n\n### 1. 安装依赖包\n\n使用 pip 进行安装（推荐配置国内镜像源）：\n\n```bash\npip install django-baton -i https:\u002F\u002Fpypi.tuna.tsinghua.edu.cn\u002Fsimple\n```\n\n若需使用最新开发版，可克隆仓库：\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton.git\n```\n\n### 2. 配置 `settings.py`\n\n打开项目的 `settings.py` 文件，修改 `INSTALLED_APPS`。**注意顺序至关重要**：\n*   `'baton'` 必须放在 `'django.contrib.admin'` **之前**。\n*   `'baton.autodiscover'` 必须放在列表的 **最后**。\n\n```python\n# settings.py\n\nINSTALLED_APPS = [\n    # ... 其他应用 ...\n    'baton',                  # 必须在 django.contrib.admin 之前\n    'django.contrib.admin',\n    # ... 其他应用 ...\n    'baton.autodiscover',     # 必须是最后一个应用\n]\n```\n\n### 3. 执行数据库迁移\n\n运行迁移命令以创建必要的数据库表：\n\n```bash\npython manage.py migrate\n```\n\n### 4. 配置 `urls.py`\n\n打开项目主目录下的 `urls.py`，替换默认的 admin 导入，并包含 Baton 的路由：\n\n```python\n# urls.py\n# from django.contrib import admin  # 注释或删除此行\nfrom baton.autodiscover import admin  # 导入 Baton 的 admin\nfrom django.urls import path, include\n\nurlpatterns = [\n    path('admin\u002F', admin.site.urls),\n    path('baton\u002F', include('baton.urls')), # 添加 Baton 路由\n    # ... 其他 URL 模式 ...\n]\n```\n\n## 基本使用\n\n完成上述安装步骤后，重启开发服务器即可体验全新的 Admin 界面。\n\n### 1. 启动服务\n\n```bash\npython manage.py runserver\n```\n\n访问 `http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002F`，你将看到经过现代化改造的登录页面和管理后台。\n\n### 2. 基础配置示例\n\n在 `settings.py` 中添加 `BATON` 字典以自定义站点信息和菜单。以下是一个最小化配置示例：\n\n```python\n# settings.py\n\nBATON = {\n    'SITE_HEADER': '我的管理后台',\n    'SITE_TITLE': 'My Admin',\n    'INDEX_TITLE': '仪表盘',\n    'COPYRIGHT': 'copyright © 2025 My Company',\n    \n    # 启用未保存更改确认、图片预览等常用功能\n    'CONFIRM_UNSAVED_CHANGES': True,\n    'ENABLE_IMAGES_PREVIEW': True,\n    \n    # 自定义侧边栏菜单\n    'MENU': (\n        {\n            'type': 'app',\n            'name': 'auth',\n            'label': '认证管理',\n            'icon': 'lock', # 使用 Google Material Symbols 图标名\n            'models': (\n                {'name': 'user', 'label': '用户', 'icon': 'group'},\n                {'name': 'group', 'label': '群组', 'icon': 'verified_user'},\n            )\n        },\n        # 添加自由链接\n        {\n            'type': 'free',\n            'label': '官方文档',\n            'url': 'https:\u002F\u002Fdjango-baton.readthedocs.io\u002F',\n            'icon': 'description',\n        },\n    ),\n}\n```\n\n### 3. 关于 AI 功能（可选）\n\n若需使用自动翻译、文本摘要、图像生成等 AI 功能，需在 `BATON` 配置中设置 `BATON_CLIENT_ID` 和 `BATON_CLIENT_SECRET`，并在 `AI` 字段中指定模型（如 `AIModels.BATON_GPT_4O_MINI`）。这些功能需要有效的订阅密钥才能运行。\n\n现在，你已经成功集成了 Django Baton，可以享受更现代化的后台管理体验了。","某电商初创团队正在快速搭建后台管理系统，需要频繁处理多语言商品上架、撰写营销文案以及为海量商品图添加描述。\n\n### 没有 django-baton 时\n- **多语言维护繁琐**：运营人员需手动将中文商品详情翻译成英文和西班牙文，不仅耗时且容易出现术语不一致，导致多语言站点内容不同步。\n- **图片无障碍缺失**：面对数千张新上传的商品图，开发人员不得不编写脚本或人工逐一填写 `alt` 属性，否则无法满足 SEO 和无障碍访问标准。\n- **文案创作效率低**：市场部在后台编辑促销介绍时，缺乏辅助工具，只能切换到外部 AI 网站生成文案再复制回来，工作流频繁中断。\n- **界面体验陈旧**：默认的 Django Admin 界面在手机端操作困难，响应式布局缺失，导致仓库管理员无法在移动设备上高效核对库存。\n\n### 使用 django-baton 后\n- **一键自动翻译**：集成 `django-modeltranslation` 后，运营人员在保存中文内容时，django-baton 利用 AI 自动填充其他语言字段，确保全球站点内容实时一致。\n- **智能图像描述**：借助计算机视觉功能，系统能自动分析上传的商品图并生成准确的 `alt` 文本，彻底解放了人工标注的成本。\n- **内置写作助手**：编辑页面直接嵌入 AI 功能，支持文本摘要、润色纠错甚至通过 DALL·E 3 生成配图，让文案创作在后台内闭环完成。\n- **现代化移动体验**：基于 Bootstrap 5 重构的界面完美适配手机和平板，仓库人员可随时随地通过响应式菜单流畅管理订单。\n\ndjango-baton 将原本割裂的 AI 工作流无缝融入 Django 后台，把繁琐的内容运维变成了智能化的自动化流程。","https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fotto-torino_django-baton_530d914c.gif","otto-torino","Otto srl","https:\u002F\u002Foss.gittoolsai.com\u002Favatars\u002Fotto-torino_38dce579.png","",null,"opensource@otto.srl","http:\u002F\u002Fwww.otto.srl","https:\u002F\u002Fgithub.com\u002Fotto-torino",[83,87,91,95,99,103],{"name":84,"color":85,"percentage":86},"Python","#3572A5",32.2,{"name":88,"color":89,"percentage":90},"JavaScript","#f1e05a",30.7,{"name":92,"color":93,"percentage":94},"SCSS","#c6538c",22,{"name":96,"color":97,"percentage":98},"HTML","#e34c26",10.4,{"name":100,"color":101,"percentage":102},"CSS","#663399",4.7,{"name":104,"color":105,"percentage":106},"Vim Script","#199f4b",0,985,96,"2026-04-08T16:41:46","MIT","未说明 (适用于任何支持 Django 的操作系统)","不需要本地 GPU。AI 功能（如图像生成、视觉分析）通过 API 调用外部服务（如 DALL·E 3, GPT-4），需配置 Baton 订阅密钥。","未说明 (取决于底层 Django 项目需求)",{"notes":115,"python":116,"dependencies":117},"该工具是 Django 管理界面的增强插件，而非独立的 AI 模型运行环境。使用 AI 功能（翻译、摘要、纠错、图像生成\u002F分析）必须配置 'BATON_CLIENT_ID' 和 'BATON_CLIENT_SECRET' 以连接外部 AI 服务，无需在本地部署大型语言模型或计算机视觉模型。","未明确指定 (需匹配所安装的 Django 版本：Django >= 5.x 对应 Baton >= 5.0；Django 2.1-5.x 对应 Baton 4.x)",[118,119,120,121,122],"Django (>= 2.1)","Bootstrap 5 (前端)","Google Material Symbols (前端)","jQuery (前端)","django-modeltranslation (可选，用于自动翻译)",[35,15,45],"2026-03-27T02:49:30.150509","2026-04-19T09:39:18.426799",[127,132,137,142,147,152,157],{"id":128,"question_zh":129,"answer_zh":130,"source_url":131},42216,"升级后保存表单时如何避免丢失当前选中的标签页（Tab）上下文？","这是 v2 版本的预期行为。为了避免破坏 Safari 兼容性，系统不会使用 `deleteUrl` API。现在的逻辑是：切换标签页时不会在浏览器历史栈中创建新条目，而是替换当前条目（例如将 URL 哈希更新为 `#tab`）。当你点击“保存并继续”时，会重定向到同一页面，从而保持标签页状态。如果仍遇到问题，请确保清除缓存或使用无痕模式测试最新开发版。","https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues\u002F157",{"id":133,"question_zh":134,"answer_zh":135,"source_url":136},42217,"如何解决与 django-import-export 插件集成时的 CSS 样式错乱问题？","该问题通常是由于缺少 postcss loader 导致的。维护者已修复此问题，请确保安装最新的开发版本。如果通过 pip 安装后仍未生效，尝试先卸载再重新安装开发分支：\n```\npip uninstall django-baton\npip install django-baton@git+https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton@develop\n```\n安装后刷新页面（可能需要强制刷新 Shift+F5）即可正常显示。","https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues\u002F161",{"id":138,"question_zh":139,"answer_zh":140,"source_url":141},42218,"在手机或小屏幕设备上，提交按钮（submit_line）排列混乱怎么办？","Baton 默认无法自动调整此行为，但你可以通过自定义 CSS 来解决。建议对 `.submit-row` 元素应用媒体查询（media queries），强制按钮在窄屏下垂直居中排列或调整布局。维护者表示，只要按钮居中对齐，垂直排列也是可以接受的视觉效果。","https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues\u002F73",{"id":143,"question_zh":144,"answer_zh":145,"source_url":146},42219,"自定义菜单中，当 Free 类型链接包含 Model 类型链接的 URL 路径时，为何两个菜单项都会被高亮？","这是因为 Baton 的菜单高亮逻辑基于 URL 匹配。如果自定义的 free 类型 URL（如 `\u002F...\u002Fcalendar\u002F`）包含了 model 类型的 URL 前缀（如 `\u002F...\u002F`），系统会认为两者都匹配从而同时高亮。目前的解决方案是修改 free 类型的 URL，使其不包含 model 的 URL 路径，或者将该视图设计为不直接依附于该 Model Admin 的路径结构。","https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues\u002F280",{"id":148,"question_zh":149,"answer_zh":150,"source_url":151},42220,"切换标签页时页面会自动向下滚动，如何禁用此行为？","该功能已在开发分支（develop branch）中修复。要解决此问题，你需要安装最新的开发版本。由于版本号可能未变，建议先卸载再安装：\n```\npip uninstall django-baton\npip install django-baton@git+https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton@develop\n```\n安装完成后，切换标签页将不再触发自动滚动。","https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues\u002F264",{"id":153,"question_zh":154,"answer_zh":155,"source_url":156},42221,"表单中包含字段显示重复文本（双倍内容）是什么原因？","这通常是由第三方插件冲突引起的，特别是 `django-easy-select2`。该插件可能会干扰 Baton 的表单渲染逻辑。解决方法是移除或禁用 `django-easy-select2`，使用 Django 原生的选择控件或 Baton 兼容的替代方案，问题即可解决。","https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues\u002F150",{"id":158,"question_zh":159,"answer_zh":160,"source_url":161},42222,"为什么安装了 Baton 后，Django 默认的 base.css 仍然加载并破坏了样式（如字体变白）？","维护者无法复现此问题，且在 Django 3.2.4 等版本中测试正常。这通常是由于项目配置错误或缓存问题导致的。建议检查 `INSTALLED_APPS` 中 Baton 是否位于 `django.contrib.admin` 之前，并确保浏览器缓存已清除。如果问题依旧，需要提供一个可复现的最小仓库供进一步排查。","https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fissues\u002F164",[163,168,173,178,183,188,193,198,203,208,213,218,223,228,233,238,243,248,253,258],{"id":164,"version":165,"summary_zh":166,"released_at":167},334296,"5.1.2","修复：\n- 后台菜单中的视图图标","2026-02-22T13:31:11",{"id":169,"version":170,"summary_zh":171,"released_at":172},334297,"5.1.1","修复：\n- 动态添加内联字段时的图片生成和视觉效果\n- Django 6 下更改列表工具栏的位置\n- Django 6 下的分页样式\n- 在 Django 6 中将图例用作标签样式\n\n特性：\n- 添加可配置的 AI 端点基础路径\n","2026-02-11T14:40:08",{"id":174,"version":175,"summary_zh":176,"released_at":177},334298,"5.1.0","功能：\n- 添加变更列表对象工具，包括","2026-01-24T10:19:46",{"id":179,"version":180,"summary_zh":181,"released_at":182},334299,"5.0.6","修复：\n- 导入 RTL CSS 时未找到字体","2026-01-21T16:03:29",{"id":184,"version":185,"summary_zh":186,"released_at":187},334300,"5.0.5","修复：\n- 当“菜单始终折叠”选项启用时，关闭图标样式问题。","2026-01-07T17:20:47",{"id":189,"version":190,"summary_zh":191,"released_at":192},334301,"5.0.4","修复：\n\n- 改进了更改表单中相关图标大小","2026-01-07T14:01:34",{"id":194,"version":195,"summary_zh":196,"released_at":197},334302,"5.0.3","修复：\n\n- #312\n- Gravatar 加载时显示加载字符串\n- 模态框关闭按钮位置","2025-11-03T08:55:18",{"id":199,"version":200,"summary_zh":201,"released_at":202},334303,"5.0.2","改进的姿势包括各个位置","2025-06-11T14:29:02",{"id":204,"version":205,"summary_zh":206,"released_at":207},334304,"5.0.1","修复 #306 ","2025-06-11T09:53:15",{"id":209,"version":210,"summary_zh":211,"released_at":212},334305,"5.0.0","新重大版本发布\n\n- FontAwesome 被 Google Material Symbols 取代\n- 字体变更：使用 Inter Tight 和 Archivo Narrow 替代 Dosis\n- 针对 Django >= 5.x 进行了优化\n\n迁移指南：https:\u002F\u002Fgithub.com\u002Fotto-torino\u002Fdjango-baton\u002Fwiki\u002FMigrate-from-v4-to-v5","2025-06-03T10:52:11",{"id":214,"version":215,"summary_zh":216,"released_at":217},334306,"4.2.3","Fix collapse-entry with new Django details markup","2025-05-21T15:57:11",{"id":219,"version":220,"summary_zh":221,"released_at":222},334307,"4.2.2","Fixes:\r\n\r\n- #295 : hide hidden fields rows\r\n- stats widget vision model value","2024-12-12T09:53:19",{"id":224,"version":225,"summary_zh":226,"released_at":227},334308,"4.2.1","Features:\r\n\r\n- improve support for rtl languages\r\n- add fa translations\r\n- add image vision to BatonAiImageField\r\n- add subject location  feature to BatonAiImageField\r\n\r\nFixes:\r\n\r\n- fix added migration issue #296 \r\n- fix minor styling problems","2024-12-11T15:41:27",{"id":229,"version":230,"summary_zh":231,"released_at":232},334309,"4.2.0","Features:\r\n\r\n-  adds image vision functionality\r\n\r\nFix:\r\n\r\n- minor style adjustments for Django 5.1.x","2024-12-02T17:18:00",{"id":234,"version":235,"summary_zh":236,"released_at":237},334310,"4.1.0","Features:\r\n- adds support for new gpt-4o-mini model\r\n\r\nFixes:\r\n- #291 \r\n- #292 \r\n- #293 ","2024-07-19T08:17:53",{"id":239,"version":240,"summary_zh":241,"released_at":242},334311,"4.0.1","Features:\r\n- Baton AI\r\n- Themes\r\n\r\nMinor fixes","2024-06-12T16:05:16",{"id":244,"version":245,"summary_zh":246,"released_at":247},334312,"3.1.0","Add change form object tools template include functionality","2024-02-21T11:41:35",{"id":249,"version":250,"summary_zh":251,"released_at":252},334313,"3.0.2","Fix submit fixed row initialization in certain conditions with tabs","2024-02-01T16:13:48",{"id":254,"version":255,"summary_zh":256,"released_at":257},334314,"3.0.1","Minor style adjustments","2024-02-01T14:53:06",{"id":259,"version":260,"summary_zh":261,"released_at":262},334315,"3.0.0","Major release because has breaking changes ONLY if you used the analytics module.\r\n\r\n- Removed analytics module (the js library I used cannot work with GA4, and I don't want to mantain a library myself, also because we do not use GA anymore)\r\n- Added submit row fixed bottom position option, enabled by default\r\n- Fix #283, #246","2024-01-31T17:21:30"]