воскресенье, 26 мая 2013 г.

Модульные приложения Flask с использованием blueprint'ов

Перевод статьи: Modular Applications with Blueprints.

Blueprint'ы доступны с версии 0.7

Flask использует понятие blueprint'ов (blueprint - эскиз) для создания компонентов приложений и поддержки общих шаблонов внутри приложения или между приложениями. Blueprint'ы могут как значительно упростить большие приложения, так и предоставить общий механизм регистрации в приложении операций из расширений Flask. Объект Blueprint работает аналогично объекту приложения Flask, но в действительности он не является приложением. Обычно это лишь эскиз для сборки или расширения приложения.

Для чего нужны blueprint'ы?

Blueprint'ы во Flask могут пригодиться в случае, если нужно:
  • Разделить приложения на набор blueprint'ов. Они идеальны для больших приложений; проект должен создать объект приложения, инициализировав несколько расширений, и зарегистрировав набор blueprint'ов.
  • Зарегистрировать blueprint в приложении по определённом префиксу URL и/или в поддомене. Параметры в префиксе URL или поддомене становятся обычными аргументами представлений (со значениями по умолчанию) для всех функций представлений в blueprint'е.
  • Зарегистрировать blueprint несколько раз в приложении с разными правилами URL.
  • Предоставить фильтры шаблонов, статический файлы, шаблоны и другие вспомогательные средства с помощью blueprint'ов. Blueprint не является реализацией приложения или функций представлений.
  • Зарегистрировать blueprint в приложении в любом из этих случаев при инициализации расширения Flask.
Blueprint во Flask не является подключаемым приложением, потому что это на самом деле не приложение - это набор операций, которые могут быть зарегистрированы в приложении, возможно даже не один раз. Почему бы не воспользоваться несколькими объектами приложений? Вы можете это сделать (обратитесь к разделу Диспетчирезация приложений), но ваши приложения будут иметь раздельные файлы конфигурации и будут управляться слоем WSGI.

Вместо этого, blueprint'ы предоставляют разделение на уровне Flask, позволяя использовать общий файл конфигурации приложения и могут менять объект приложения необходимым образом при регистрации. Побочным эффектом будет невозможность отменить регистрацию blueprint'а, если приложение уже было создано, если только не уничтожить целиком весь объект приложения.

Концепция blueprint'ов

Основная концепция blueprint'ов заключается в том, что они записывают операции для выполнения при регистрации в приложении. Flask связывает функции представлений с blueprint'ами при обработке запросов и генерировании URL'ов от одной конечной точки* к другой.

Мой первый blueprint

Приведём пример того, как выглядит основа простейшего blueprint'а. В данном случае мы хотим реализовать blueprint, который выполняет простую отрисовку статических шаблонов:
from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound

simple_page = Blueprint('simple_page', __name__,
                        template_folder='templates')

@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
    try:
        return render_template('pages/%s.html' % page)
    except TemplateNotFound:
        abort(404)
При связывании функции при помощи декоратора @simple_page.route, blueprint записывает намерение зарегистрировать в приложении функцию show, когда blueprint будет зарегистрирован. Кроме того, декоратор предварит название конечной точки префиксом - именем blueprint'а, который был указан конструктору Blueprint (в данном случае это тоже simple_page).

Регистрация blueprint'ов

Как теперь зарегистрировать этот blueprint? Например, так:
from flask import Flask
from yourapplication.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page)
Если теперь посмотреть на правила, зарегистрированные в приложении, то можно обнаружить следующее:
[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
<Rule '/' (HEAD, OPTIONS, GET) -> simple_page.show>]
Первым обычно является правило для статических файлов самого приложения. Следующие два правила - правила для функции show из blueprint'а simple_page. Как можно заметить, они тоже предварены именем blueprint'а и отделены от него точкой.

