Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Django_-_podrobnoe_rukovodstvo

.pdf
Скачиваний:
306
Добавлен:
01.03.2016
Размер:
4.88 Mб
Скачать

200

Глава 9. Углубленное изучение шаблонов­

‘user’: request.user,

‘ip_address’: request.META[‘REMOTE_ADDR’], ‘message’: ‘ Я - представление 1.’

})

return t.render(c)

def view_2(request):

# ...

t = loader.get_template(‘template2.html’) c = Context({

‘app’: ‘My app’, ‘user’: request.user,

‘ip_address’: request.META[‘REMOTE_ADDR’], ‘message’: ‘Я - второе представление.’

})

return t.render(c)

(Обратите внимание, что в этих примерах мы сознательно не используем вспомогательную функцию render_to_response(), а вручную загружаем шаблоны,­ конструируем контекстные объекты и выполняем отображение. Мы «выписываем» все шаги, чтобы было понятно, что присходит.)

Оба представления передают в шаблон­ одни и те же переменные: app, user и ip_address. Было бы неплохо избавиться от подобного дублирования.

Объект RequestContext и контекстные процессоры специально придуманы для решения этой задачи. Контекстный процессор позволяет определить ряд переменных, автоматически добавляемых в каждый контекст, избавляя вас от необходимости задавать их при каждом обращении к render_to_response(). Надо лишь при отображении шаблона­ использовать объект RequestContext вместо Context.

Самый низкоуровневый способ применения контекстных процессоров заключается в том, чтобы создать несколько процессоров и передать их объекту RequestContext. Вот как можно было бы переписать предыдущий пример с использованием контекстных процессоров:

from django.template import loader, RequestContext

def custom_proc(request):

“Контекстный процессор, добавляющий ‘app’, ‘user’ и ‘ip_address’.” return {

‘app’: ‘My app’, ‘user’: request.user,

‘ip_address’: request.META[‘REMOTE_ADDR’]

}

def view_1(request):

# ...

t = loader.get_template(‘template1.html’)

c = RequestContext(request, {‘message’: ‘Я - представление 1.’}, processors=[custom_proc])

Объект RequestContext и контекстные процессоры

201

return t.render(c)

def view_2(request):

# ...

t = loader.get_template(‘template2.html’)

c = RequestContext(request, {‘message’: ‘Я - второе представление.’}, processors=[custom_proc])

return t.render(c)

Рассмотрим этот код более внимательно.

•• Сначала мы определяем функцию custom_proc. Это контекстный процессор – он принимает объект HttpRequest и возвращает словарь переменных, который будет использован в контексте шаблона­. Больше он ничего не делает.

•• Мы изменили обе функции представления так, что теперь вместо Context в них используется RequestContext. Такой способ конструирования контекста имеет два отличия. Во-первых, конструктор Request­ Context требует, чтобы первым аргументом был объект HttpRequest­ – тот самый, который с самого начала был передан функции представления. Во-вторых, конструктор RequestContext принимает необязательный аргумент processors – список или кортеж функций – контекстных процессоров. В данном случае мы передаем одну функцию custom_proc, которую определили выше.

•• Теперь в контексты, конструируемые в представлениях, не нужно включать переменные app, user, ip_address, потому что они предоставляются функцией custom_proc.

•• Однако каждое представление сохраняет возможность включить в контекст любые дополнительные шаблонные­ переменные, которые ему могут понадобиться. В данном случае переменная message получает разные значения.

В главе 4 мы познакомились со вспомогательной функцией render_to_ response(), которая избавляет от необходимости вызывать loader.get_ template(), создавать объект Context и обращаться к методу шаблона­ render(). Чтобы продемонстрировать низкоуровневый механизм работы контекстных процессоров, мы в предыдущих примерах обошлись без render_to_response(). Однако возможно и даже рекомендуется использовать контекстные процессоры в сочетании с render_to_response(). Для этого предназначен аргумент context_instance:

from django.shortcuts import render_to_response from django.template import RequestContext

def custom_proc(request):

