视图中的模板

作者: 大爷的二舅 | 来源:发表于2018-01-29 22:55 被阅读18次

    您已经学习了使用模板系统的基础知识; 现在让我们用这个知识来创建一个视图。

    回想一下mysite.views中的current_datetime视图,这是我们在前一章中开始的。 以下是它的样子:

    from django.http import HttpResponse
    import datetime
    
    def current_datetime(request):
        now = datetime.datetime.now()
        html = "<html><body>It is now %s</body></html>" % now
        return HttpResponse(html)
    

    让我们改变这个视图来使用Django的模板系统。 起初,你可能会想这样做:

    from django.template import Template, Context
    from django.http import HttpResponse
    import datetime
    
    def current_datetime(request):
        now = datetime.datetime.now()
        t = Template("<html><body>It is now {{ current_date }}.</body></html>")
        html = t.render(Context({'current_date': now}))
        return HttpResponse(html)
    

    当然,这是使用模板系统,但是它并不能解决我们在本章介绍中指出的问题。 也就是说,模板仍然嵌入在Python代码中,所以没有实现数据和表示的真正分离。 让我们通过把模板放在一个单独的文件中来解决这个问题。

    您可能首先考虑将模板保存在文件系统的某个位置,并使用Python的内置文件打开功能来读取模板的内容。 假设模板保存为\users\djangouser\templates\mytemplate.html文件:

    from django.template import Template, Context
    from django.http import HttpResponse
    import datetime
    
    def current_datetime(request):
    
      now = datetime.datetime.now()
      # Simple way of using templates from the filesystem.
      # This is BAD because it doesn't account for missing files!
      fp = open('\users\djangouser\templates\mytemplate.html')
      t = Template(fp.read())
      fp.close()
    
      html = t.render(Context({'current_date': now}))
      return HttpResponse(html)
    

    然而,这种方法由于以下原因而不够优雅:

    1. 它不处理丢失文件的情况。 如果文件mytemplate.html不存在或不可读,则open()调用将引发IOError异常。

    2. 它硬编码您的模板位置。 如果您对每个视图函数都使用这种技术,则会复制模板位置。 更何况这涉及到很多打字!

    3. 它包含了很多枯燥的样板代码。 每次加载模板时,你都有更好的事情要做,而不是写调用open(),fp.read()和fp.close()。

    为了解决这些问题,我们将使用模板加载和模板目录。

    模板加载

    Django提供了一个方便而强大的API来从文件系统加载模板,目的是在模板加载调用和模板本身中删除冗余。 为了使用这个模板加载API,首先你需要告诉框架存储模板的地方。 做这个的地方在你的设置文件中 - 我上一章提到的settings.py文件,当我介绍ROOT_URLCONF设置时。 如果你跟着,打开你的settings.py并找到模板设置。 这是一个配置列表,每个引擎一个:

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [],
            'APP_DIRS': True,
            'OPTIONS': {
                # ... some options here ...
            },
        },
    ]
    

    BACKEND是实现Django模板后端API的模板引擎类的虚线Python路径。 内置的后端是django.template.backends.django.DjangoTemplates和django.template.backends.jinja2.Jinja2。

    由于大多数引擎从文件加载模板,因此每个引擎的顶级配置包含三个常用设置:

    • DIRS根据搜索顺序定义引擎应该查找模板源文件的目录列表。
    • APP_DIRS告诉引擎是否应该在已安装的应用程序中查找模板。 按照惯例,当APPS_DIRS设置为True时,DjangoTemplates将在每个INSTALLED_APPS中查找\ templates子目录。 这允许模板引擎即使DIRS为空也能找到应用程序模板。
    • OPTIONS包含后端特定的设置。
    模板目录

    DIRS默认情况下是一个空的列表。 为了告诉Django的模板加载机制在哪里寻找模板,选择一个你想存储你的模板的目录,并将其添加到DIRS,如下所示:

    'DIRS': [
               '/home/html/templates/site',
               '/home/html/templates/default',
           ],
    

    有几件事要注意:

    • 除非你正在构建一个没有应用程序的简单程序,否则最好把DIRS留空。 默认设置文件将APP_DIRS配置为True,所以你最好在你的Django应用程序中有一个“templates”子目录。

    • 如果您想在项目根目录下拥有一组主模板,例如 mysite \ templates,你需要设置DIRS,如下所示:

      'DIRS': [os.path.join(BASE_DIR, 'templates')],
    
    • 您的模板目录不必被称为模板 - Django不会对您使用的名称进行任何限制 - 但是如果您坚持使用惯例,它会使您的项目结构更容易理解。如果您不想使用 默认的,或不能由于某种原因,你可以指定你想要的任何目录,只要该目录中的目录和模板可以被运行你的Web服务器的用户帐户读取。

    • 如果您在Windows上,请包含您的驱动器盘符,并使用Unix风格的正斜杠而非反斜杠,如下所示:

      'DIRS':
      [
      'C:/www/django/templates',
      ]
    

    由于我们还没有创建一个Django应用程序,我将使用一个非常简单的配置来演示如何加载模板。

    首先,你必须按照上面的例子设置DIRS到[os.path.join(BASE_DIR,'templates')]。 你的设置文件现在应该是这样的:

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR,'templates')],
            'APP_DIRS': True,
            'OPTIONS': {
                # ...
    

    设置DIRS后,接下来要在root \ mysite文件夹中创建一个\ templates目录。 完成后,文件夹结构应如下所示:

    \mysite_project
        \mysite
            \mysite
            \templates
            manage.py
    

    下一步是将视图代码更改为使用Django的模板加载功能,而不是对模板路径进行硬编码。 回到我们的current_datetime视图,让我们改变它:

    #mysite\mysite\views.py
    
    from django.template.loader import get_template
    from django.http import HttpResponse
    import datetime
    
    def current_datetime(request):
        now = datetime.datetime.now()
        t = get_template('current_datetime.html')
        html = t.render({'current_date': now})
        return HttpResponse(html)
    

    通常,您将使用Django模板加载器,而不是像前面的示例中那样使用低级模板API。 在这个例子中,我们使用函数django.template.loader.get_template()而不是手动从文件系统加载模板。 get_template()函数将模板名称作为其参数,计算模板在文件系统上的位置,打开该文件并返回编译后的Template对象。

    还要注意这里的render()方法和前面的例子之间的区别。 在前面的例子中,我们调用了django.template.Template.render,它要求你传入一个Context对象。 get_template()从django.template.backends.base.Template返回一个依赖于后端的Template,其中render()方法只接受一个字典对象,而不接受一个Context对象。

    我们在这个例子中的模板是current_datetime.html,但没有什么特别的.html扩展名。 你可以给你的模板任何扩展名对你的应用程序有意义,或者你可以完全忽略扩展名。

    要确定文件系统上模板的位置,get_template()将按顺序查找:

    • 如果APP_DIRS设置为True,并且假设您正在使用DTL,则会在当前应用程序中查找/ templates目录。

    • 如果它没有在当前应用程序中找到您的模板,get_template()会将您的DIRS模板目录与传递给get_template()的模板名称相结合,并依次遍历每个模板直到找到您的模板。 例如,如果DIRS中的第一个条目设置为'/ home / django / mysite / templates',则上面的get_template()调用将查找模板/home/django/mysite/templates/current_datetime.html。

    • 如果get_template()无法找到具有给定名称的模板,则会引发TemplateDoesNotExist异常。

    Windows用户

    一定要使用正斜杠而不是反斜杠。 get_template()假定一个Unix风格的文件名称。

    要查看模板异常的样子,请在Django项目的目录中运行python manage.py runserver,再次启动Django开发服务器。 然后,将浏览器指向激活current_datetime视图的页面(例如http://127.0.0.1:8000/time/)。 假设你的DEBUG设置被设置为True,并且你还没有创建一个current_datetime.html模板,你应该看到一个Django错误页面,突出显示了TemplateDoesNotExist错误(图3-1)。

    图3-1:缺少模板错误页面.png
    这个错误页面与我在第2章中解释的错误页面类似,另外还有一个调试信息:“Template-loader postmortem”部分。 本节将告诉您Django尝试加载哪些模板以及每次尝试失败的原因(例如,“文件不存在”)。 当您尝试调试模板加载错误时,此信息是无价的。

    要解决我们缺少的模板问题,我们使用以下模板代码创建current_datetime.html文件:

    # \mysite_project\mysite\templates\current_datetime.html
    
    <html>
        <body>
            It is now {{ current_date }}
        </body>
    </html>
    It is now {{ current_date }}.
    

    将此文件保存到您以前创建的\ mysite \ templates目录中。 刷新网页浏览器中的页面,你会看到完全呈现的页面(图3-2)。


    图3-2:Django现在可以正确地呈现模板.png
    render()

    到目前为止,我已经向您展示了如何加载一个模板,填充一个Context,并返回一个HttpResponse对象和渲染模板的结果。 下一步是优化它使用get_template()而不是硬编码模板和模板路径。

    Django的开发人员认识到,因为这是一个常见的习惯用法,所以Django需要一个可以在一行代码中完成所有这些工作的捷径。 这个快捷方式是一个名为render()的函数,它位于模块django.shortcuts中。

    大多数情况下,您将使用render()而不是加载模板,并手动创建Context和HttpResponse对象 - 但是,记住完整的流程是有用的,因为它适用于某些非标准的用例。

    以下是正在进行的current_datetime示例,重写为使用render():

    from django.shortcuts import render
    import datetime
    
    def current_datetime(request):
        now = datetime.datetime.now()
        return render(request, 'current_datetime.html', {'current_date': now})
    

    有什么区别! 我们来看看代码上的变化:

    • 我们不再需要导入get_template,Template,Context或HttpResponse。 相反,我们导入django.shortcuts.render。 导入日期时间保持不变。

    • 在current_datetime函数中,我们现在仍在计算,但是模板加载,上下文创建,模板渲染和HttpResponse创建都由render()调用来处理。 因为render()返回一个HttpResponse对象,所以我们可以直接在视图中返回这个值。

    render()的第一个参数是请求,第二个参数是要使用的模板的名称。 第三个参数(如果给出的话)应该是一个字典,用来为该模板创建一个Context。 如果你不提供第三个参数,render()将使用一个空的字典。

    模板子目录

    将所有模板存储在单个目录中可能会很困难。 您可能希望将模板存储在模板目录的子目录中,这很好。

    实际上,我建议这样做; 一些更高级的Django特性(比如我们在第10章介绍的通用视图系统)将这种模板布局视为默认的约定。 它还为您的模板提供了自己的命名空间,在我们开始构建Django应用程序时,我们将在本书的后面探讨它的用途。

    将模板存储在模板目录的子目录中很容易。 在对get_template()的调用中,只需在模板名称之前包含子目录名称和斜线,如下所示:

    t = get_template('dateapp/current_datetime.html')
    

    因为render()是get_template()的一个小包装,所以你可以用render()的第二个参数做同样的事情,就像这样:

    return render(request, 'dateapp/current_datetime.html', {'current_date': now})
    

    子目录树的深度没有限制。 随意使用尽可能多的子目录,只要你喜欢。 与本章中的所有其他方法一样,Windows用户必须记住在路径名中使用正斜杠(/),而不是反斜杠(\)。

    包含模板标签

    现在我们已经介绍了模板加载机制,我们可以引入一个内置的模板标签来利用它:{%include%}。

    这个标签允许你包含另一个模板的内容。 标签的参数应该是要包含的模板的名称,模板名称可以是变量,也可以是硬编码(带引号)的字符串,可以是单引号或双引号。

    只要您在多个模板中使用相同的代码,请考虑使用{%include%}来删除重复项。 这两个示例包括模板nav.html的内容。 这些例子是等价的,说明可以使用单引号或双引号:

    {% include 'nav.html' %}
    {% include "nav.html" %}
    

    此示例包含模板includes / nav.html的内容:

    {% include 'includes/nav.html' %}
    

    此示例包含名称包含在变量template_name中的模板的内容:

    {% include template_name %}
    

    {%include%}标签也允许相对路径:

    {% include './nav.html' %}
    {% include '../nav_base.html' %}
    

    与get_template()中一样,模板的文件名是通过将当前Django应用程序中的\ templates目录的路径(如果APPS_DIR为True)或通过将DIRS中的模板目录添加到请求的模板名称来确定的。 包含的模板将使用包含它们的模板的上下文进行评估。

    例如,考虑这两个模板:

    # mypage.html
    
    <html>
    <body>
        {% include "includes/nav.html" %}
        <h1>{{ title }}</h1>
    </body>
    </html>
    
    # includes/nav.html
    
            You are in: {{ current_section }}
    

    如果使用包含current_section的上下文呈现mypage.html,则该变量将在“included”模板中可用,如您所期望的那样。

    如果在{%include%}标记中找不到具有给定名称的模板,Django将执行以下两项操作之一:

    • 如果DEBUG设置为True,你会在Django错误页面上看到TemplateDoesNotExist异常。

    • 如果DEBUG设置为False,则标签将自动失败,在标签处没有显示任何内容。

    包含的模板之间没有共享状态 - 每个包含都是完全独立的呈现过程。 在包含块之前对它们进行评估。 这意味着包含另一个块的模板将包含已经被评估和渲染的块 - 而不是可以被例如扩展模板覆盖的块。

    模板的继承

    到目前为止,我们的模板示例都是微小的HTML片段,但在现实世界中,您将使用Django的模板系统来创建整个HTML页面。这导致了一个共同的Web开发问题:在一个网站上,如何减少常见页面区域的重复和冗余,如全站导航?

    解决这个问题的一个经典的方法是使用服务器端包含,你可以在HTML页面中嵌入指令来将一个网页“包含”在另一个网页中。事实上,Django支持这种方法,刚刚描述了{%include%}模板标记。

    但是用Django解决这个问题的首选方法是使用一种更优雅的策略,即模板继承。实质上,模板继承允许您构建一个基本“骨架”模板,其中包含站点的所有常见部分,并定义子模板可以覆盖的“块”。让我们通过编辑current_datetime.html文件,为current_datetime视图创建一个更完整的模板来看一个例子:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
    
    <html lang="en">
    <head>
        <title>The current time</title>
    </head>
    <body>
        <h1>My helpful timestamp site</h1>
        <p>It is now {{ current_date }}.</p>
        <hr>
        <p>Thanks for visiting my site.</p>
    </body>
    </html>
    

    这看起来很好,但是当我们想为另一个视图创建一个模板时会发生什么 - 比如说第二章中的hours_ahead视图? 如果我们想再次制作一个很好的,有效的,完整的HTML模板,我们会创建如下所示的内容:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
    <html lang="en">
    <head>
        <title>Future time</title>
    </head>
    <body>
        <h1>My helpful timestamp site</h1>
        <p>
            In {{ hour_offset }} hour(s), it will be {{ next_time }}.
        </p>
        <hr>
        <p>Thanks for visiting my site.</p>
    </body>
    </html>
    

    显然,我们刚刚复制了大量的HTML。 想象一下,如果我们有一个更典型的网站,包括一个导航栏,几个样式表,也许还有一些JavaScript - 我们最终将各种冗余HTML放入每个模板。

    服务器端解决这个问题的方法是将两个模板中的常见比特分解出来,并将它们保存在单独的模板片段中,然后将其包含在每个模板中。 也许你会将模板的最高位保存在一个名为header.html的文件中:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
    <html lang="en">
    <head>
    

    也许你会把最低位保存在一个名为footer.html的文件中:

        <hr>
        <p>Thanks for visiting my site.</p>
        </body>
    </html>
    

    采用基于包含的策略,页眉和页脚很容易。 这是混乱的中间地带。 在这个例子中,两个页面都有一个标题 - “我的有用的时间戳站点” - 但是这个标题不适合header.html,因为这两个页面的标题是不同的。 如果我们在标题中包含<h1>,我们必须包含标题,这不允许我们在每个页面上定制它。

    Django的模板继承系统解决了这些问题。 您可以将其视为服务器端包含的“内外”版本。 不是定义常见的片段,而是定义不同的片段。

    第一步是定义一个基本模板 - 子模板稍后将填充的页面骨架。下面是我们正在进行的示例的基本模板:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
    <html lang="en">
    <head>
        <title>{% block title %}{% endblock %}</title>
    </head>
    <body>
        <h1>My helpful timestamp site</h1>
        {% block content %}{% endblock %}
    
        {% block footer %}
        <hr>
        <p>Thanks for visiting my site.</p>
        {% endblock %}
    </body>
    </html>
    

    我们将这个磨板定义为base.html,定义了一个简单的HTML骨架文档,我们将把这个磨板使用到该网站的所有页面。

    这是儿童模板的工作,可以覆盖或添加或单独保留块的内容。 (如果你正在跟着,把这个文件保存到你的模板目录中,如base.html。)

    我们在这里使用了一个您以前从未见过的模板标签:{%block%}标签。 所有的{%block%}标签都会告诉模板引擎一个子模板可能会覆盖模板的那些部分。

    现在我们有了这个基础模板,我们可以修改我们现有的current_datetime.html模板来使用它:

    {% extends "base.html" %}
    {% block title %}The current time{% endblock %}
    {% block content %}
        <p>It is now {{ current_date }}.</p>
    {% endblock %}
    

    我们来看第3章的hours_ahead视图。(如果你跟着代码,我会留下来让你改变hours_ahead来使用模板系统而不是硬编码 HTML。)以下是可能的样子:

    {% extends "base.html" %}
    {% block title %}Future time{% endblock %}
    {% block content %}
    <p>
      In {{ hour_offset }} hour(s), it will be {{ next_time }}.
    </p>
    {% endblock %}
    

    这不美吗?每个模板只包含该模板特有的代码。不需要冗余。如果您需要进行网站范围内的设计更改,只需对base.html进行更改,其他所有模板都会立即反映更改。

    这是如何工作的。当您加载模板current_datetime.html时,模板引擎会看到{%extends%}标记,注意这个模板是一个子模板。引擎立即加载父模板 - 在这种情况下,base.html。

    此时,模板引擎会注意到base.html中的三个{%block%}标记,并将这些块替换为子模板的内容。因此,我们在{%block title%}中定义的标题将被使用,{%block content%}也将被使用。

    请注意,由于子模板未定义页脚块,因此模板系统将使用父模板中的值。父模板中{%block%}标记中的内容始终用作回退。

    继承不会影响模板上下文。换句话说,继承树中的任何模板都可以访问上下文中的每一个模板变量。您可以根据需要使用尽可能多的继承级别。使用继承的一个常见方法是以下三层方法:

    1. 创建一个base.html模板,该模板包含您网站的主要外观。 这是很少有变化的东西。

    2. 为您的网站的每个“部分”(例如,base_photos.html和base_forum.html)创建一个base_SECTION.html模板。 这些模板扩展了base.html并包含了特定部分的样式/设计。

    3. 为每种类型的页面创建单独的模板,例如论坛页面或照片库。 这些模板扩展了适当的部分模板。

    此方法最大限度地实现了代码重用,并使项目可以轻松地添加到共享区域,例如部分区域导航。

    以下是使用模板继承的一些准则:

    • 如果您在模板中使用{%extends%},那么它必须是该模板中的第一个。
      否则,模板继承将不起作用。

    • 通常,基本模板中的{%block%}标记越多越好。请记住,子模板不必定义所有父块,因此可以用合理的默认值填充合理数量的块,并仅在子模板中定义所需的值。有更多的钩子比更少的钩子更好。

    • 如果您发现自己从大量模板中复制代码,则可能意味着您应该将代码移动到父模板中的{%block%}。

    • 如果您需要从父模板获取块的内容,请使用{{block.super}},这是一个“魔术”变量,用于提供父模板的渲染文本。如果要添加父块的内容而不是完全覆盖它,这非常有用。

    • 您无法在同一模板中定义多个具有相同名称的{%block%}标记。这个限制的存在是因为块标签在“两个”方向上工作。也就是说,块标记不仅提供了一个要填充的孔,还定义了填充父项的孔的内容。如果模板中有两个具有相似名称的{%block%}标记,则模板的父级不知道要使用哪个块的内容。

    • 传递给{%extends%}的模板名称使用get_template()所使用的相同方法加载。也就是说,模板名称会附加到您的DIRS设置或您当前的Django应用程序的\ templates文件夹中。

    • 在大多数情况下,{%extends%}的参数将是一个字符串,但如果您在运行时不知道父模板的名称,它也可能是一个变量。这可以让你做一些很酷和动态的事情。

    下一步是什么?

    你现在已经掌握了Django模板系统的基础知识。 下一步是什么? 大多数现代网站都是数据库驱动的:网站的内容存储在关系数据库中。 这允许数据和逻辑的清晰分离(与视图和模板相同,可以分离逻辑和显示)。下一章介绍了Django为您提供的与数据库交互的工具。

    相关文章

      网友评论

        本文标题:视图中的模板

        本文链接:https://www.haomeiwen.com/subject/xcrtzxtx.html