developer.co.ua

Holy Copypasters
23.07.2007
Евгений Загородний

Pylons: python-фреймворк 0.10

Эта статья посвящена знакомству с веб-фреймворком Pylons, написанном на языке python.
Поскольку функциональность подавляющего большинства веб-приложений сводится к обработке форм и взаимодействию с базой данных, будет рассмотрено создание простейшего приложения, которое... обрабатывает формы и взаимодействует с базой данных ;)
Материал, изложенный здесь, следует воспринимать как отправную точку для дальнейшего знакомства с возможностями фреймворка.
Предполагается, что читатель знаком с концепциями архитектуры MVC.

Установка

Установка Pylons довольно проста, и при отсутствии непредвиденных обстоятельств производится «в одну команду».
Один из способов установки — с помощью утилиты easy_install, которая входит в коллекцию setuptools.
Другой способ, не требующий установленных setuptools — с помощью одного скрипта ez_setup.py.
Кроме того, в коллекции портов FreeBSD имеется порт www/py-pylons, которым я и воспользовался. Неожиданностей при установке не возникло.
Подробное описание установки и последующих настроек можно найти в разделе Installing Pylons официальной документации.

Создание проекта

Сразу отмечу одну из характерных особенностей Pylons — он интенсивно использует сторонние компоненты. Сам фреймворк преимущественно играет роль связующего звена между ними. Требовательные к гибкости разработчики оценят возможность замены используемых компонентов на другие, аналогичные по функциональности, но более привычные / подходящие для определенного круга задач. Имеем такой вот «конструктор» :)

Для создания «скелета» проекта используется скрипт Paste:
> paster create -t pylons basic
Это команда создаст в текущей директории проект “basic”. Описание структуры проекта можно найти в разделе Getting Started официальной документации.
Все, что касается конкретного проекта находится в basic/basic (аналог app в CakePHP или apps в Symfony).

Собственно, это было необходимым минимумом для того, чтобы можно было запустить сервер, что мы и сделаем (из директории basic):
> paster serve --reload development.ini

Это запустит веб-сервер, по умолчанию — сервер из модуля Pylons, который будет обслуживать localhost (то есть только ту машину, на которой был запущен). Это в большинстве случаев удобно для разработки и отладки. На production-сервере можно либо сконфигурировать Pylons для работы с имеющимся веб-сервером, например, Apache с mod_python, либо настроить имеющийся веб-сервер для проксирования запросов на веб-сервер Pylons.

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

Если все прошло успешно, то на http://localhost:5000/ мы увидим «умолчальную» страничку.

Добавляем контроллер

Начнем с добавления в наш проект контроллера “welcome”. Для этого, опять же, воспользуемся Paste:
> paster controller welcome
После этого появится файл basic/controllers/welcome.py со следующим содержимым:
from basic.lib.base import *
 
class WelcomeController(BaseController):
    def index(self):
        # Return a rendered template
        #   return render_response('/some/template.html')
        # or, Return a response object
        return Response('Hello World')


Как видим, вновь созданный конроллер имеет единственное действие (action) “index”. Его результат можно увидеть на http://localhost:5000/welcome — простая страничка с текстом “Hello World”, результатом return Response('Hello World').

Что возвращает контроллер?

Есть два способа для того, чтобы вернуть значение из контроллера.
Первый — вернуть объект Response. Конечным результатом будет страница, содержащая строку, которой инициализировался объект Response (как в предыдущем примере с “Hello World”).
Второй — воспользоваться методом render_response, который принимает в качестве аргумента название шаблона (template). Шаблоны находятся в директории templates.
Честно говоря, трудно представить, в каких случаях в реальных приложениях будет целесообразно использование первого способа. Поэтому в дальнейшем будет использоватся только второй способ. Для этого понадобится создать шаблон, чем мы сейчас и займемся.

Добавление шаблона

Как уже было замечено, шаблоны нашего проекта хранятся в директории basic/templates. Создадим новый шаблон basic/templates/intro.html:
Hello.<br />
You can <a href="/welcome/add">add a new record</a> or <a href="/welcome/list">list existent records</a>.<br />
Good luck!


Как можно заметить, в шаблон не были добавлены некоторые необходимые теги, например <html>. Это было сделано намеренно — «обертка» будет общей для всех страниц проекта. Она определяется в basic/templates/autohandler. Добавим для наших страничек простое оформление из header-а footer-а:
<html>
 <head><title>Basic web application</title></head>
 <body>
  <div class="header"><h1>Welcome to Pylons framework!<h1><hr /></div>
  <div class="content">