“Контекстный процессор, добавляющий ‘app’, ‘user’ and ‘ip_address’.” return {

‘app’: ‘My app’,

202

Глава 9. Углубленное изучение шаблонов­

‘user’: request.user,

‘ip_address’: request.META[‘REMOTE_ADDR’]

}

def view_1(request):

# ...

return render_to_response(‘template1.html’, {‘message’: ‘Я - представление 1.’},

context_instance=RequestContext(request, processors=[custom_proc]))

def view_2(request):

# ...

return render_to_response(‘template2.html’, {‘message’: ‘Я - второе представление.’},

context_instance=RequestContext(request, processors=[custom_proc]))

Здесь мы свели код отображения шаблона­ в каждом представлении к одной строчке.

Это улучшение, но, оценивая лаконичность кода, мы должны признать, что теперь опустилась другая чаша весов. Мы избавились от дублирования данных (шаблонных­ переменных), зато появилось дублирование кода (при передаче аргумента processors). Поскольку теперь всякий раз приходится набирать processors, то получается, что использование контекстных процессоров мало что сэкономило.

Поэтому Django поддерживает глобальные контекстные процессоры. Параметр TEMPLATE_CONTEXT_PROCESSORS (в файле settings.py) определяет контекстные процессоры, которые всегда должны применяться к Request­ Context. Это избавляет от необходимости передавать аргумент processors при каждом использовании RequestContext.

По умолчанию TEMPLATE_CONTEXT_PROCESSORS определен следующим образом:

TEMPLATE_CONTEXT_PROCESSORS = ( ‘django.core.context_processors.auth’, ‘django.core.context_processors.debug’, ‘django.core.context_processors.i18n’, ‘django.core.context_processors.media’,

)

Этот параметр представляет собой кортеж вызываемых объектов с таким же интерфейсом, как у рассмотренной выше функции custom_proc: они принимают в качестве аргумента объект запроса и возвращают словарь элементов, добавляемых в контекст. Отметим, что значения в кортеже TEMPLATE_CONTEXT_PROCESSORS задаются в виде строк, то есть процессоры должны находиться в пути Python (чтобы на них можно было сослаться из файла параметров).

Процессоры применяются в указанном порядке. Если один процессор добавил в контекст некоторую переменную, а затем другой процессор

Объект RequestContext и контекстные процессоры

203

добавил переменную с таким же именем, то первое определение будет затерто.

Django предоставляет несколько простых контекстных процессоров, включая применяемые умолчанию.

django.core.context_processors.auth

Если TEMPLATE_CONTEXT_PROCESSORS включает этот процессор, то все объекты RequestContext будут содержать следующие переменные:

•• user: экземпляр класса django.contrib.auth.models.User, описывающий текущего аутентифицированного пользователя (или экземпляр класса AnonymousUser, если пользователь не аутентифицирован).

•• messages: список сообщений (в виде строк) для текущего аутентифицированного пользователя. В действительности при обращении к этой переменной каждый раз происходит вызов метода request.user. get_and_delete_messages(), который извлекает сообщения данного пользователя и удаляет их из базы данных.

•• perms: экземпляр класса django.core.context_processors.PermWrapper, представляющий разрешения текущего аутентифицированного пользователя.

Дополнительные сведения о пользователях, разрешениях и сообщениях см. в главе 14.

django.core.context_processors.debug

Этот процессор передает отладочную информацию на уровень шаблона­. Если TEMPLATE_CONTEXT_PROCESSORS включает этот процессор, то все объекты RequestContext будут содержать следующие переменные:

•• debug: значение параметра DEBUG (True или False). Обратившись к этой переменной, шаблон­ может узнать, работает ли приложение в режиме отладки.

•• sql_queries: список словарей {‘sql’: ..., ‘time’: ...}, в котором представлены все SQL-запросы, произведенные в ходе обработки данного запроса, и время выполнения каждого из них. Запросы следуют в порядке выполнения.

Поскольку отладочная информация конфиденциальна, этот процессор добавляет переменные в контекст только при выполнении следующих условий:

•• Параметр DEBUG равен True

•• Запрос поступил с одного из IP-адресов, перечисленных в параметре

