TemplateDoesNotExist 在 python app-engine django 1.2 上模板渲染相对路径

发布于 2024-10-21 18:01:54 字数 830 浏览 1 评论 0原文

我在 Windows 计算机上本地运行 1.4.2 appengine SDK。我有一个运行 Django 0.96 的应用程序。模板渲染使用 django 包装器来

google.appengine.ext.webapp.template.render

渲染模板。我经常使用相对路径来链接我的模板,例如

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

升级到 Django 1.2 后,find_template 方法从 当使用相对路径时,appengine 的 Django 1.2 lib 文件夹中的 django.template.loader 现在会引发 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 代码一段时间了,但看不到任何原因。谁能提供更多见解?

谢谢,

理查德

I'm running the 1.4.2 appengine SDK locally on a windows machine. I have an application running Django 0.96. The template rendering is using the django wrapper from

google.appengine.ext.webapp.template.render

to render templates. I often use a relative path to link my templates e.g

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

After upgrading to Django 1.2 the find_template method from
django.template.loader in the appengine's Django 1.2 lib folder is now raising a TemplateDoesNotExist when the relative paths are used

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)

I've been stepping through the Django and AppEngine code for a while now but can't see any reason for this. Can anyone provide any more insight?

Thanks,

Richard

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

酷炫老祖宗 2024-10-28 18:01:54

当我从 0.96 转换为 1.2 Django 模板时,这个问题也困扰了我。当 SDK 1.4.2 开始发出我需要选择版本的警告时,我最初被迫这样做,但当我研究模板语言中急需的改进时,我渴望做出改变。

然后一切都崩溃了。和您一样,我在 extendsinclude 命令中使用了很多相对路径。经过大量的调试和挖掘,但我确实找出了问题的原因并找到了一个很好的解决方案。

原因:在 Django 1.2 中,加载模板文件的代码开始使用名为 safe_join 的命令来连接路径部分(您可以在 google_appengine\lib\django_1_2\django\template\ 中查看代码) loader\filesystem.py) 。它不允许相对路径超出它认为的顶级目录。这与 Web 服务器被配置为阻止您仅通过将一些 .. 粘贴到您的 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/中。理想情况下,我什至不想使用 .. up-dir 到达那里,但 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 代码以匹配模板目录的布局方式,它不仅可以让您更好地控制如何布局模板层次结构,还可以更好地控制如何编写 < code>extends 和 include 语句。您不再使用 .. ,而只是给出相对于项目中的任何内容的模板路径,相当于我的 views 目录。

This problem bit me too when I converted from 0.96 to 1.2 Django templates. I was initially pushed to do so when SDK 1.4.2 started issuing the warning that I needed to pick a version, but when I looked into the much-needed improvements in the template language, I was eager to make the change.

And then everything broke. Like you, I used a lot of relative paths in my extends and include commands. It took a lot of debugging and digging, but I did figure out the cause of the problem and a pretty good solution.

The cause: in Django 1.2, the code that loads template files started using a command called safe_join to join path parts (you can see the code in google_appengine\lib\django_1_2\django\template\loaders\filesystem.py) . It won't allow relative paths to go above what it thinks of as the top-level directory. This is the same thing as a web server being configured to prevent you gaining access to the server's whole filesystem just by sticking some ..'s into your URL. The end result is that the

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

that used to be just fine breaks the rules and it isn't going to work.

The way that I fixed this in my application without completely restructuring how my templates are laid out is by implementing a custom TemplateLoader. Django's template rendering engine allows an application to have many different classes that know how to find templates in different ways. If you look in the directory that I gave above, you'll see that there are several provided, and they are all classes that inherit from BaseLoader. I provided my own that is custom-tailored to how my templates are laid out.

My project has a Rails-like lay-out:

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

Every template extends base.html and a couple include post.html, and they previously used relative paths to get to their location in base/. Ideally, I didn't even want to use the .. up-dir to get there, but it was required with 0.96. I created the following template loader to work with my scheme:

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

And I caused my custom template loader to be included in the collection that Django tries by changing my application's main function to look like this:

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')

(and then all the rest of the stuff that normally goes into your main function).

So, I think that you should be able to modify the TemplateLoader code above to match how you have your template directories laid out, and it will give you a greater control over not only how you layout you templates hierarcy but also how you write your extends and include statement. You no longer use .. but rather just give the path of the template relative to whatever in your project is the equivalent of my views directory.

素染倾城色 2024-10-28 18:01:54

收到错误消息 TemplateDoesNotExist: templates\AdminListstr.html 的原因之一

是仔细检查“templates”和模板名称之间使用的斜杠。我浪费了几个小时,然后检查了 os.path.join 返回的值。

总之,您需要确保使用的是“/”而不是Windows“\”。如果您使用了错误的斜杠,您的代码将在 (Windows) 开发中运行,但在部署时会失败。

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

哎呀呀!

One reason you get the error message TemplateDoesNotExist: templates\AdminListstr.html

Doublecheck the slash you use between "templates" and your template name. I wasted a few hours, then checked the value being returned by os.path.join.

In summary you need to insure you are using "/" not the Windows "\". If you use the wrong slash, your code will work on (Windows) development but fail when deployed.

My diagnostic code printed this: look at the last slash
And the value returned by os.path.join is:/base/data/home/apps/s~myapp/1.356037954289984934/templates\LabListstr.html

Aye yi yi!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文