如何覆盖和扩展基本的Django管理模板?

172

如何在扩展它的同时覆盖管理模板(例如admin/index.html)(请参见https://docs.djangoproject.com/en/dev/ref/contrib/admin/#overriding-vs-replacing-an-admin-template)?

首先,我知道这个问题以前已经被问过和回答过了(请参见Django: Overriding AND extending an app template),但是正如答案所说,如果您正在使用app_directories模板加载器(大多数情况下都是如此),它并不直接适用。

我的当前解决方法是制作副本并从中进行扩展,而不是直接从管理模板进行扩展。这很好用,但当管理模板更改时会增加额外的工作量并且容易混淆。

我可以想到一些自定义扩展标签来处理这些模板,但如果已经存在解决方案,我就不想重复造轮子。

顺便说一句:有人知道Django本身是否会解决这个问题吗?


2
复制管理员模板,扩展它们并覆盖/添加块是最有效的工作流程,虽然不是Django当前状态下的最佳工作流程。在我三年的使用中,我没有看到其他方法来做你试图要做的事情 :) - Brandon Taylor
1
嗯,我不知道这是好事还是坏事,但至少像你这样的人得出了相同的结论。听到这个消息很高兴。 :) - Semmel
12个回答

128

更新:

阅读您的Django版本的文档,例如最新版本或旧的LTS版本:3.22.21.11

2011年的原始答案:

一年半前我也遇到了同样的问题,我在djangosnippets.org上找到了一个很好的模板加载器,可以轻松解决这个问题。它允许您扩展特定应用程序中的模板,使您能够创建自己的admin/index.html,并将其扩展到来自管理员应用程序的admin/index.html模板。就像这样:

{% extends "admin:admin/index.html" %}

{% block sidebar %}
    {{block.super}}
    <div>
        <h1>Extra links</h1>
        <a href="/admin/extra/">My extra link</a>
    </div>
{% endblock %}

我在我的网站上的博客文章中给出了一个完整的示例,说明如何使用这个模板加载器。


22
供参考:问题中的片段已转换为 Django 应用程序,并可在 PyPi(pip/easy_install)上作为 django-apptemplates 下载:http://pypi.python.org/pypi/django-apptemplates/ - Romløk
12
只是为了100%明确:上述解决方案在最近版本的Django(至少1.4)中将不再起作用,因为脚本使用的函数之一已经过时。您可以在此处找到更新的源代码 - OldTinfoil
3
请注意,使用Django 1.8仍然可行,但需要以特定方式进行设置(例如,请参阅app_namespace.Loader setup)。如果django-apptemplates有一天无法使用,django-app-namespace-template-loader也是一个可行的替代方案。 - Peterino
这篇回答对于旧版Django非常好。但是现在,程的另一个答案更为相关。https://dev59.com/NWw15IYBdhLWcg3weLwF#29997719 - DevLoverUmar

85

关于Django 1.8是当前版本的问题,不需要像上面的回答建议的那样创建软链接、将admin/templates复制到项目文件夹中或安装中间件。以下是操作步骤:

  1. 按照官方文档推荐,创建以下目录结构

your_project
     |-- your_project/
     |-- myapp/
     |-- templates/
          |-- admin/
              |-- myapp/
                  |-- change_form.html  <- do not misspell this

