Основы разработки на примере приложения онлайн-магазина
Содержание- Установка TurboGears
- Быстрый запуск
- Создание модели
- Управление моделью с помощью CatWalk
- Создание вида
- Создание контроллера
- Отображение продукта
- Исправление ошибок
- Магазинная корзина
- Добавление Ajax в корзину
- Заключение: Сравнение TurboGears и Django
- Ресурсы
- Об авторе
Уровень сложности: средний
Источник: IBM developerWorks
Йен Маурер, Senior Consultant, Brulant, Inc.
Во второй статье данного выпуска, мы продемонстрируем TurboGears, другую среду разработки Web-приложений в стиле шаблонов модель-вид-контроллер (model-view-controller - MVC) с открытым исходным кодом, работающий на языке программирования Python. Первая статья была введением в среду разработки Django, в этой статье будет показано использование TurboGears для создания приложения Web-магазина, а в заключении будет приведено сравнение Turbogears и Django.
Разработчики TurboGears называют этот проект «мега-средой разработки», поскольку она создана из нескольких, прежде существующих подпроектов. TurboGears помогает склеивать вместе несколько главных компонентов:
- MochiKit: библиотека JavaScript
- Kid: язык шаблонов
- CherryPy: Основная Web-среда разработки
- SQLObject: Объектно-реляционное преобразование (ORM)
Первый этап работы с TurboGears - проверка, установлен ли у вас Python. Последняя версия TurboGears требует Python 2.4. См. в разделе Ресурсы ссылку на домашнюю страницу Python.
Setuptools (инструменты установки) - новый проект от Python-сообщества, который намного упрощает процессы установки и обновления программного обеспечения (ПО) Python. ПО запаковано в файл-архив, называемый Egg (яйцо), также как у языка Java файл JAR (бак) или у языка Ruby файл GEM (жемчужина), и с помощью инструмента easy_install его можно скачать и установить.
TurboGears является одним из больших Python-проектов, где используется setuptools для распространения и установки. См. Ресурсы для ссылки на страницу setuptools и страницу для скачивания (download) TurboGears.
В данной статье используется последняя разработка версии на момент написания статьи (0.9a5), которую вы можете найти в предварительной странице скачивания:
easy_install -f http://www.turbogears.org/preview/download/index.html TurboGears
Чтобы получить последнюю устойчивую версию среды TurboGears (в настоящий момент 0.8.9), переместите опцию -f и адрес (URL) на сайт предварительного просмотра:
easy_install TurboGears
Проверка инструмента администратора среды TurboGears
После установки TurboGears необходимо получить инструмент администратора, tg-admin, доступный по вашему пути. Проверьте в командной строке, набрав tg-admin: Листинг 1. Использование инструмента администратора TurboGears
~/dev$ tg-admin TurboGears 0.9a5 command line interface Usage: /usr/bin/tg-admin [command] [options] Available commands: i18n Manage i18n data info Show version info quickstart Create a new TurboGears project shell Start a Python prompt with your database available sql Run the SQLObject manager toolbox Launch the TurboGears Toolbox update Update an existing turbogears project |
Чтобы начать проект TurboGears, используйте функцию tg-admin quickstart. Вас попросят написать как имя проекта, так и имя директории. Для данной статьи, мы создаём простой пример магазинной корзины (shopping cart) под названием TG Commerce, у которой есть имя пакета tgcommerce: Листинг 2. Запуск проекта в TurboGears
~/dev$ tg-admin quickstart Enter project name: TG Commerce Enter package name [tgcommerce]: Selected and implied templates: turbogears#turbogears web framework Variables: package: tgcommerce project: TG-Commerce Creating template turbogears Creating ./TG-Commerce/ ... (output snipped) ... |
TurboGears создает директорию проекта TG-Commerce с пакетом tgcommerce внутри. Теперь вы можете запустить тест-сервер, который идет вместе с вашим проектом: Листинг 3. Запуск тест-сервера
~/dev/TG-Commerce$ python start-tgcommerce.py ... (output snipped) ... 05/Mar/2006:11:31:54 HTTP INFO Serving HTTP on http://localhost:8080/ |
Просмотрите тестовую страницу по приведенному выше адресу (URL), а затем остановите сервер с помощью Ctrl-C.
SQLObject представляет собой библиотеку объектно-реляционного преобразования (ORM), которая позволяет разрабатывать объекты обновляющихся баз данных на языке Python. Вы определяете класс на Python, добавляете необходимые вам атрибуты (поля), но потом SQLObject управляет формированием нужного SQL для создания таблиц, вставляет новые записи и затем находит, обновляет или удаляет существующие записи.
SQLObject поддерживает несколько баз данных, включая MySQL, PostgreSQL, Firebird и другие. Вы можете найти более подробную информацию по SQLObject по следующей ссылке в Ресурсы.
В этом примере, мы используем SQLite в качестве простой базы данных. SQLite является легкой базой данных, не требующей конфигурации, и существует на диске, в виде простого файла. Чтобы использовать SQLite, установите библиотеку pysqlite с помощью setuptools:
easy_install pysqlite
Для конфигурации базы данных TurboGears задайте sqlobject.dburi в файле dev.cfg. Для SQLite, задайте путь, где вы хотите оставить файл базы данных: Листинг 4. Разработка файла конфигурации (dev.cfg)
sqlobject.dburi= "notrans_sqlite:///path/to/devdir/TG-Commerce/tgcommerce.database" server.environment="development" autoreload.package="tgcommerce" |
Быстрый старт TurboGears создает и заполняет файл model.py стандартным кодом. Сюда должны помещаться классы SQLObject. Наверху есть раздел, который устанавливает соединительный хаб (hub) базы данных: Листинг 5. Модель стандартного кода (model.py)
from sqlobject import * from turbogears.database import PackageHub hub = PackageHub("tgcommerce") __connection__ = hub |
Затем следуют классы моделей. Каждый из них представляет собой таблицу в базе данных, которые определяются атрибутами уровня классов, отображаемые в колонках базы данных. Эти атрибуты являются примерами из типов колонок SQLObject, которые включают базовые типы данных, такие как StringCol и CurrencyCol, и типы отношений, такие как ForeignKey и MultipleJoin.
Для данной (магазинной) корзины существует иерархический класс Category (Категория) и простой класс Product (Продукт). Иерархия категорий определяется исходным элементом («parent») ForeignKey и подкатегориями MultipleJoin. Листинг 6. Классы Category и Product (model.py, продолженный)
class Category(SQLObject): name = StringCol(length=64) parent = ForeignKey('Category', default=None) subcategories = MultipleJoin('Category', joinColumn='parent_id') products = MultipleJoin('Product') class Product(SQLObject): name = StringCol(length=64) sku = StringCol(length=64) price = CurrencyCol(notNone=True, default=0.0) category = ForeignKey('Category') |
Чтобы заверить модель, используйте команду tg-admin sql sql для изображения кода SQL, который создаст необходимые таблицы. Отметим, что SQLObject создает колонку id для каждой таблицы. Это происходит, когда первичный ключ не указан. См. Ресурсы для ссылок на подробную документацию по SQLObject. Листинг 7. Просмотр схемы базы данных с помощью команды 'tg-admin sql sql'
~/dev/TG-Commerce$ tg-admin sql sql Using database URI sqlite:///home/ubuntu/dev/TG-Commerce/tgcommerce.db CREATE TABLE category ( id INTEGER PRIMARY KEY, name VARCHAR(64), parent_id INT ); CREATE TABLE product ( id INTEGER PRIMARY KEY, name VARCHAR(64), sku VARCHAR(64), price DECIMAL(10, 2) NOT NULL, category_id INT ); |
Далее приведены классы Order (Порядок) и OrderItem (Товар), которые используются для записи корзины и ее товаров. Так как order является ключевым словом в SQL, имя таблицы для класса Order переопределяется в orders, используя sqlmeta, атрибут таблицы подкласса:
Листинг 8. Классы Order и OrderItem (model.py, продолженный)
class Order(SQLObject): items = MultipleJoin('OrderItem', joinColumn='order_id') class sqlmeta: table = 'orders' class OrderItem(SQLObject): quantity = IntCol(notNone=True) price = CurrencyCol(notNone=True) total = CurrencyCol(notNone=True) order = ForeignKey('Order') product = ForeignKey('Product') |
Используйте команду tg-admin sql create для создания таблиц базы данных:
Листинг 9 Создание таблиц базы данных с помощью команды 'tg-admin sql create'
~/dev$ tg-admin sql create Using database URI sqlite:///home/ubuntu/dev/TG-Commerce/tgcommerce.db |
Вы можете найти больше команд, используя команду tg-admin sql help, включая команду для сброса таблиц. Будьте очень осторожны при использовании этой команды, так как она удалит все ваши таблицы вместе с данными. По этой единственной причине, команда tg-admin должна быть заблокирована в среде продукции.
Управление моделью с помощью CatWalk
Начиная с версии 0.9, TurboGears стала поставляться с набором инструментариев, называемых Toolbox, которые включают в себя модель браузера (browser), называемого CatWalk. CatWalk спроектирован для разработчиков, которые хотят быстро создавать, обновлять и удалять данные из своих моделей с помощью инструмента GUI.
Toolbox запускается, как отдельный сервер и начинает работу при помощи команды tg-admin: Листинг 10. Запуск toolbox-сервера с помощью tg-admin
~/dev/TG-Commerce$ tg-admin toolbox ... (snip) ... 05/Mar/2006:15:01:33 HTTP INFO Serving HTTP on http://localhost:7654/ |
Если браузер не открывается автоматически, вы можете переместиться по адресу URL (http://localhost:7654/), заданному Toolbox-сервером, и нажать на ссылку CatWalk, чтобы его открыть.
Toolbox помогает, в первую очередь, разработчикам, а не конечным пользователям, и его лучше использовать, как вспомогательный инструмент при моделировании данных и начальной загрузке данных вашего приложения. Вы можете отключить toolbox-сервер с помощью Ctrl-C. Но вам не нужно это использовать в данном примере.
В изначальной настройке в TurboGears создание видов происходит при помощи языка шаблонов Kid XML. Так как Kid использует XML, все шаблоны должны быть правильно оформлены, иначе во время рендеринга возникнет ошибка. Kid также поддерживает наследование шаблонов, где унаследованные шаблоны могут расширяться, исходя из базовых шаблонов, поэтому можно создавать и управлять стандартным кодом в одном месте.
В среде TurboGears, файлы языка Kid располагаются в директории шаблонов с .kid-расширением. Изначально существует файл master.kid и файл welcome.kid, где master.kid является файлом базовых шаблонов, а welcome.kid наследует шаблоны от него при помощи атрибута py:extends в -тэге.
Чтобы создать новый шаблон, для начала мы рекомендуем скопировать или переименовать файл welcome.kid и использовать в качестве своего. Для настоящего примера сначала был создан шаблон категории, который отображает следующую информацию о данной категории:
- Имя категории (заголовок (title) и breadcrumb)
- Ссылки на breadcrumbs
- Ссылки на подкатегории (список)
- Ссылки на продукты (список)
Листинг 11. Файл со страницей категорий в формате kid-шаблона (category.kid)
Листинг 11 показывает некоторые из ключевых функциональных возможностей языка Kid:
- Наследование от master.kid с помощью py:extends
- Подстановка выражений в заголовке с помощью ${category.name}
- «Зацикливание» через предшествующий элемент (ancestors), через подкатегории и продукты с помощью py:for
- Использование slicing logic для переключения отображения предшествующих элементов в breadcrumbs
- Заполнение текста ссылок с помощью py:content
- Полная замена тэга span с помощью py:replace
Чтобы атрибут ancestors был верным, класс Category-модели необходимо изменить, включив в него метод _get_ancestors: Листинг 12. Добавление атрибута 'ancestors' метода 'get' в класс Category
class Category(SQLObject): name = StringCol(length=64) parent = ForeignKey('Category', default=None) subcategories = MultipleJoin('Category', joinColumn='parent_id') products = MultipleJoin('Product') def _get_ancestors(self): ancestor = self.parent while ancestor: yield ancestor ancestor = ancestor.parent |
Быстрый старт в TurboGears предоставляет проект вместе с модулем controllers.py, который является местоположением класса контроллера Root (корень). Для вашего приложения - это важный момент, теперь вы знаете, куда добавлять новые методы контроллера.
Далее приведены два примера метода контроллеров, которые связаны с категорией HTML-шаблона через декоратор (decorator) expose среды TurboGears. Методы контроллера возвращают словари, которые используются в качестве области имен, или контекста, во время рендеринга специального Kid-шаблона.
Листинг 13. Класс контроллера
from turbogears import controllers, expose class Root(controllers.Root): @expose("tgcommerce.templates.category") def index(self): from model import Category category = Category.selectBy(parent=None)[0] return dict(category=category) @expose("tgcommerce.templates.category") def category(self, categoryID): from model import Category category = Category.get(categoryID) return dict(category=category) |
В среде TurboGears, URL точно отображаются на методах и содержатся внутри контроллера Root. Корневой URL / указывает на специальный метод, называемый index. Добавляя метод под названием category к Root, он может быть доступен через адрес (URL) /category. Любой отправленный URL, не соответствующий данному методу, вызывает ошибку 404, в другом случае определяется метод default (по-умолчанию).
Далее приведены некоторые из возможных URL-сценариев и их результаты:
- /: отображает первую категорию без исходного элемента id
- /category?categoryID=2: отображает категорию с id двух исходных элементов (паттернов).
- /category/1: отображает категорию с id первого исходного элемента/парента (доступный формат, как в TG 0.9)
- /category: выдает ошибку 500, по причине отсутствия id категории.
- /xyz: выдает ошибку 404
Рисунок 2 показывает страницу отображения категории:
Для страницы отображения продукта, создается контроллер product, который отыскивает продукт в базе данных и пропускает его в Kid-шаблон product для рендеринга. Листинг 14. Добавление метода контроллера продукта
@expose("tgcommerce.templates.product") def product(self, productID): from model import Product product = Product.get(productID) return dict(product=product) |
Шаблон product . kid имеет таблицу информации о продуктах. Обратите внимание на использование форматирования строки в Python для отображения цены с двумя значащими цифрами:
Листинг 15. Файл страницы Category в виде kid-шаблона (product.kid)
|
Рисунок 3 показывает страницу отображения продуктов:
Единственное, что не предусматривает метод контроллера - ошибка SQLObjectNotFound, вызываемая get-методами SQLObject. Листинг 16 показывает рефакторинг (refactoring), который берет данную ошибку и снова выдает ее в виде исключения NotFound, которое посылает основную HTTP-ошибку 404: Листинг 16. Исправление ошибок, добавленных в класс контроллера
from model import Category, Product from sqlobject import SQLObjectNotFound from cherrypy import NotFound from turbogears import controllers, expose, url class Root(controllers.Root): @expose("tgcommerce.templates.category") def category(self, categoryID): try: category = Category.get(categoryID) except SQLObjectNotFound: raise NotFound() return dict(category=category) @expose("tgcommerce.templates.product") def product(self, productID): try: product = Product.get(productID) except SQLObjectNotFound: raise NotFound() return dict(product=product) |
Другой способ для исправления недостающих объектов - перенаправление вместо вызова ошибки 404. Осуществляется с помощью метода turbogears.redirect(...): Листинг 17. Пример перенаправления
from turbogears import redirect try: object = ClassName.get(objectID) except SQLObjectNotFound: raise redirect("/path_to_redirect") |
Чтобы магазинная корзина работала, вам необходимо активировать сессии для поддержки состояния между запросами. Сессии можно активировать в файле конфигураций session_filter.on, установив значение True. Листинг 18. Активация сессий в файле конфигураций (dev.cfg)
session_filter.on = True sqlobject.dburi= "notrans_sqlite:///path/to/devdir/TG-Commerce/tgcommerce.database" server.environment="development" autoreload.package="tgcommerce" |
Для отображения актуальной корзины магазина на каждой странице, вы можете поместить код HTML в файл с главным шаблоном, вместо того, чтобы его копировать и вставлять в каждую из страниц: Листинг 19. Корзина магазина, включенная в главный kid-шаблон (master.kid)
...
Shopping Cart:
items
($)
|
Вышеуказанное изменение шаблона само по себе не произойдет, так как нет значения cart в возвратных словарях из методов контроллера. Листинг 20 показывает изменение в методе products, который будет включать в себя корзину, только что сохраненную в сессии. Листинг 20. Возвращенный от контроллеров объект магазинной корзины
@expose("tgcommerce.templates.product") def product(self, productID): try: product = Product.get(productID) except SQLObjectNotFound: raise NotFound() return self.results(product=product) def results(self, cart=None, **kw): if not cart: cart = self.get_cart() kw['cart'] = cart return kw def get_cart(self): cartID = session.get("cartID", None) cart = None if cartID: try: cart = Order.get(cartID) except SQLObjectNotFound: pass if cart is None: cart = Order() session["cartID"] = cart.id return cart |
Метод results вызывает get_cart, который будет отыскивать настоящий объект Order или создаст новый, если он не существует или не найден. Это изменение произойдет также в методах index и category.
Чтобы данный пример корзины магазина действительно стал соответствовать нашим потребностям, функция добавления продукта в корзину осуществляется с помощью вызова Ajax без обновления страницы. Для непосвященных: Ajax представляет собой реализацию Web-шаблона и означает Asynchronous (Асинхронный) JavaScript + XML. Статья, в которой дается определение термина Ajax, есть в списке ссылок в ресурсах.
В этом примере используется язык JavaScript Object Notation (JSON), вместо XML, в качестве транспортного формата. JSON является более легким и поддерживается как TurboGears, так и MochiKit. Даже, несмотря на то, что использование асинхронного языка JavaScript вместе с JSON набирает популярность, имя Ajax все равно будет использоваться по той простой причине, что аббревиатура Ajaj представляется неблагозвучной.
Далее приведен метод контроллера add_to_cart, который получает заданный продукт и добавляет его в корзину прямо в сессии. Этот метод возвращает объект корзины вместе с итоговой ценой и количеством товаров в корзине: Листинг 21. Метод контроллера add_to_cart
@expose() def add_to_cart(self, productID): cart = self.get_cart() product = Product.get(productID) cart.add_product(product, 1) return self.results(cart=cart, price=cart.total_price, quantity=cart.total_quantity) |
Отметим, что метод expose не был исполнен вместе с именем шаблона. Так произошло потому, что вызов метода напрямую из браузера, и его рендеринг в HTML происходить не будет. Если вы видите страницу add_to_cart (http://localhost:8080/add_to_cart/1) непосредственно в браузере или с помощью инструмента, такого как curl, вам будут предоставлены сырые данные JSON: {"tg_flash":null, "price":24, "cart":{"id":24}, "quantity":1}.
Произвести JavaScript-вызов из клиента (браузера) в метод контроллера add_to_cart на сервере очень просто. MochiKit предоставляет функцию, называемую loadJSONDoc, которая берет URL и возвращает то, что известно как задержанный объект. Вы можете использовать этот задержанный объект для определения метода возврата, когда асинхронный вызов возвращает отклик. Листинг 22. Логика магазинной корзины на AJAX (cart.js)
function addToCart(productID) { var deferred = loadJSONDoc('/add_to_cart/' + productID); deferred.addCallback(updateCart); } function updateCart(results) { $("cart-amt").innerHTML = numberFormatter("#.00")(results["price"]); $("cart-qty").innerHTML = results["quantity"]; } |
Функция updateCart вызывается благодаря возврату, и значения JSON приходят в качестве ассоциативной матрицы переменных, называемой results. Функция обновляет количество и сумму товаров в корзине с помощью ID и атрибута innerHTML. Наконец, полезная функция MochiKit numberFormatter используется для установки значащих цифр цены.
Последнее, что нужно сделать, - добавить ссылку, которая вызывает верхнюю функцию addToCart, пропуская настоящий ID продукта. Вам также необходимо добавить файлы MochiKit.js и ваш файл cart.js в качестве скриптовых тэгов в ваш шаблон продукта: Листинг 23. Добавление в корзину JavaScript-вызова, добавленного к странице продуктов (product.kid)
...
|