% m.call_next()
  </div>
  <div class="footer"><hr />powered by <a href="http://pylonshq.com/">Pylons</a></div>
 </body>
</html>


Обратите внимание на строчку % m.call_next() — она предназначена для шаблонного движка Myghty, используемого в Pylons (как уже упоминалось, Pylons напоминает своеобразный «конструктор», и вместо Myghty можно использовать другой движок, удобный Вам).

То, что в Pylons называется template — в CakePHP называется view.
То, что я назвал «оберткой» — в CakePHP называется layout.

Использование шаблона в контроллере

Теперь изменим действие “index” контроллера “welcome” (basic/controllers/welcome.py), чтобы оно использовало шаблон “intro”:
def index(self):
        return render_response('intro.html')


После этого на http://localhost:5000/welcome будет находится наш шаблон, оформленный в соответствии с «оберткой».
Ссылки на этой страничке пока что не работают, но нажав на них можно ознакомиться с debuger-ом фреймворка.

Helpers

Ни один уважающий себя фреймворк не обходится без так называемых helper-ов — функций, облегчающих генерирование html-кода и делающих шаблоны более читабельными. Pylons — не исключение, и сейчас мы воспользуемся helper-ами для создания формы.

Создадим шаблон basic/templates/add-record.html:
<% h.form(h.url(action='save'), method='post') %>
 Record title: <% h.text_field('title') %> <br />
 Record description: <% h.text_field('description') %> <br />
 <% h.submit('Add record') %>
<% h.end_form %>


Как видно, содержимое шаблона говорит само за себя. Комментария заслуживает только префикс h перед вызовами методов. h — это одна из глобальных переменных, предоставляемых Pylons. Она используется для обращения к helper-ам.

Снова добавим в контроллер “welcome” новое действие “add”, использующее этот шаблон:
def add(self):
        return render_response('add-record.html')

Теперь одна из ссылок на странице http://localhost:5000/welcome стала рабочей.

Обработка формы

После заполнения и отправки формы на http://localhost:5000/welcome/add управление перейдет к действию “save” (см. action='save'). Наша задача — принять данные из формы и обработать их.

Данные, переданные через GET или POST запросы, доступны в контроллере как request.params['имя_поля']. Продемонстрируем это, заодно рассмотрев передачу данных из контроллера в шаблон. Добавим в контроллер “welcome” действие “save”:
def save(self):
        c.title       = request.params['title']
        c.description = request.params['description']
        return render_response('save-record.html')

c — это еще одна глобальная переменная Pylons, предназначенная для передачи значений в шаблон.

Добавим шаблон basic/templates/save-record.html:
Record posted<br />
Title: <% c.title %><br />
Description: <% c.description %><br />


Заполнив форму на http://localhost:5000/welcome/add и отправив ее, мы перейдем к действию save, которое пока что только выводит переданные значения на страницу.
Теперь самое время заняться настройкой связи с базой данных.


Настройка связи с базой данных

И снова дает знать о себе «конструктороподобность» Pylons: выбор движка для взаимодействия с базой данных, такого как SQLAlchemy или SQLObject, остается за нами. Вплоть до того, что можно вообще отказаться от его использования в пользу стандартного API.

Воспользуемся набирающим популярность SQLAlchemy. Прежде всего его необходимо установить, включив поддержку используемого сервера базы данных (в моем случае — MySQL). Инструкции по установке есть в официальной документации. Кроме того, порт databases/py-sqlalchemy опять-таки присутствует в коллекции портов FreeBSD.

Приступим к связванию нашего проекта с базой данных. Прежде всего, добавим в секцию [app:main] файла development.ini (предназначенного для Paste) строку, указывающую, как соединиться с базой данных, например
sqlalchemy.dburi = mysql://root@localhost/basic
Эта строка будет использована в качестве аргумента для create_engine; ее формат описан в разделе Database Engines документации по SQLAlchemy.

Далее, в файле basic/models/__init__.py инициализируем метаданные для связи с базой данных и проассоциируем их с объектом, в виде которого данные из таблицы будут представлены в нашем приложении:
from sqlalchemy import *
from sqlalchemy.ext.assignmapper import assign_mapper
from pylons.database import session_context as ctx
 