Однако, blueprint'ы можно связывать с другими местами:
app.register_blueprint(simple_page, url_prefix='/pages')
И, чтобы убедиться в этом, посмотрим на правила, сгенерированные на этот раз:
[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
<Rule '/pages/' (HEAD, OPTIONS, GET) -> simple_page.show>]
Плюс ко всему, можно зарегистрировать blueprint'ы несколько раз, хотя не каждый blueprint будет работать правильно. Это зависит от того, был ли реализован blueprint'е с учётом возможности многократного монтирования.

Ресурсы blueprint'а

Blueprint'ы могут, кроме всего прочего, предоставлять ресурсы. Иногда может потребоваться ввести дополнительный blueprint только ради предоставления ресурсов.

Каталог ресурсов blueprint'а

Как и обычные приложения, blueprint'ы задуманы для размещения в отдельном каталоге. Хотя несколько blueprint'ов можно разместить в одном и том же каталоге, так делать не рекомендуется.

Имя каталога берётся из второго аргумента blueprint'а, которым обычно является __name__. Этот аргумент указывает, какой логический модуль или пакет Python соответствует blueprint'у. Если он указывает на существующий пакет Python (который является каталогом файловой системы), то он и будет каталогом ресурсов. Если это модуль, то каталогом ресурсов будет тот каталог, в котором содержится модуль. Можно обратиться к свойству Blueprint.root_path, чтобы увидеть, что это за каталог:
>>> simple_page.root_path
'/Users/username/TestProject/yourapplication'
Для быстрого открытия ресурсов из этого каталога можно воспользоваться функцией open_resource():
with simple_page.open_resource('static/style.css') as f:
    code = f.read()
Статические файлы

Blueprint может выставлять наружу каталог со статическими файлами, если в его конструкторе указан каталог файловой системы с помощью аргумента с ключевым словом static_folder. Аргумент может быть абсолютным путём или каталогом относительно каталога blueprint'а:
admin = Blueprint('admin', __name__, static_folder='static')
По умолчанию самая правая часть пути выставляется наружу в веб. Поскольку в данном случае указан каталог с именем static, он будет располагаться внутри каталога blueprint'а и будет называться static. В данном случае при регистрации blueprint'а в каталоге /admin, каталог static будет находиться в /admin/static.

Конечная точка будет иметь имя blueprint_name.static, так что можно генерировать URL'ы точно так же, как это делается для статического каталога приложения:
url_for('admin.static', filename='style.css')
Шаблоны

Если нужно выставить наружу каталог с шаблонами, это можно сделать указав параметр template_folder конструктору Blueprint:
admin = Blueprint('admin', __name__, template_folder='templates')
Как и в случае статических файлов, путь может быть абсолютным или располагаться в каталоге ресурсов blueprint'а. Каталог шаблона добавляется к пути поиска шаблонов, но с меньшим приоритетом, чем каталог самого приложения. Таким образом, можно легко заменить шаблоны blueprint'а в самом приложении.

Например, если есть blueprint в каталоге yourapplication/admin и нужно отрисовать шаблон 'admin/index.html', а в параметре template_folder указан каталог templates, тогда нужно создать файл yourapplication/admin/templates/admin/index.html.

Генерирование URL'ов

Если нужно вставить ссылку с одной страницы на другую, можно воспользоваться функцией url_for(), как обычно: нужно просто добавить к конечной точке URL'а префикс с именем blueprint'а и точкой:
url_for('admin.index')
Наконец, если в функции представления blueprint'а или в отрисованном шаблоне нужно добавить ссылку на другую конечную точку того же blueprint'а, можно воспользоваться относительным перенаправлением, добавив префикс, состоящий только из точки:
url_for('.index')
Получится ссылка на admin.index в случае обработки текущего запроса в любой другой конечной точке blueprint'а.

Примечания переводчика

* Нашёл такое объяснение понятия конечной точки: Что такое конечная точка?
@app.route('/user/', endpoint='user'):
def view_user(name):
    pass

@app.route('/user/new', endpoint='user'):
def new_user():
    pass
