Django:覆盖和扩展应用程序模板

48
如果你想覆盖 Django 中应用程序自带的模板(在 app/templates/app/ 目录下),你需要在另一个目录中创建同名的模板,这个模板会在应用程序模板目录之前被模板加载器检查。如果你只想覆盖模板的某些块,你还需要复制整个模板并更改该块,这实际上并不是很符合 DRY 原则。
有人知道一种同时扩展原始模板并覆盖特定块的方法吗?(关键是这样做时不要更改模板名称,因为在某些情况下,你可能必须更改视图以使其与另一个模板配合使用)
编辑:正如 Adam Taylor 在 Django 1.9 on 的评论中指出的那样,现在可以在没有任何 hack 的情况下实现此操作。

@paulo:据我所知,关于一般情况下覆盖应用程序模板的文档当然是有的,因为这是日常实践,但我从未看到过关于扩展具有相同名称的模板的任何内容。如果您知道文档中有关于此的内容,请指出该位置... - Bernhard Vallant
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#overriding-vs-replacing-an-admin-template - Paulo Scardine
1
那种方法只适用于特定于应用程序的管理模板,因为它们与原始路径不同。我猜这就是为什么这在admin文档中而不是在通用文档中的原因;) - Florian Ledermann
2
对于所有使用Django 1.9+的人:这现在已经内置于Django中。引用自Django 1.9发布说明中的“模板”部分:“Django模板加载器现在可以递归地扩展模板。” - Adam Taylor
@AdamTaylor 谢谢,这是一个非常有用的提示,指向了一个深藏在发布说明中的有趣功能。 - Bernhard Vallant
@AdamTaylor 我已经将你的评论转换成答案 - Flimm
6个回答

37

我认为这个相关问题的答案很恰当;目前,最好的解决方案似乎是使用PyPI上的django-apptemplates包中的自定义模板加载程序,因此您可以使用pip进行安装(例如:pip install django-apptemplates)。

模板加载程序允许您在特定应用程序中扩展模板;例如,要扩展管理员界面的索引页面,您需要添加

'apptemplates.Loader',

将以下代码添加到settings.py文件中的TEMPLATE_LOADERS列表中,并使用:
{% extends "admin:admin/index.html" %}

在你的模板中。


1
我所参与的一个项目使用这个代码片段来解决这个确切的问题。我很惊讶为什么更多的人没有遇到这个问题:一款想要调整现有页面部分的应用程序。 - Steve Bennett
2
我认为这是一个常见的问题,但人们通过复制完整模板来解决。如果有人尝试过覆盖和扩展,通常会导致无限递归。该问题在此票证中有描述:https://code.djangoproject.com/ticket/15053 - Nagyman

10

Django 1.9及更高版本有此功能

Django模板加载器现在可以递归扩展模板。

因此,您可以拥有来自app1的文件,其内容如下:

app1/templates/example/foobar.html

<p>I'm from app1</p>
{% block main %}{% endblock %}

你可以使用app2中的文件来扩展它(请注意,模板名称example/foobar.html是相同的):

app2/templates/example/foobar.html

{% extends "example/foobar.html" %}
{% block main %}
  <p>I'm from app2</p>
{% endblock %}

最终结果应该是:

<p>I'm from app1</p>
<p>I'm from app2</p>

我尝试过这个设置,但似乎不起作用。 - Soul Donut
@HoHo,你使用的是哪个版本的Django? - Flimm
我正在使用1.10版本--如果app1有一组相关的模板(比如app1/templates/app1/base.html,它被app1/templates/app1/subfolder/subsite.html扩展),那么app2的模板应该如何同时扩展这两个模板?我认为使用您的方法--在app1中复制文件夹层次结构和文件名--会起作用,但似乎并没有。 - Soul Donut

7
作为最新的更新,因为这似乎是一个常见的问题。我已经使用了 overextends 应用程序 ,没有任何问题。它提供了一个新的关键字 overextends,可以通过相同名称扩展模板。
而且,它很容易通过 pip 安装:
 pip install -U django-overextends

FYI,Django 1.4 的最后一个版本是0.3.2,后续版本需要1.7。 - Vajk Hermecz

2
在 Django 的维基百科中,Simon Willison 提出了一个 技巧 来实现“自扩展模板”的效果。但如果您正在使用 app_directories 模板加载器,则无法直接应用该技巧。
将应用程序的模板目录符号链接到一个新目录中可能会有所帮助。

谢谢,这也不完全是我想要的,但我认为你已经很接近了;在为不同的站点定制模板时,我已经在做类似的事情了! - Bernhard Vallant

-1

[更新]

我误读了问题,我的原始答案仅适用于管理员应用程序,该应用程序具有内置的模板扩展机制。对于其他缺乏此类机制的应用程序,我将只是分叉原始模板,而不是像所选答案建议的那样摆弄自定义模板加载器。如果您担心分叉,您也可以实现扩展机制并向原始项目贡献,如果您认为它值得。


直接从精细的手册中得知: 由于管理模板的模块化设计,通常不必也不建议替换整个模板。更好的方法是只覆盖需要更改的模板部分。

接着上面的例子,我们希望在页面模型的历史工具旁边添加一个新链接。在查看change_form.html后,我们确定只需要覆盖object-tools块。因此,这就是我们的新change_form.html:

{% extends "admin/change_form.html" %}
{% load i18n %}
{% block object-tools %}
{% if change %}{% if not is_popup %}
  <ul class="object-tools">
    <li><a href="history/" class="historylink">{% trans "History" %}</a></li>
    <li><a href="mylink/" class="historylink">My Link</a></li>
    {% if has_absolute_url %}
        <li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">
            {% trans "View on site" %}</a>
        </li>
    {% endif%}
  </ul>
{% endif %}{% endif %}
{% endblock %}

就是这样!如果我们将此文件放置在 templates/admin/my_app 目录中,我们的链接将出现在每个模型的更改表单上。


谢谢您发布这篇文章,但我的问题不是关于管理模板的。我正在寻找使用(前端)模板来完成类似于这样的操作,例如使用第三方应用程序提供的模板,我可能只想更改一个块,而复制整个剩余部分以覆盖一部分看起来像是DRY违规... - Bernhard Vallant
1
@lazerscience:这是相同的过程,只不过在您的模板目录中,您应该遵循与覆盖的模板相同的目录结构。请查看:http://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loader.select_template - Andrew Sledge
4
我认为这个回答并没有解答原问题。假设 niceapp.views.index() 使用模板 niceapp/index.html,如果我想使用原视图功能但自定义模板,我可以在项目或自己的应用程序的 templates 目录下创建一个 niceapp/index.html。但是如何扩展原始模板并仅覆盖其中一些块,而不是复制整个模板?使用 {% extend "niceapp/index.html" %} 显然无法完成任务。 - akaihola
@akaihola:你说得对,我误读了问题。已更新。 - Paulo Scardine

-3

一个简单的方法是这样的:

假设你想要扩展和覆盖django/contrib/admin/templates/admin/change_form.html。

首先,将原始的change_form.html复制到你的应用程序模板目录中,并将其重命名为类似于myapp/templates/admin/original_change_form.html的名称。(你也可以使用符号链接来完成此操作。)

第二步,在myapp/templates/admin中创建你自己的change_form.html。在顶部放置以下内容:

{% extends "admin/original_change_form.html" %}

简单!


这其实不是一个坏的解决方案,特别是如果你的系统支持符号链接。 - Flimm

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