Ответ 1
Эта проблема немного меня тоже, когда я конвертировал от 0.96 до 1.2 шаблонов Django. Сначала мне было предложено сделать это, когда SDK 1.4.2 начал выдавать предупреждение о том, что мне нужно выбрать версию, но когда я просмотрел столь необходимые улучшения в языке шаблонов, я очень хотел внести изменения.
И потом все сломалось. Как и вы, я использовал множество относительных путей в моих командах extends
и include
. Это потребовало много отладки и копания, но я выяснил причину проблемы и довольно хорошее решение.
Причина: в Django 1.2 код, загружающий файлы шаблонов, начинался с команды safe_join
для объединения частей пути (вы можете увидеть код в google_appengine\lib\django_1_2\django\template\loaders\filesystem.py
). Это не позволит относительным путям идти выше того, что, по его мнению, является каталогом верхнего уровня. Это то же самое, что и веб-сервер, настроенный для предотвращения доступа к всей файловой системе сервера, просто вставив в свой URL ..
. Конечным результатом является то, что
{% extends "../templates/base.html" %}
который раньше был просто прекрасным, нарушает правила, и он не будет работать.
Способ, которым я исправил это в своем приложении без полной реструктуризации, как мои шаблоны выложены, - это реализовать собственный шаблонный шаблон. Механизм рендеринга шаблонов 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
И я заставил свой пользовательский загрузчик шаблонов быть включенным в коллекцию, которую Django пытается, изменив мою программу main
, чтобы выглядеть так:
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 выше, чтобы соответствовать тому, как вы создали свои каталоги шаблонов, и это даст вам больший контроль над тем, как вы макет, который вы шаблоны, но и как вы напишите инструкцию extends
и include
. Вы больше не используете ..
, а просто указываете путь к шаблону относительно того, что в вашем проекте является эквивалентом моего каталога views
.