url_for('user') выдаст URL для new_user
url_for('user', name='krace') выдаст URL для view_user

Такой способ не работает, если не динамические части одинаковые.
В данном случае конечной точкой является альтернативное имя user для двух разных представлений. Это имя можно использовать в функции url_for, как показано в примере, или в шаблонах, в виде тега {{ url_for('user') }}, {{ url_for('user', name='krace') }} или {{ url_for('user', name = user.name) }}

Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.

воскресенье, 19 мая 2013 г.

Настройка принтера HP LaserJet 1018 в Debian Wheezy

Ранее я уже писал о настройке этого принтера в Debian.

С тех пор принцип настройки остался прежним, но кое-что успело поменяться. Каждый раз приходится при настройке принтера вспоминать, как я это делал в прошлый раз. Чтобы не пришлось вспоминать, решил записать, как это делается.

Первым делом ставим необходимые пакеты:
# apt-get install cups printer-driver-hpcups printer-driver-foo2zjs
Затем скачиваем прошивку для принтера:
# getweb 1018
Теперь включаем принтер. Если принтер уже был включен, стоит его выключить и включить, чтобы в него загрузилась прошивка.

Запускаем браузер и открываем страницу по ссылке http://localhost:631/.

Далее действуем в соответствии со скриншотами (указатель мыши на скриншотах показывает, куда нужно нажать):

воскресенье, 12 мая 2013 г.

Расширения Flask

Перевод статьи: Flask Extensions

Расширения Flask различным образом расширяют функциональность Flask. Например, добавляют поддержку баз данных и т.п.

Поиск расширений

Расширения Flask перечислены в реестре расширений Flask и могут быть скачаны при помощи easy_install или pip. Если добавить расширение Flask в качестве зависимости в файл requirements.rst или setup.py, то обычно их можно установить с помощью простой команды или при установке приложения.

Использование расширений

Обычно расширения снабжены документацией, которая объясняет как их использовать. Нет общих правил обращения с расширениями, но их можно импортировать из обычных мест. Если у вас имеется расширение под названием Flask-Foo или Foo-Flask, в любом случае его можно импортировать из flask.ext.foo:
from flask.ext import foo
Flask до версии 0.8

Во Flask версии 0.7 и более ранних пакет flask.ext отсутствует, а вместо него можно импортировать расширение из flaskext.foo или flask_foo, в зависимости от способа поставки расширения. Если вы хотите разрабатывать приложения, поддерживающие Flask 0.7 и более ранние, импортировать всё равно нужно из пакета flask.ext. Есть модуль, предоставляющий этот пакет и позволяющий достичь совместимости со старыми версиями Flask. Его можно скачать с github: flaskext_compat.py

Вот пример его использования:
import flaskext_compat

flaskext_compat.activate()
from flask.ext import foo
После активации модуля flaskext_compat появится flask.ext из которого можно импортировать расширения.

Примечания переводчика

Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.

Собственные страницы ошибок во Flask

Перевод статьи: Custom Error Pages

Flask поставляется с удобной функцией abort(), которая досрочно прерывает запрос с кодом ошибки HTTP. В комплекте с ней идут простая чёрно-белая страница ошибки с небольшим описанием, ничего особого.

В зависимости от кода ошибки, с разной степенью вероятности, пользователь может увидеть эту страницу.

Обычные коды ошибок

Чаще всего, даже если приложение работает правильно, пользователь может увидеть следующие коды ошибок:

404 Не найдено

Старое доброе сообщение "чувак, ты допустил ошибку при вводе URL". Встречается настолько часто, что даже новички в интернете знают, что означает 404: блин, тут нет того, что я искал. Неплохо поместить на этой странице что-то более полезное, хотя-бы ссылку на стартовую страницу.

403 Запрещено

Если на сайте есть какая-нибудь разновидность ограничения доступа, может понадобиться отправить код 403 при попытке доступа к ресурсам, доступ к которым запрещён. Позволяет не потерять пользователя, который попытался получить доступ к запрещённому ресурсу.