INTERNAL_IPS

204

Глава 9. Углубленное изучение шаблонов­

Проницательный читатель заметит, что шаблонная­ переменная debug никогда не принимает значение False, так как если параметр DEBUG имеет значение False, то эта переменная вообще не добавляется в шаблон­.

django.core.context_processors.i18n

Если этот процессор включен, то все объекты RequestContext будут содержать следующие переменные:

•• LANGUAGES: значение параметра LANGUAGES.

•• LANGUAGE_CODE: значение атрибута request.LANGUAGE_CODE, если он существует, в противном случае – значение параметра LANGUAGE_CODE.

Дополнительные сведения об этих параметрах см. в приложении D.

django.core.context_processors.request

Если этот процессор включен, то все объекты RequestContext будут содержать переменную request, которая является ссылкой на текущий объект HttpRequest. Отметим, что по умолчанию этот процессор не включен, его нужно активировать явно.

Этот процессор может потребоваться, если шаблонам­ необходим доступ к атрибутам текущего объекта HttpRequest, например, к IP-адресу клиента:

{{ request.REMOTE_ADDR }}

Как написать собственный контекстный процессор

Вот несколько советов:

•• Ограничивайте функциональность одного контекстного процессора. Использовать несколько процессоров совсем несложно, поэтому лучше разбить функциональность на логически независимые части, что облегчит повторное использование в будущем.

•• Не забывайте, что все контекстные процессоры, перечисленные в параметре TEMPLATE_CONTEXT_PROCESSORS, доступны любому шаблону,­ пользующемуся файлом параметров, поэтому старайтесь выбирать имена переменных так, чтобы не возникало конфликтов с переменными, уже встречающимися в шаблонах­. Поскольку имена переменных чувствительны к регистру, будет разумно зарезервировать для переменных, добавляемых процессором, имена, написанные заглавными буквами.

•• Безразлично, в каком каталоге файловой системы­ находятся процессоры, лишь бы он был включен в путь Python, чтобы на процессоры можно было сослаться из параметра TEMPLATE_CONTEXT_PROCESSORS. Тем не менее по соглашению принято помещать процессоры в файл context_processors.py в каталоге приложения или проекта.

Автоматическое экранирование HTML

205

Автоматическое экранирование HTML

При генерации HTML-разметки по шаблону­ всегда есть опасность, что значение переменной будет содержать нежелательные символы. Возьмем, к примеру, такой фрагмент:

Привет, {{ name }}.

На первый взгляд, совершенно безобидный способ вывести имя пользователя, но представьте, что произойдет, если пользователь введет такое имя:

<script>alert(‘hello’)</script>

В этом случае при отображении шаблона­ будет создана такая HTMLразметка:

Привет, <script>alert(‘hello’)</script>

Иследовательно, броузер откроет всплывающее окно с сообщением!

Ачто если имя пользователя содержит символ ‘<’, например:

<b>username

Тогда шаблон­ породит такую разметку:

Привет, <b>username

В результате оставшаяся часть страницы будет выведена жирным шрифтом!

Очевидно, что полученным от пользователя данным нельзя слепо доверять и просто копировать их в веб-страницу, поскольку злоумышленник может воспользоваться такой брешью и нанести ущерб. При наличии подобных уязвимостей становятся возможными атаки типа «межсайтовый скриптинг» (XSS)1.

Совет

Дополнительные сведения о безопасности см. в главе 20.

Есть два способа избежать такой опасности:

•• Пропускать каждую сомнительную переменную через фильтр escape, который преобразует потенциально опасные символы в безопасные. Поначалу именно это решение и применялось в Django по умолчанию, но оно возлагает ответственность за экранирование на разработчика шаблона­. Если вы забудете профильтровать какую-то переменную, пеняйте на себя.

1Подробнее с этим видом атак можно познакомиться в Википедии по адресу http://ru.wikipedia.org/wiki/Межсайтовый_скриптинг. Прим. науч. ред.

206

Глава 9. Углубленное изучение шаблонов­

•• Воспользоваться автоматическим экранированием. Ниже в этом разделе мы опишем, как действует этот механизм.