注意:此文件的位置并不重要。您可以将其放在应用程序内部,它仍然可以正常工作。只要django可以发现其位置即可。更重要的是,HTML文件的名称必须与django提供的原始HTML文件名称相同。

  1. 将此模板路径添加到您的settings.py中:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], # <- add this line
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
  • 确定您要覆盖的名称和块。这可以通过查看Django的admin/templates目录来完成。我正在使用virtualenv,因此对我来说,路径在此处:

  • ~/.virtualenvs/edge/lib/python2.7/site-packages/django/contrib/admin/templates/admin
    

    在这个例子中,我想修改添加新用户的表单。负责这个视图的模板是change_form.html。打开change_form.html并找到你想要扩展的{% block %}。

    1. 你的change_form.html中,编写类似以下内容的代码:

    {% extends "admin/change_form.html" %}
    {% block field_sets %}
         {# your modification here #}
    {% endblock %}
    
  • 加载您的页面,您应该看到更改


  • 仅仅通过复制所有块来扩展主要的“index.html”模板还不够。一种解决方案是在“extends”路径中编写一些../,并指定更独特的原始路径{% extends "../../admin/templates/admin/index.html" %}。[答案链接](https://dev59.com/Dm445IYBdhLWcg3wO31u#23696160) - hynekcer
    1
    我认为在模板中我们应该使用'DIRS': [os.path.join(BASE_DIR, 'templates')]。 - Raul Reyes
    这是一种典型的线程,完美地说明了SO的缺陷。当一个框架得到更新时,问题就不再相关了,实际上它会阻碍正确的路径。这里有一个很好的答案。孩子们,请先阅读手册。 - Derek Adair
    谢谢你的回答。除了“这个文件的位置不重要”之外,一切都很好。 - Jaswanth Manigundan

    66

    如果您需要覆盖 admin/index.html,可以设置 AdminSiteindex_template 参数。

    例如:

    # urls.py
    ...
    from django.contrib import admin
    
    admin.site.index_template = 'admin/my_custom_index.html'
    admin.autodiscover()
    

    将模板放置于<appname>/templates/admin/my_custom_index.html


    6
    太好了!这样做可以让你在my_custom_index.html中使用{% extends "admin/index.html" %},而无需复制django管理模板。谢谢。 - mattmc3
    3
    @Semmel 应该将此标记为正确答案,因为它是使用内置的 Django 功能并且不需要使用自定义模板加载器的最简单方法。 - MrColes

    23

    使用 django 1.5 (或更高版本),您可以为特定的 modeladmin 定义要使用的模板。

    参见https://docs.djangoproject.com/en/1.5/ref/contrib/admin/#custom-template-options

    您可以像这样进行操作:

    class Myadmin(admin.ModelAdmin):
        change_form_template = 'change_form.htm'
    

    如果您想从头开始,可以使用一个简单的 HTML 模板来扩展 admin/change_form.html,或者直接扩展 change_form.html


    12

    我在官方 Django 文档中找不到一篇完整的答案或章节,包含我需要覆盖/扩展默认管理模板的所有信息。因此,我写了这篇完整指南,并希望它能对将来的读者有所帮助。

    假设标准的 Django 项目结构:

    mysite-container/         # project container directory
        manage.py
        mysite/               # project package
            __init__.py
            admin.py
            apps.py
            settings.py
            urls.py
            wsgi.py
        app1/
        app2/
        ...
        static/
        templates/
    

    以下是您需要做的事情:

    这里是您需要做的步骤:

    1. In mysite/admin.py, create a sub-class of AdminSite:

      from django.contrib.admin import AdminSite
      
      
      class CustomAdminSite(AdminSite):
          # set values for `site_header`, `site_title`, `index_title` etc.
          site_header = 'Custom Admin Site'
          ...
      
          # extend / override admin views, such as `index()`
          def index(self, request, extra_context=None):
              extra_context = extra_context or {}
      
              # do whatever you want to do and save the values in `extra_context`
              extra_context['world'] = 'Earth'
      
              return super(CustomAdminSite, self).index(request, extra_context)
      
      
      custom_admin_site = CustomAdminSite()
      

      Make sure to import custom_admin_site in the admin.py of your apps and register your models on it to display them on your customized admin site (if you want to).

    2. In mysite/apps.py, create a sub-class of AdminConfig and set default_site to admin.CustomAdminSite from the previous step:

      from django.contrib.admin.apps import AdminConfig
      
      
      class CustomAdminConfig(AdminConfig):
          default_site = 'admin.CustomAdminSite'
      
    3. In mysite/settings.py, replace django.admin.site in INSTALLED_APPS with apps.CustomAdminConfig (your custom admin app config from the previous step).

    4. In mysite/urls.py, replace admin.site.urls from the admin URL to custom_admin_site.urls

      from .admin import custom_admin_site
      
      
      urlpatterns = [
          ...
          path('admin/', custom_admin_site.urls),
          # for Django 1.x versions: url(r'^admin/', include(custom_admin_site.urls)),
          ...
      ]
      
    5. Create the template you want to modify in your templates directory, maintaining the default Django admin templates directory structure as specified in the docs. For example, if you were modifying admin/index.html, create the file templates/admin/index.html.

      All of the existing templates can be modified this way, and their names and structures can be found in Django's source code.

    6. Now you can either override the template by writing it from scratch or extend it and then override/extend specific blocks.

      For example, if you wanted to keep everything as-is but wanted to override the content block (which on the index page lists the apps and their models that you registered), add the following to templates/admin/index.html:

      {% extends 'admin/index.html' %}
      
      {% block content %}
        <h1>
          Hello, {{ world }}!
        </h1>
      {% endblock %}
      

      To preserve the original contents of a block, add {{ block.super }} wherever you want the original contents to be displayed:

      {% extends 'admin/index.html' %}
      
      {% block content %}
        <h1>
          Hello, {{ world }}!
        </h1>
        {{ block.super }}
      {% endblock %}
      

      You can also add custom styles and scripts by modifying the extrastyle and extrahead blocks.


    你有关于这个的源代码或文档吗? - user12951147
    除了我在第5点中添加的两个参考文献,没有,我没有其他东西。 - Faheel

    10

    Cheng的回答是正确的,但是根据管理员文档,并非每个管理员模板都可以以这种方式进行覆盖: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#overriding-admin-templates

    每个应用程序或模型可以被覆盖的模板

    contrib/admin/templates/admin中并非所有模板都可以按应用程序或模型进行覆盖。下列内容可以:

    app_index.html
    change_form.html
    change_list.html
    delete_confirmation.html
    object_history.html
    

    对于那些无法通过此方式进行覆盖的模板,您仍可以为整个项目覆盖它们。只需将新版本放置在templates/admin目录中。这对于创建自定义404和500页面特别有用。

    我必须覆盖管理的login.html文件,因此不得不将被覆盖的模板放在这个文件夹结构中:

    your_project
     |-- your_project/
     |-- myapp/
     |-- templates/
          |-- admin/
              |-- login.html  <- do not misspell this
    

    (在管理员中没有myapp子文件夹的情况下) 我没有足够的声望来评论Cheng的帖子,这就是为什么我不得不写一个新答案的原因。


    感谢您的反馈hyneker,我希望我的回答现在更加清晰明了了。 - matyas
    是的,了解到即使某些模板可以在应用程序级别上进行可选更改,但在项目级别上也可以自定义模板是很有用的。 - hynekcer

    6
    最好的方法是将Django管理模板放在您的项目中。因此,您的模板应该位于templates/admin,而标准的Django管理模板应该位于例如template/django_admin。然后,您可以执行以下操作:

    templates/admin/change_form.html

    {% extends 'django_admin/change_form.html' %}
    
    Your stuff here
    

    如果你担心保持股票模板的最新状态,你可以使用svn externals或类似工具来包含它们。


    使用svn外部引用是一个好主意。但是这会带来一个问题,所有的翻译者都要翻译那些模板(因为makemessages将从所有管理模板中收集翻译字符串),如果您使用多种语言,则会增加很多额外的工作量。也许有一种方法可以从makemessages中排除这些模板? - Semmel
    makemessages 命令中使用 --ignore 参数。参考文档:https://docs.djangoproject.com/en/dev/ref/django-admin/#makemessages - Chris Pratt
    我认为另一个答案更适合我的需求。但我喜欢你的解决方案,如果你不想搞乱你的模板加载器,那么它是一个很好的选择。 - Semmel

    4

    对于应用程序索引,请将此行添加到某个通用的py文件中,例如url.py。

    admin.site.index_template = 'admin/custom_index.html'
    

    对于应用程序模块索引:将以下行添加到admin.py中

    admin.AdminSite.app_index_template = "servers/servers-home.html"
    

    对于更改列表:在管理员类中添加此行:

    change_list_template = "servers/servers_changelist.html"
    

    对于应用程序模块表单模板:将此行添加到您的管理员类中

    change_form_template = "servers/server_changeform.html"
    

    等等,然后在同一管理员模块类中查找其他内容。


    3
    你可以通过多种方式覆盖Django管理模板。
    例如,下面是一个示例中的django-project
    django-project
     |-core
     |  └-settings.py
     |-app1
     |  |-models.py
     |  └-admin.py
     |-app2
     └-templates
    

    然后,在`settings.py`中,将`BASE_DIR / 'templates'`设置为`TEMPLATES`中的`DIRS`,使得`templates`文件夹能够被识别,如下所示:
    # "core/settings.py"
    
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [
                BASE_DIR / 'templates', # Here
            ],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    

    app1/models.py中有FoodDrink模型,如下所示:
    # "app1/models.py"
    
    class Food(models.Model):
        name = models.CharField(max_length=20)
    
    class Drink(models.Model):
        name = models.CharField(max_length=20)
    

    app1/admin.py 中,如下所示,有 FoodDrink 的管理员。
    # "app1/admin.py"
    
    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
        pass
    
    @admin.register(Drink)
    class DrinkAdmin(admin.ModelAdmin):
        pass
    

    现在,你可以在以下位置覆盖django管理模板change_form.htmltemplates/admin/templates/admin/app1/templates/admin/app1/food/。*你可以从虚拟环境中复制django管理模板,路径为django/contrib/admin/templates/admin/。有些django管理模板无法在templates/admin/app1/templates/admin/app1/food/中被覆盖,但是这些django管理模板可以在templates/admin/templates/admin/app1/templates/admin/app1/food/中被覆盖。你可以查看我的回答,了解哪些django管理模板可以在哪些目录中被覆盖。

    change_form.htmltemplates/admin/ 目录下可以自动应用于所有应用程序中的所有管理员。*小写的文件夹名称 admin 正常工作:

    django-project
     |-core
     |  └-settings.py
     |-app1
     |  |-models.py
     |  └-admin.py
     |-app2
     └-templates
        └-admin
           └-change_form.html # Here
    

    change_form.htmltemplates/admin/app1/ 下可以自动应用于 app1 中的所有管理员。*小写的文件夹名 app1 正常工作:

    django-project
     |-core
     |  └-settings.py
     |-app1
     |  |-models.py
     |  └-admin.py
     |-app2
     └-templates
        └-admin
           |-app1
           |  └-change_form.html # Here
           └-app2
    

    下面的`change_form.html`在`templates/admin/app1/food/`中可以自动应用于`app1`中的`food`管理界面。*小写的文件夹名`food`正常工作:
    django-project
     |-core
     |  └-settings.py
     |-app1
     |  |-models.py
     |  └-admin.py
     |-app2
     └-templates
        └-admin
           |-app1
           |  |-food
           |  |  └-change_form.html # Here
           |  └-drink
           └-app2
    

    现在,您可以将change_form.html重命名为custom_change_form.html,但是任何文件夹中的custom_change_form.html都不能自动应用于任何应用程序中的任何管理员。因此,您需要手动将custom_change_form.html应用于您想要应用custom_change_form.html的任何应用程序中的任何管理员。
    对于位于templates/admin/下的custom_change_form.html
    django-project
     |-core
     |  └-settings.py
     |-app1
     |  |-models.py
     |  └-admin.py
     |-app2
     └-templates
        └-admin
           └-custom_change_form.html # Here
    

    admin/custom_change_form.html设置为change_form_template,如下所示,在FoodDrink管理员中。*您可以找到更多自定义模板选项
    # "app1/admin.py"
    
    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
        change_form_template = 'admin/custom_change_form.html'
    
    @admin.register(Drink)
    class DrinkAdmin(admin.ModelAdmin):
        change_form_template = 'admin/custom_change_form.html'
    

    对于位于templates/admin/app1/下的custom_change_form.html文件:
    django-project
     |-core
     |  └-settings.py
     |-app1
     |  |-models.py
     |  └-admin.py
     |-app2
     └-templates
        └-admin
           |-app1
           |  └-custom_change_form.html # Here
           └-app2
    

    admin/app1/custom_change_form.html设置为change_form_template,如下所示,在FoodDrink管理员中:
    # "app1/admin.py"
    
    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
        change_form_template = 'admin/app1/custom_change_form.html'
    
    @admin.register(Drink)
    class DrinkAdmin(admin.ModelAdmin):
        change_form_template = 'admin/app1/custom_change_form.html'
    

    对于位于templates/admin/app1/food下的custom_change_form.html

    django-project
     |-core
     |  └-settings.py
     |-app1
     |  |-models.py
     |  └-admin.py
     |-app2
     └-templates
        └-admin
           |-app1
           |  |-food
           |  |  └-custom_change_form.html # Here
           |  └-drink
           └-app2
    

    admin/app1/food/custom_change_form.html设置为change_form_template,如下所示,在FoodDrink管理员中:

    # "app1/admin.py"
    
    @admin.register(Food)
    class FoodAdmin(admin.ModelAdmin):
        change_form_template = 'admin/app1/food/custom_change_form.html'
    
    @admin.register(Drink)
    class DrinkAdmin(admin.ModelAdmin):
        change_form_template = 'admin/app1/food/custom_change_form.html'
    

    1
    我同意Chris Pratt的观点。但我认为最好在原始的Django文件夹中创建符号链接,其中管理模板放置在其中:
    ln -s /usr/local/lib/python2.7/dist-packages/django/contrib/admin/templates/admin/ templates/django_admin
    

    正如您所见,它取决于Python版本和Django安装的文件夹。因此,在未来或生产服务器上,您可能需要更改路径。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接