meta = MetaData()
 
records_table = Table('records', meta,
    Column('id',          Integer, primary_key=True),
    Column('title',       String(40)),
    Column('description', String(40)),
)
 
class Record(object):
    def __str__(self):
        return self.title
        
record_mapper = assign_mapper(ctx, Record, records_table)


В реальных приложениях со сложными моделями класс Record будет дополнен вспомоготельными методами.

И, наконец, дополним basic/websetup.py действиями, необходимыми для инициализации базы данных. В его начале импортируем модели проекта:


А в методе setup_config добавим следующее:
from pylons.database import create_engine
engine = create_engine(conf['sqlalchemy.dburi'])
model.meta.connect(engine)
model.meta.create_all()


Убедившись, что сервер баз данных запущен и создав пустую базу данных с названием, указанным в sqlalchemy.dburi, можно приступить к инициализаци базы данных. Эта процедура выглядит одинаково как во время разработки, так и при установке завершенного приложения на сервер:
> paster setup-app development.ini

После этого база данных будет содержать необходимые таблицы.

Обращение к модели

Итак, в наших руках теперь вся мощь SQLAlchemy. Переделаем действие save так, чтобы оно действительно сохраняло запись в базу данных:
def save(self):
        record = model.Record();
        record.title       = request.params['title']
        record.description = request.params['description']
        record.flush()
        h.redirect_to(action='saved')

Код в комментариях не нуждается — создается новый объект «запись», заполняется данными из формы, и, наконец, сохраняется в базе данных (record.flush()).
Однако, обратите внимание на последнюю строчку — вместо привычного return_response указывается перенаправление на другое действие, saved. Это сделано для того, чтобы в том случае, если после успешного сохранения пользователь обновит в броузере содержимое страницы, информация в POST-запросе не посылалась повторно.
Разумеется, действие saved необходимо определить:
def saved(self):
        return render_response('save-record.html')

Шаблон save-record.html, которым мы воспользовались, также нуждается в правке, чтобы соответствовать своему новому предназначению:
Record saved.<br />
<a href="/welcome/add">Add another record</a><br />
<a href="/welcome/">Return</a><br />

И наконец...

Что ж, осталось, используя уже знакомые средства, «оживить» вторую ссылку на http://localhost:5000/welcome. Для этого нужно определить действие list
def list(self):
        c.records = model.Record.select()
        return render_response('list-records.html');

и создать для него шаблон list-records.html
List of records:
<table>
 <tr><th>title</th><th>description</th></tr>
% for record in c.records:
 <tr><td><% record.title %></td><td><% record.description %></td></tr>
%
</table>
<a href="/welcome">Return</a>

Заключение

Итак, было рассмотрено создание приложения, использующего базовые возможности фреймворка Pylons — создание контроллеров, шаблонов, моделей; обработка форм, взаимодействие с базой данных; использование шаблонного движка и helper-ов.
Изложенный материал может помочь в дальнейшем освоении возможностей Pylons, которые здесь остались без внимания — URL routing, валидация форм, автоматизация установки готового приложения на сервер, интеграция с библиотеками prototype и script.aculo.us для создания AJAX-приложений и др.

Ссылки

Pylons — python фреймворк
Myghty — шаблонный движок
SQLAlchemy — движок для связи в базой данных
Обзор Pylons
Пробуем Pylons, часть 1: install, db setup
Пробуем Pylons, часть 2: views
Стандарт WSGI
Production deployment using supervisor Apache as a reverse proxy

1 2 3 4 5

Последние комментарии:

исправления Автор
2Ard
Спасибо за замечания, внес в статью исправления.

dymko
Спасибо, одна из очень немногих статей на русском языке.
И полезная, к тому же.
Было бы продолжение – было бы замечательно.
Т.к. по пилонам практически ничего нет, днём с огнём не сыщешь.

Ard
«Далее, в файле инициализируем метаданные для связи с базой данных и проассоциируем их с объектом, в виде которого данные из таблицы будут представлены в нашем приложении:»

не понятно о каком файле идет речь в разделе

Ard
Спасибо за туториал! Дело в освоении питона для веба движется :)
там у Вас ошибочка...

Снова добавим в контроллер “welcome” новое действие “add”, использующее этот шаблон:

def index(self):
return render_response('add-record.html')

Надо:
def add(self):

Обсудить (комментариев: 4)