По умолчанию каждый шаблон­ в Django автоматически экранирует значения всех шаблонных­ переменных. Точнее, экранируются следу­­ ющие пять символов:

<преобразуется в <

>преобразуется в >

(одиночная кавычка) преобразуется в '

(двойная кавычка) преобразуется в "

&преобразуется в &

Еще раз подчеркнем, что по умолчанию этот механизм включен. Если вы пользуетесь системой­ шаблонов­ Django, то можете считать себя в безопасности.

Как отключить автоматическое экранирование

Есть несколько способов отключить автоматическое экранирование для конкретного сайта, шаблона­ или переменной.

Но зачем вообще его отключать? Иногда шаблонные­ переменные могут содержать данные, которые должны интерпретироваться как HTMLкод, а потому экранирование было бы излишним. Например, в вашей базе данных может храниться безопасный фрагмент HTML-кода, который вставляется в шаблон­ напрямую. Или вы используете систему­ шаблонов­ Django для генерации текста в формате, отличном от HTML, скажем, для вывода сообщения электронной почты.

Для отдельных переменных

Чтобы отключить автоматическое экранирование для одной переменной, воспользуйтесь фильтром safe:

Это экранируется: {{ data }}

А это не экранируется: {{ data|safe }}

Можете воспринимать слово safe как safe from further escaping (защищено от последующего экранирования) или can be safely interpreted as HTML (можно безопасно интерпретировать как HTML). В примере выше, если переменная data содержит строку ‘<b>’, то будет выведено следующее:

Это экранируется: <b> А это не экранируется: <b>

Для блоков шаблона­

Тег autoescape позволяет управлять автоматическим экранированием на уровне шаблона­. Для этого достаточно заключить весь шаблон­ или его часть в операторные скобки, например:

Автоматическое экранирование HTML

207

{% autoescape off %} Привет {{ name }} {% endautoescape %}

Тег autoescape принимает в качестве аргумента on или off. Иногда требуется включить автоматическое экранирование там, где оно иначе было бы отключено. Рассмотрим пример:

Автоэкранирование по умолчанию включено. Привет {{ name }}

{% autoescape off %}

Здесь автоэкранирование отключено: {{ data }}.

И здесь тоже: {{ other_data }} {% autoescape on %}

Автоэкранирование снова включено: {{ name }} {% endautoescape %}

{% endautoescape %}

Действие тега autoescape, как и всех блочных тегов, распространяется на шаблоны,­ наследующие текущий, а также на включаемые с помощью тега include, например:

# base.html

{% autoescape off %}

<h1>{% block title %}{% endblock %}</h1> {% block content %}

{% endblock %}

{% endautoescape %}

# child.html

{% extends “base.html” %}

{% block title %}То да се{% endblock %}

{% block content %}{{ greeting }}{% endblock %}

Поскольку в базовом шаблоне­ автоматическое экранирование отключено, то оно будет отключено и в дочернем шаблоне,­ поэтому если переменная greeting содержит строку <b>Hello!</b>, то будет выведена такая HTML-разметка:

<h1>То да се</h1> <b>Привет!</b>

Примечания

Обычно авторы шаблонов­ не задумываются об автоматическом экранировании. Но разработчики частей, написанных на Python (представлений и фильтров), должны подумать о тех случаях, когда данные не нужно экранировать, и соответствующим образом пометить их, чтобы шаблон­ работал правильно.

Если вы пишете шаблон,­ который может использоваться в ситуациях, где неизвестно, будет ли включено автоматическое экранирование, добавляйте фильтр escape к любой переменной, нуждающейся в экранировании. Если автоматическое экранирование и так включено, то двойное экранирование не на-

208

Глава 9. Углубленное изучение шаблонов­

несет никакого вреда, потому что фильтр escape не затрагивает переменные, уже подвергнутые автоматическому экранированию.

Автоматическое экранирование строковых литералов в аргументах фильтра

Как отмечалось выше, аргументы фильтра могут быть строками:

{{ data|default:”Это строковый литерал.” }}