410 Удалено

Вы знаете, что у "404 Не найдено" есть брат по имени "410 Удалено"? Мало кто делает так, что прежде существовавшие, но ныне удалённые ресурсы отвечают ошибкой 410 вместо 404. Если вы не удаляете документы из базы данных навсегда, а просто помечаете их как удалённые, можно сделать пользователю одолжение и воспользоваться кодом 410, а не отображать сообщение о том, что искомое было удалено навечно.

500 Внутренняя ошибка сервера

Обычно происходит в случае ошибок в программе или при повышенной нагрузке на сервер. Ужасно хорошо было бы иметь на этот случай приятную страницу, потому что рано или поздно в приложении может случится сбой (см. также: Журналирование ошибок приложения).

Обработчики ошибок

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

Обработчик ошибок регистрируется при помощи декоратора errorhandler() с кодом ошибки. Помните, что Flask не устанавливает код ошибки за вас, поэтому удостоверьтесь, что возвращаете в ответе код статуса HTTP.

Здесь приведён пример обработки исключения "404 Страница не найдена":
from flask import render_template

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404
Шаблон для этого примера может быть таким:
{% extends "layout.html" %}
{% block title %}Страница не найдена{% endblock %}
{% block body %}
  <h1>Страница не найдена</h1>
  <p>Здесь нет того, что вы искали.
  <p><a href="{{ url_for('index') }}">Вот клёвое место.</a>
{% endblock %}
Примечания переводчика

После прочтения перевода у вас может возникнуть недоумение - что за "чувак" и "клёво"? Я не стал бы добавлять в текст эти слова по собственной инициативе - я просто попытался в меру своих скудных способностей воспроизвести стиль оригинального текста. В оригинале этим словам соответствуют chap и nice.

Более подробный список кодов статуса HTTP можно увидеть здесь: Список кодов состояния HTTP.

Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.

воскресенье, 5 мая 2013 г.

Шаблоны Jinja2 во Flask

Перевод статьи: Templates

Flask использует в качестве системы шаблонизации Jinja2. Можно использовать другие системы шаблонизации, но для запуска Flask всё равно необходимо установить Jinja2. Это необходимо для использования дополнительных возможностей. Расширения могут зависеть от наличия Jinja2.

Этот раздел предоставляет лишь краткое описание интеграции Jinja2 во Flask. Если вам нужна информация о синтаксисе самой системы шаблонизации, за более подробной информацией обратитесь к официальной документации по шаблонам Jinja2.

Установка Jinja

По умолчанию Flask настраивает Jinja2 следующим образом:
  • включено автоматическое экранирование для всех шаблонов, с раширениями .html, .htm, .xml, .xhtml
  • шаблон может включать или отключать автоматическое экранирование при помощи тега {% autoescape %}.
  • Flask добавляет пару функций и хелперов в контекст Jinja2, дополнительно к значениям, имеющимся по умолчанию.
Стандартный контекст

По умолчанию из шаблонов Jinja2 доступны следующие глобальные переменные:
config Объект текущей конфигурации (flask.config)
Новинка версии 0.6.
requestОбъект текущего запроса (flask.request)
sessionОбъект текущего сеанса (flask.session)
gСвязанный с запросом объект с глобальными переменными (flask.g)
url_for()Функция flask.url_for()
get_flashed_messages()Функция flask.get_flashed_messages()

Контекстное поведение Jinja

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

Какое это имеет значение? Если вам нужно получить доступ из макроса к объекту запроса, есть две возможности:
  1. явным образом передать объект запроса или его атрибут в макрос в качестве параметра.
  2. импортировать макрос с контекстом, указав ключевые слова "with context".
Импорт с контекстом выглядит следующим образом:
{% from '_helpers.html' import my_macro with context %}
Стандартные фильтры

В дополнение к собственным фильтрам Jinja2, доступны следующие фильтры:

