python app-engine django 1.2 渲染相对路径时出现TemplateDoesNotExist错误

11 投票
2 回答
5746 浏览
提问于 2025-04-16 13:27

我在一台Windows电脑上本地运行1.4.2版本的appengine SDK。我有一个应用程序在使用Django 0.96。模板渲染是通过使用django的包装器来完成的。

google.appengine.ext.webapp.template.render

我通常使用相对路径来链接我的模板,比如说:

{% extends "../templates/base.html" %}

但是在我升级到Django 1.2后,appengine的Django 1.2库文件夹中的find_template方法在使用相对路径时,开始出现了TemplateDoesNotExist的错误。

for loader in template_source_loaders:
    try:
        #raises TemplateDoesNotExist name='../templates/home.html' dirs=None
        source, display_name = loader(name, dirs)
        return (source, make_origin(display_name, loader, name, dirs))
    except TemplateDoesNotExist:
        pass
raise TemplateDoesNotExist(name)

我已经在Django和AppEngine的代码中调试了一段时间,但还是找不到原因。有没有人能提供一些更多的见解?

谢谢,

理查德

2 个回答

-2

你看到错误信息TemplateDoesNotExist: templates\AdminListstr.html的一个原因是

请仔细检查一下“templates”和你的模板名称之间的斜杠。 我浪费了几个小时,最后才检查到os.path.join返回的值。

总之,你需要确保使用的是“/”,而不是Windows系统的“\”。 如果你用错了斜杠,代码在Windows上运行没问题,但一旦部署到其他地方就会出错。

我的诊断代码打印了这个:看看最后的斜杠
而os.path.join返回的值是:/base/data/home/apps/s~myapp/1.356037954289984934/templates\LabListstr.html

哎呀呀!

15

我在把Django模板从0.96升级到1.2的时候也遇到了这个问题。最开始我被迫升级,因为SDK 1.4.2开始发出警告,提醒我需要选择一个版本。不过,当我看到模板语言的那些必要改进时,我就迫不及待想要进行升级。

结果一切都崩溃了。像你一样,我在extendsinclude命令中用了很多相对路径。经过大量的调试和研究,我终于找到了问题的原因以及一个不错的解决方案。

问题的原因是:在Django 1.2中,加载模板文件的代码开始使用一个叫safe_join的命令来连接路径部分(你可以在google_appengine\lib\django_1_2\django\template\loaders\filesystem.py中看到这段代码)。这个命令不允许相对路径超出它认为的顶级目录。这就像一个网络服务器被配置成防止你通过在URL中加一些..来访问整个服务器的文件系统。最终结果就是,

{% extends "../templates/base.html" %}

以前没问题的路径现在违反了规则,无法正常工作。

我在我的应用中解决这个问题的方法是实现一个自定义的TemplateLoader。Django的模板渲染引擎允许应用使用多种不同的类来以不同的方式查找模板。如果你查看我上面提到的目录,你会看到有几个提供的类,它们都是从BaseLoader继承而来的。我提供了一个专门为我的模板布局量身定制的类。

我的项目有点像Rails的布局:

app/
   controllers/
      home_controller.py
      posts_controller.py
   models/
      ...
   views/
      home/
          index.html
          about.html
      posts/
          show.html
          new.html
      shared/
          base.html
          post.html

每个模板都扩展了base.html,还有几个包含了post.html,它们之前使用相对路径来访问base/中的位置。理想情况下,我甚至不想使用..这个上级目录来访问,但在0.96版本中这是必须的。我创建了以下模板加载器来配合我的方案:

from django.conf import settings
from django.template import TemplateDoesNotExist
from django.template.loader import BaseLoader
from django.utils._os import safe_join
import os

class MvcTemplateLoader(BaseLoader):
    "A custom template loader for the MVCEngine framework."

    is_usable = True

    __view_paths = None

    def __init__(self, views_path):
        self.views_path = views_path
        # We only need to instantiate the view_paths class variable once.
        if MvcTemplateLoader.__view_paths is None:
            temp_paths = []
            for each_path in os.listdir(views_path):
                # We want to skip hidden directories, so avoid anything that starts with .
                # This works on both Windows and *NIX, but could it fail for other OS's?
                if not each_path.startswith('.'):
                    full_path = os.path.join(views_path, each_path)
                    if each_path == "shared":
                        # The shared directory is special. Since templates in many other directories will be
                        # inheriting from or including templates there, it should come second, right after the
                        # root views directory. For now, it will be first.
                        temp_paths.insert(0, full_path)
                    else:
                        temp_paths.append(full_path)
            # The root views_path itself will always be first in order to give resolution precendence to templates
            # that are specified with a parent directory. In other words, home/index.html will be immediately
            # resolved with no ambiguity; whereas, index.html could resolve as bar/index.html rather than
            # foo/index.html.
            temp_paths.insert(0, views_path)
            MvcTemplateLoader.__view_paths = temp_paths


    def get_template_sources(self, template_name):
        for template_dir in MvcTemplateLoader.__view_paths:
            try:
                yield safe_join(template_dir, template_name)
            except UnicodeDecodeError:
                # The template dir name was a bytestring that wasn't valid UTF-8.
                raise
            except ValueError:
                # The joined path was located outside of this particular
                # template_dir (it might be inside another one, so this isn't
                # fatal).
                pass

    def load_template_source(self, template_name, template_dirs=None):
        tried = []
        for filepath in self.get_template_sources(template_name):
            try:
                file = open(filepath)
                try:
                    return (file.read().decode(settings.FILE_CHARSET), filepath)
                finally:
                    file.close()
            except IOError:
                tried.append(filepath)

        error_msg = "Could not find %s in any of the views subdirectories." % template_name
        raise TemplateDoesNotExist(error_msg)
    load_template_source.is_usable = True

_loader = MvcTemplateLoader

然后我通过修改应用的main函数,将我的自定义模板加载器包含在Django尝试的集合中,代码如下:

def main():    
    from google.appengine.dist import use_library
    use_library('django', '1.2')

    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

    from django.conf import settings 
    views_path = os.path.join(os.path.dirname(__file__), 'app','views')
    settings.TEMPLATE_LOADERS = (('gaemvclib.mvctemplateloader.MvcTemplateLoader', views_path), 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader')

(然后是通常放入你的主函数中的其他内容)。

所以,我认为你应该能够修改上面的TemplateLoader代码,以匹配你模板目录的布局,这样你就可以更好地控制模板的层次结构,以及如何编写extendsinclude语句。你不再需要使用..,而是直接给出模板相对于你项目中相当于我views目录的路径。

撰写回答