Любой строковый литерал вставляется в шаблон­ без автоматического экранирования, как если бы был пропущен через фильтр safe. Такое решение принято потому, что за содержимое строкового литерала несет ответственность автор шаблона,­ значит, он может экранировать его самостоятельно.

Следовательно, правильно писать так:

{{data|default:”3 < 2” }}

ане так:

{{data|default:”3 < 2” }} <-- Плохо! Не делайте так.

Это никак не отражается на том, что происходит с данными, поступающими из самой переменной. Содержимое переменной по-прежнему автоматически экранируется при необходимости, так как автор шаблона­ им не управляет.

Загрузка шаблонов­ – взгляд изнутри

Вообще говоря, шаблоны­ хранятся в файлах, но можно написать специальные загрузчики шаблонов­, которые будут загружать шаблоны­ из других источников.

В Django предусмотрено два способа загрузки шаблонов:­

•• django.template.loader.get_template(template_name): get_template возвращает откомпилированную версию (объект Template) шаблона­ с заданным именем. Если указанного шаблона­ не существует, то возбуждается исключение TemplateDoesNotExist;

•• django.template.loader.select_template(template_name_list): select_ template аналогичен get_template, но принимает список имен шаб­ лонов. Возвращает откомпилированную версию первого существующего шаблона­ из перечисленных в списке. Если не существует ни одного шаблона,­ возбуждается исключение TemplateDoesNotExist.

В главе 4 отмечалось, что для загрузки шаблонов­ обе функции по умолчанию пользуются параметром TEMPLATE_DIRS. Но всю сложную работу они делегируют загрузчику шаблонов­.

Расширение системы­ шаблонов­

209

Некоторые загрузчики по умолчанию отключены, но их можно активировать, изменив параметр TEMPLATE_LOADERS. Значением этого парамет­ ра должен быть кортеж строк, в котором каждая строка представляет один загрузчик шаблонов­. В комплект поставки Django входят следующие загрузчики:

•• django.template.loaders.filesystem.load_template_source: загружает шаб­ лоны из файловой системы,­ используя параметр TEMPLATE_DIRS. По умолчанию включен;

•• django.template.loaders.app_directories.load_template_source: загружает шаблоны­ из каталогов приложений Django в файловой системе­. Для каждого приложения, перечисленного в параметре INSTALLED_ APPS, загрузчик ищет подкаталог templates. Если таковой существует, Django ищет в нем шаблоны­.

Следовательно, шаблоны­ можно хранить вместе с приложениями, что упрощает распространение приложений Django с шаблонами­ по умолчанию. Например, если INSTALLED_APPS содержит (‘myproject. polls’, ‘myproject.music’), то загрузчик get_template(‘foo.html’) будет искать шаблоны­ в таком порядке:

•• /path/to/myproject/polls/templates/foo.html

•• /path/to/myproject/music/templates/foo.html

Отметим, что при первом вызове загрузчик выполняет оптимизацию – он кэширует список пакетов, перечисленных в параметре INSTALLED_APPS, у которых есть подкаталог templates.

Этот загрузчик по умолчанию включен.

•• django.template.loaders.eggs.load_template_source: аналогичен загрузчику app_directories, но загружает шаблоны­ из пакетов, оформленных в виде eggs-пакетов Python, а не из файловой системы­. По умолчанию этот загрузчик отключен; его следует включить, если вы распространяете свое приложение в виде eggs-пакета. (Технология Python Eggs – это способ упаковки исполняемого Python кода в единый файл.)

Django опробует загрузчики шаблонов­ в том порядке, в котором они перечислены в параметре TEMPLATE_LOADERS. Перебор прекращается, как только очередной загрузчик найдет подходящий шаблон­.

Расширение системы­ шаблонов­

Теперь, когда вы кое-что узнали о внутреннем устройстве системы­ шаб­ лонов, посмотрим, как ее можно расширить за счет дополнительного кода.

Чаще всего расширение производится путем создания пользовательских шаблонных­ тегов и (или) фильтров. Хотя в язык шаблонов­ Django встроено много тегов и фильтров, вы, скорее всего, постепенно создади-

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]