tojson()

Эта функция конвертирует переданный объект в JSON-представление. Это может быть полезно, когда нужно на лету сгенерировать JavaScript.

Отметим, что внутри тегов script не должно производиться экранирование, поэтому убедитесь в том, что отключили экранирование при помощи фильтра |safe, если собираетесь использовать фильтр tojson внутри тегов script:
<script type=text/javascript>
  doSomethingWith({{ user.username|tojson|safe }});
</script>

Фильтр |tojson правильно экранирует прямую косую черту.

Управление автоэкранированием

Автоэкранирование - это автоматическое экранирование специальных символов. Специальными символами в HTML (а также в XML и в XHTML) являются &, >, <, " и '. Поскольку эти символы имеют особое значение в документах, для использования в тексте их нужно заменить на так называемые "сущности". Если этого не сделать, это не только может повлиять на невозможность использования этих символов пользователем, но и привести к проблемам с безопасностью (см. Кросс-сайтовый скриптинг - XSS).

Однако, иногда в шаблонах может потребоваться отключить автоэкранирование. Это может понадобиться, если нужно явным образом вставить в страниц фрагмент HTML, если фрагмент поступил из системы генерации безопасного HTML, например, из преобразователя markdown в HTML.

Для достижения этого есть три способа:
  • В коде Python обернуть строку HTML в объект Markup перед передачей в шаблон. Это рекомендуемый способ.
  • Внутри шаблона, воспользовавшись фильтром |safe для явной отметки строки, как безопасного HTML ({{ myvariable|safe }})
  • Временно отключить систему автоэкранирования.
Для отключения системы автоэкранирования в шаблонах можно воспользоваться блоком {% autoescape %}:
{% autoescape false %}
  <p>здесь автоэкранирование отключено
  <p>{{ will_not_be_escaped }}
{% endautoescape %}
Соблюдайте осторожность и всегда следите за переменными, которые помещаете в этот блок.

Регистрация фильтров

Если нужно зарегистрировать собственные фильтры в Jinja2, у есть два способа. Можно просто поместить их вручную в атрибут jinja_env приложения или использовать декоратор template_filter().

Следующие примеры делают одно и то же, переставляя элементы объекта в обратном порядке:
@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

def reverse_filter(s):
    return s[::-1]

app.jinja_env.filters['reverse'] = reverse_filter
При использовании декоратора указывать аргумент не обязательно, если вы хотите чтобы имя фильтра совпадало с именем функции. Однажды зарегистрировав фильтр, вы можете использовать его в шаблонах точно так же, как и встроенные фильтры Jinja2, например, если имеется список Python, имеющий в контексте имя mylist:
{% for x in mylist | reverse %}
{% endfor %}
Процессоры контекста

Для автоматической вставки в контекст шаблона новых переменных существуют процессоры контекста Flask. Процессоры контекста запускаются перед отрисовкой шаблона и позволяют добавить новые переменные в контекст. Процессор контекста - это функция, возвращающая словарь, который будет объединён с контекстом шаблона, для всех шаблонов в приложении. Например, для app:
@app.context_processor
def inject_user():
    return dict(user=g.user)
Процессор контекста, приведённый выше, сделает переменную g.user доступной из шаблона под именем user. Этот пример не очень интересен, поскольку g и так доступна в шаблонах, но даёт представление о том, как это работает.

Процессор контекста может передавать в шаблон не только переменные, но и функции (поскольку Python позволяет передавать функции):
@app.context_processor
def utility_processor():
    def format_price(amount, currency=u'€'):
        return u'{0:.2f}{1}.format(amount, currency)
    return dict(format_price=format_price)
Вышеприведённый процессор контекста передаст функцию format_price во все шаблоны:
{{ format_price(0.33) }}
Вы также можете встроить format_price как фильтр шаблона (см. выше раздел Регистрация фильтров), но этот пример демонстрирует, как передавать функции в контекст шаблона.

Примечания переводчика

Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.