【问题标题】:How to make a Template extend another one?如何使模板扩展另一个模板?
【发布时间】:2010-07-09 03:56:41
【问题描述】:

我有一些模板,这些模板是根据我存储在数据库中的一些数据构建的:

my_template = Template(string_data)

现在我希望该模板扩展另一个模板(也作为文本存储在数据库中)。我怎样才能做到这一点?寻找类似的东西:

my_template.base = other_template

或任何语法。找不到这方面的文档。


我在源代码中看到了template.nodelist;我可以预先添加某种扩展节点吗?


添加了赏金...将授予第一个可以告诉我如何在不破解 django 核心的情况下做到这一点的人。

【问题讨论】:

    标签: django django-templates


    【解决方案1】:

    无需破解 Django 的核心。按照 Ignacio 的建议,创建自己的模板加载器非常简单。您只需要继承django.template.loader.BaseLoader,并实现load_template_source 方法。这只需要一个字符串作为模板名称,您可以简单地使用它来查找数据库中的项目并返回字符串。

    在您的情况下,由于您在单个 db 行中有两个模板元素,您可能需要做一些事情以在 extends 标记中指定它:

    {% extends "myinstance.html_version" %}
    

    然后简单地将其拆分为load_template_source:

    def load_template_source(self, template_name, template_dirs=None):
        name, field = template_name.split('.')
        tpl = TemplateModel.objects.get(name=name)
        return getattr(tpl, field)
    

    当然,您希望在其中进行一些错误处理,但您明白了。然后,您只需在设置中指定此模板加载器TEMPLATE_LOADERS tuple。

    评论后编辑我还是不太明白为什么,但是如果你想做的只是选择动态扩展的模板,为什么不在处理之前将它插入到文本模板中?

        tpl_string = get_template_from_wherever()
        extender = '{% extends "%s" %}' % template_name_to_extend
        tpl = Template(extender + tpl_string)
    

    仍然是一个 hack,但可能远不如对模板继承的内部进行处理。

    【讨论】:

    • 是的..但问题是,我不想在我的模板中写任何{% extends ... %} 的东西。我有一个包含所有模板的下拉/选择菜单,因此您只需从 Django-admin 中选择它,然后我想在发送电子邮件之前插入它。因此,我想用 Python 代码而不是模板代码来做。
    • 很公平......克雷格说服我接受你的回答。
    【解决方案2】:

    要重申您的问题,您希望:

    1. 获取您已经在字符串中获取的文本(因为您是从数据库加载的),
    2. 将您要加载到字符串中的更多文本(因为您知道从哪里加载),
    3. 用第二个字符串制作父模板,
    4. 从第一个字符串中创建一个子模板,
    5. 将子模板的父模板(从第一个字符串)设置为父模板(从第二个字符串),
    6. 评估子模板。
    7. 最重要的是,您希望避免在子模板中包含 {% extends ...%} 之类的内容,因为...为什么?
    8. 最后,您希望在不破解 Django 核心的情况下做到这一点。

    简答:

    不可能。开箱即用,Django 不会按照您的要求进行操作。

    长答案:

    Django中模板继承的整个概念是通过extends标签实现的。更准确地说,它是通过 django.template.loader_tags 模块的 ExtendsNode 类实现的,该类是在解析 extends 标签时创建的。当您创建 Template() 时,它会解析其源字符串,并创建节点列表(存储在模板的节点列表中,如前所述)。以后,您可以使用任何您喜欢的上下文来渲染模板,并且可以多次渲染。

    大致上,渲染通过在节点列表中的每个节点上调用 render() 来工作。如果模板节点列表中的第一个节点是 ExtendsNode(并且它必须是第一个节点),那么模板继承魔法就会发生。当ExtendsNode被创建时,它被赋予模板的节点列表,和一个父名字(可以是字符串(parent_name)或表达式(parent_name_expr)。当ExtendsNode被渲染时,它会使用它的get_parent()方法来调用get_template(parent_name) ),它将调用模板加载器机制来加载父模板。一旦有了父模板,ExtendsNode::render() 方法就会发挥模板继承的魔力。

    随时查看code yourself。

    为了避免使用模板加载器,您必须执行以下操作:

    1. 创建一个类 SpecialExtendsNode(ExtendsNode),覆盖 __init__ 方法和 get_parent 方法。
    2. 从子字符串创建模板后,创建子类的实例,从父模板初始化。
    3. 将您的 SpecialExtendsNode 实例插入子模板节点列表的头部。
    4. 祈祷这一切都不会被 Django 开发人员改变。

    只是为了方便您,这是您的课程:

    class SpecialExtendsNode(ExtendsNode):
    
        def __init__( self, nodelist, parent, name ):
            self.myparent = parent
            ExtendsNode.__init__( self, nodelist, name, None, None )
    
        def get_parent( self ):
            return self.myparent
    

    使用它的代码如下所示:

    parent = Template( parent_string )
    child = Template( child_string )
    hack = SpecialExtendsNode( child.nodelist, parent, "parent name" )
    child.nodelist.insert( 0, hack )
    output = child.render( context )
    

    既然我已经花时间和精力给你一把枪并装满子弹,我将试图说服你不要扣动扳机,而是按照其他人推荐的方式做事。

    我在这里编写的代码没有错误检查,并且是针对 Django 1.2 编写的。虽然我还没有测试过,但我 99% 确信它可以在 Django 1.2 上运行。我不知道它是否适用于任何其他版本的 Django。另一方面,除了提供源代码之外,Django 开发人员还没有记录他们的模板处理器的内部结构,除了提供用于编写​​新模板标签和过滤器的文档化接口,以及用于编写模板加载器的文档化接口(特别提到从数据库加载模板的用例)。这意味着有一个合理的案例,有一天 Django 开发人员可能会选择重写或大量修改模板扩展机制。如果他们这样做,我将 100% 保证这段代码会被破坏。为什么?因为它是一个黑客。这就是破解 Django 核心的样子。它一次可以工作一天、一周甚至几个月,但迟早,你(或你之后的程序员)将从 Django 1.2 升级到更高版本,然后它就会崩溃。而且当它坏了,我不会在那里帮助你,Django 开发人员都会问,你为什么不写一个模板加载器?

    现在告诉我,这涉及到更多破解 Django 的核心——编写一个模板加载器,由开发人员记录和支持,还是我刚才描述的?

    【讨论】:

    • 如果我编写一个自定义模板加载器,大概我必须将它添加到设置文件中的模板加载器列表中,是吗?如果我最后添加它,那么大概它会被用作最后的手段,是吗?然后,当我一开始就知道它不存在时,每次“失败”的查找(在以前的加载器中找不到模板)都会导致数据库命中,因为这不是我打算使用它的方式。此外,我的电子邮件在查询数据库之前必须通过所有其他模板加载器,即使我知道它在数据库中的确切位置......“搜索”某些东西似乎真的很脏......跨度>
    • ...当您已经知道它在哪里时。据推测,这只是一个小的性能损失,但完全不需要一个 IMO。不过,明智的做法是让我的应用程序面向未来并暂时搁置我对性能问题的疑虑。但我仍然认为这两种解决方案充其量都是同样肮脏的。
    • 这是一种权衡,但根据我的经验,数据库会缓存查询结果,而对于小表,整个表会缓存在内存中,从而导致快速查找/失败。如果您真的很担心,您可以随时将 memcached 加入其中……但我实际上建议您在做任何事情之前测量性能损失。
    • 如果我要编写一个模板加载器,我会让它匹配一个像'db:name'这样的模式——如果'db:'前缀不存在,自动失败。如果 'db:' 存在,将其剥离并使用其余部分作为数据库键。这使您可以快速失败,甚至无需接触数据库。然后,我会将模板加载器 first 放在列表中。
    【解决方案3】:

    不幸的是,您需要编写自己的template loader 才能读取数据库。 {% extends %} 模板标签通常用于此目的,但它将实际加载模板的任务推迟到加载器。

    【讨论】:

    • 这正是我不想明确使用{% extends %} 标签的原因。我知道它只会扫描模板目录,除非我在那里添加我自己的黑客魔法,但必须有一种方法可以用我选择的模板注入ExtendsNode,而不是求助于加载程序?我会自己加载!
    • 另外,我不确定这是否可行,因为我实际上有 两个 模板存储在一个数据库条目中(电子邮件的 HTML 和文本版本) .所以....它怎么知道要扩展哪一个?好吧……我想我必须要有真正的创造力……但是啊。太丑了。
    【解决方案4】:

    我认为您需要继承 django.template.loader_tags.ExtendNode 并覆盖其 get_parent 方法,以便您可以在其中使用自己的 get_template!然后您应该可以将ExtendNode 添加到您的template.nodelist,但也要注意它必须是第一个!

    【讨论】:

    • 讨厌...不敢相信这是我必须这样做的方式。哦,好吧...我会在几秒钟内详细介绍。
    • 嗯,不知道这是否是唯一的方法,但我想它应该可以工作!
    • 好的...所以看起来覆盖get_parent 应该很容易。我只是返回我的模板,但是我到底如何初始化该死的节点?输入args这么多,不知道从哪里来的!
    【解决方案5】:
    from app.models.misc import EmailTemplate
    from django.template import TemplateDoesNotExist
    from django.template.loader import BaseLoader
    
    class Loader(BaseLoader):
        is_usable = True
        def load_template_source(self, template_name, template_dirs=None):
            try:
                key, ext = template_name.split('.')
                attr = {
                    'html': 'html_content',
                    'txt': 'text_content',
                }[ext]
                tpl = EmailTemplate.objects.get(key=key)
                return (getattr(tpl, attr), repr(tpl))
            except:
                raise TemplateDoesNotExist
    

    当你找不到更新的文档时真的很难写:\

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-22
      • 2012-06-14
      相关资源
      最近更新 更多