Размер: 32116
Комментарий:
|
Размер: 34127
Комментарий:
|
Удаления помечены так. | Добавления помечены так. |
Строка 329: | Строка 329: |
== Проверка динамических маршрутов == Использование динамических маршрутов это хорошо, но во многих случаях имеет смысл проверить динамическую часть маршрута. Например, мы ожидаем целое число в нашем выше описанном маршруте для редактирования. Но если число с плавающей точкой, символы итд поступают, интерпретатор Python генерирует исключение, которое не то, что мы хотим. Для таких случаев, Bottle предлагает @valdiate декоратор, который проверяют "input" (ввод) до передачи его в функцию. Для того, чтобы применить проверки, дополним код следующим образом: {{{#!highlight python numbers=off from bottle import route, run, debug, template, request, validate ... @route('/edit/:no', method='GET') @validate(no=int) def edit_item(no): ... }}} Сначала мы импортировали "validate" из модуля Bottle , затем мы применяем @validate-декоратор. Прямо здесь, мы проверяем, если "no" является целым числом. В принципе, проверка работает со всеми типами данных, т.е. числа с плавающей точкой, списки т.д. Сохраните код и вызовите еще раз страницу, будет использовано значение "403 Forbidden" для ":no", например, с плавающей точкой. Вы получите не исключение, а "403 Forbidden" ошибка, которая говорит, что ожидалось целое число. |
Bottle
Bottle - это быстрый, простой и легкий WSGI микро веб-фреймворк для Python. Он распространяется в виде одного файла-модуля и не имеет никаких зависимостей, кроме стандартной библиотеки Python.
Учебник: Приложение Список-Задач
Этот учебник краткое введение в WSGI фреймфорк Bottle. Основной целью является то, что после прочтения этого учебника вы сможете создавать проекты использующие Bottle. В этом документе, будут рассмотрены не все возможности , но по крайней мере, основные и важные, такие, как маршрутизация, использование Bottle шаблонов для форматирования вывода и обработка GET / POST параметров.
Для понимания изложенного здесь материала не обязательно иметь базовые знания о WSGI, т.к. Bottle пытается скрыть механизмы работы WSGI от пользователя. Вы должны иметь чёткое понимание языка программирования Python . Кроме того, примеры используемые в учебнике извлекают и хранят данные в базе данных SQL, так что основные сведения о SQL помогут, но не являются ключевыми для понимания концепции Bottle. В некоторых примерах Bottle отправляет выходные данные браузеру отформатированными при помощи HTML. Таким образом, общее представление об HTML тегах будет полезным.
Во введении в Bottle, промежуточный код на Python максимально короткий для сохранения понимания. Кроме того, весь учебный код работает нормально, но не стоит его использовать его в "сыром" виде на общедоступном веб-сервере. Для того, его использовать лучше добавить например, больше обработок ошибок, защитить базу данных с помощью пароля, проверок и ошибок ввода т.д.
Содержание
Цели
В конце этого учебника мы получим простое веб-ориентированный Список-Задач. Список представляет собой для каждой записи текст( максимум 100 символов) и статус (0 для выполненного, 1 для открытой). Через веб-ориентированный интерфейс пользователь может открыть запись для просмотра, редактирования или добавить новую запись.
В время разработки, все страницы будут доступны только локально, но в дальнейшем будет показано как адаптировать приложение для "реального" сервера, включая использование с Apache’s mod_wsgi.
Bottle будет делать маршрутизацию и форматирование вывода, при помощи шаблонов. Элементы списка будут храниться в базе данных SQLite. Чтение и запись в / из базы данных будет осуществляться с помощью кода на Python.
В итоге мы будем иметь приложение со следующими страницами и функциональными возможностями:
• Стартовая страница http://localhost:8080/todo
• Добавление новых элементов в список: http://localhost:8080/new
• Страница для редактирования элементов: http://localhost:8080/edit/:no
• Проверки данных, заданных динамическими маршрутами с @validate в качестве декоратора
• Определять ошибки
Прежде чем мы начнём ...
Установка Bottle
Предположим, что у вас установлен Python (версия 2.5 или выше) и все, что вам нужно это установить Bottle в дополнение к этому. Bottle не имеет других зависимостей, кроме самого Python.
Вы можете установить Bottle вручную или использовать easy_install от Python: easy_install bottle
Программное обеспечение которое понадобится в дальнейшем
Так как мы используем SQLite3 в качестве базы данных, убедитесь, что он установлен. В системах Linux, большинство дистрибутивов имеют SQLite3 установленным по умолчанию. SQLite также доступен для Windows and MacOS X .
Кроме того, необходим Pysqlite, модуль Python для доступа к базам данных SQLite. Опять же, во многих дистрибутивах Linux предустановлен этот модуль (их часто называют "Python-sqlite3"), в противном случае можно просто установить вручную или через easy_install pysqlite .
Примечание: Многие старые системы имеют предустановленным sqlite2 . Все примеры тоже будут корректно работать с этой версией. Вам просто нужно импортировать соответствующий модуль Python под названием "SQLite" вместо "sqlite3", как оно используется в примерах ниже.
Создание базы данных SQL
Во-первых, мы должны создать базу данных, мы используем её в дальнейшем. Для этого запустите SQLite с помощью команды sqlite3 todo.db . Это позволит создать пустую базу данных под названием "todo.sql" и вы увидите приглашение SQLite , которое может выглядеть следующим образом: sqlite> . Прямо здесь, ввести следующие команды:
CREATE TABLE todo (id int PRIMARY KEY, task char(100) NOT NULL, status bool NOT NULL);
INSERT INTO todo (task,status) VALUES ('Read A-byte-of-python to get a good introduction into Python',0);
INSERT INTO todo (task,status) VALUES ('Visit the Python website',1);
INSERT INTO todo (task,status) VALUES ('Test various editors for and check the syntax highlighting',1);
INSERT INTO todo (task,status) VALUES ('Choose your favorite WSGI-Framework',0);
Первая строка создаёт таблицу которая называется "todo" с тремя столбцами "id", "task" и "status". "id" является уникальным идентификатором для каждой строки, который используется в дальнейшем для обращения к строкам. В графе "task" хранится текст, который описывает задачу, он может быть не более 100 символов. Наконец, колонка "status" используется для обозначения задачи как открыта (значение 1) или закрыта (значение 0).
Использование Bottle для веб-приложения Список-Задач (ToDo)
Теперь настало время применить Bottle с целью создания веб-приложения. Но во-первых, мы должны рассмотреть основную концепцию Bottle: маршруты.
Понимание маршрутов
В принципе, каждая страница отображается в браузере после динамической генерации когда вызывается адрес страницы. Таким образом, нет статического контента. Это именно то, что называется "route" в Bottle: некий адрес на сервере. Так, например, когда страница http://localhost:8080/todo вызывается из браузера, Bottle "перехватывает" вызов и проверяет, есть ли (Python) функция, определённая для маршрута(route) "todo". Если да, то Bottle будет выполнять соответствующий код Python и возвращает результат.
Первый шаг - Отображение всех открытых задач
Таким образом, после понимания концепции маршрутов, давайте создадим один первый. Цель состоит в том, чтобы увидеть все открытые элементы из Списка-Заданий(ToDo):
import sqlite3
from bottle import route, run
@route('/todo')
def todo_list():
conn = sqlite3.connect('todo.db')
c = conn.cursor()
c.execute("SELECT id, task FROM todo WHERE status LIKE '1'")
result = c.fetchall()
return str(result)
run()
Сохраните код "todo.py", предпочтительно в той же папке где и файл "todo.db". В противном случае, вам необходимо добавить путь к "todo.db" в параметрах sqlite3.connect().
Давайте посмотрим, что мы только что сделали: мы импортировали необходимый модуль "sqlite3" для доступа к базе данных SQLite и из Bottle мы импортировали "route" и "run". Оператор run() просто запускает веб-сервер находящийся в Bottle. По умолчанию веб-сервер обслуживает страницы на localhost и порту 8080. Кроме того, мы импортировали "route", функцию которая отвечает в Bottle за маршрутизацию. Как вы можете видеть, мы определили одну функцию " todo_list ()", с несколькими строками кода чтения из базы данных. Важным моментом является определение декоратора @route('/todo') непосредственно перед определением def todo_list(). Делая это, мы связываем эту функцию с маршрутом "/todo", так что каждый раз, когда браузер вызывает http://localhost:8080/todo , Bottle возвращает результат функции "todo_list ()". Так работает маршрутизации внутри Bottle.
На самом деле вы можете к функции связать более чем один маршрут.
Так следующий код
...
@route('/todo')
@route('/my_todo_list')
def todo_list():
...
тоже будет работать нормально. Что не будет работать, связывание одного маршрута с более чем одной функцией.
То, что вы увидите в браузере является данными которые описаны в параметрах return. В этом примере, мы должны преобразовать "result" в строку через функцию str() , т.к. Bottle ожидает строку или список строк в параметрах оператора return. Но здесь, в результате запроса к базе данных возвращается список кортежей, который является стандартом определеным в Python DB API .
Теперь, после понимания немного скрипта выше , настало время исполнить его и посмотреть результат самостоятельно. Помните о том, что в Linux-/ Unix подобных системах сначала файл "todo.py" необходимо сделать исполняемым . После этого достаточно просто запустить python todo.py и открыть страницу http://localhost:8080/todo в вашем браузере. Если Вы не ошиблись в написании скрипта, результат должен выглядеть следующим образом:
[(2, u'Visit the Python website'), (3, u'Test various editors for and check the syntax highlighting')]
Если это так - поздравляем!. Вы теперь успешной пользователь Bottle. В случае, если что-то не работает, и вам необходимо внести некоторые изменения в скрипт, не забудьте остановить Bottle который обслуживает страницу, в противном случае исправленный вариант не будет загружен.
На самом деле, будет не очень интересно и не приятно читать вывод . Это сырой результат возвращённый SQL-запросом.
Таким образом, в следующем шаге мы красивым способом отформатируем вывод . Но прежде, чем мы это делаем, мы делаем нашу жизнь легче.
Отладка и Авто-Обновление
Возможно, вы уже испытали Bottle, что отправляет краткое сообщение об ошибке в браузере, если что-то в сценарии не так, например, соединение с базой данных не работает. Для отладки полезно получить более подробную информацию. Это можно легко сделать, добавив следующее заявление в скрипт:
from bottle import run, route, debug
...
#add this at the very end:
debug(True)
run()
При включении "debug", вы получите полную трассировку стека интерпретатора Python, который обычно содержит полезную информацию для поиска ошибок. Кроме того, шаблоны (см. ниже) не будут кэшироваться, таким образом, изменения в шаблон вступит в силу без остановки сервера.
Обратите внимание, что опция debug(True) должна быть использована только при разработке, не должны использоваться в рабочей обстановке.
Дальнейшей полезным свойством является автоматическая перезагрузка, которая может быть включена по изменению параметров run()
run(reloader=True)
Это позволит автоматически выявлять изменения в скрипте и загружать новый вариант, когда он будет вызван повторно, без необходимости остановки и запуска сервера.
Опять же, в основном предполагается использовать функцию при разработке, а не на рабочих системах.
Шаблоны для форматирования вывода
Теперь давайте посмотрим как оформить вывод скрипта в надлежащем формате.
На самом деле Bottle рассчитывает получить строку или список строк из функции и возвращает их при помощи встроенного сервера в браузер. Bottle не беспокоиться о содержании самой строки, поэтому она может быть отформатирована как текст с разметкой HTML.
Шаблоны хранятся как отдельные файлы, имеющие расширение ".tpl". Шаблоны могут содержать любой тип текста (который будет, скорее всего, HTML-разметка, смешанная с операторами Python). Кроме того, шаблоны могут принимать аргументы, например, в результат запроса к базе данных, который затем будет красиво отформатированный в шаблоне.
Прямо здесь, мы будем передавать результат нашего запроса показа открытых позиций в Списке Задач, в виде в простой таблицы с двумя столбцами: первый столбец будет содержать идентификатор элемента, второй столбец текста. Результирующие множество, как мы видели выше, список кортежей, каждый кортеж содержит один набор результатов.
Чтобы включить шаблон в нашем примере, просто добавьте следующие строки:
from bottle import from bottle import route, run, debug, template
...
result = c.fetchall()
c.close()
output = template('make_table', rows=result)
return output
...
Таким образом, мы делаем две вещи: во-первых, мы импортируем "template" из Bottle, с тем чтобы иметь возможность использовать шаблоны. Во-вторых, назначить вывод как шаблон "make_table" переменной "output", которая затем возвращается. В дополнение к вызову шаблона, назначить "result", который мы получили в результате запроса к базе данных, для переменной "rows", которая в дальнейшем используется в шаблоне. При необходимости можно назначить более чем одну переменную / значение в шаблон.
Шаблоны всегда возвращает список строк, поэтому нет необходимости дополнительного конвертирования. Конечно, мы можем сохранить одну строку кода записав return template('make_table', rows=result) , что дает тот же результат, как указано выше.
Теперь настало время, чтобы написать соответствующий шаблон, который выглядит следующим образом:
%#template to generate a HTML table from a list of tuples (or list of lists, or tuple of tuples or ...)
<p>The open items are as follows:</p>
<table border="1">
%for row in rows:
<tr>
%for r in row:
<td>{{r}}</td>
%end
</tr>
%end
</table>
Сохраните код как "make_table.tpl" в тот же каталог, где сохранен"todo.py" .
Давайте посмотрим на код: Каждая строка, начинающиеся с% интерпретируется как код Python. Обратите внимание, что, конечно, разрешается только правильные определения Python , в противном случае шаблон будет вызывать исключения, как и любой другой код Python. Другие строки просто HTML-разметка.
Как вы видите, мы используем Python оператор "for" два раза, чтобы перебрать "rows". Как мы видели выше, "rows" является переменной, которая содержит результат запроса к базе данных, т.е. список кортежей.Первый "for"-оператор получает доступ к кортежам в списке, второй элементы в кортеже, которые находятся в каждой ячейке таблицы. Важным является тот факт, что вам нужно дополнительно закрыть все "for" и "if", "while" и т.д. оператором %end , в противном случае результат может быть не тем, что вы ожидаете.
Если вам нужно получить доступ к переменной внутри шаблона и вне кода Python , нужно поместить его в двойные фигурные скобки. Это говорит шаблону вставить фактическое значение переменной правильное место.
Выполните скрипт еще раз и посмотрите на вывод. По-прежнему не очень хорошо, но по крайней мере лучше читается, чем список кортежей. Конечно, вы можете приукрасить с помощью очень простой HTML-разметки , например, с использованием in-line стилей, чтобы улучшить вывод.
Использование GET и POST значений
Мы можем просмотреть все открытые Задачи надлежащим образом, теперь мы переходим к следующему шагу, который является добавлением новых пунктов в Список Задач. Новый пункт, должен быть получен из обычной HTML-формы, которая отправляет свои данные через GET-метод.
Чтобы сделать это, мы сначала добавляем новый маршрут на наш скрипт и объясняем маршруту, что он должен получить GET-данные:
from bottle import route, run, debug, template, request
...
return template('make_table', rows=result)
...
@route('/new', method='GET')
def new_item():
new = request.GET.get('task', '').strip()
conn = sqlite3.connect('todo.db')
c = conn.cursor()
query = "INSERT INTO todo (task,status) VALUES ('%s',1)" %new
c.execute(query)
conn.commit()
c.execute("SELECT last_insert_rowid()")
new_id = c.fetchone()[0]
c.close
return '<p>The new task was inserted into the database, the ID is %s</p>
Чтобы получить доступ к GET (или POST) данным, мы должны импортировать "request" из Bottle. Чтобы назначить фактические данные переменной, мы используем определение request.GET.get('task','').strip() , где "task" это имя GET-данного, к которому мы хотим получить доступ. Вот и все. Если ваши GET-данные содержат более одной переменной, несколько request.GET.get() могут быть использованы и назначены для других переменных.
В остальной части кода просто обработка полученных данных: запись в базу данных, получение соответствующего идентификатора из базы данных и формирование вывода.
Но где мы можем получить GET-данные? Ну, мы можем использовать статические HTML страницы содержащие форму. Или, что мы делаем сейчас, заключается в использовании шаблона для вывода, когда вызывается маршрут "/new" без GET-данных.
Код, необходимо будет расширить до:
...
@route('/new', method='GET')
def new_item():
if request.GET.get('save','').strip():
new = request.GET.get('task', '').strip()
conn = sqlite3.connect('todo.db')
c = conn.cursor()
query = "INSERT INTO todo (task,status) VALUES ('%s',1)" %new
c.execute(query)
conn.commit()
c.execute("SELECT last_insert_rowid()")
new_id = c.fetchone()[0]
c.close
return '<p>The new task was inserted into the database, the ID is %s</p>' %new_id
else:
return template('new_task.tpl')
...
"new_task.tpl" выглядит следующим образом:
<p>Add a new task to the ToDo list:</p>
<form action="/new" method="GET">
<input type="text" size="100" maxlength="100" name="task">
<input type="submit" name="save" value="save">
</form>
Вот и все. Как вы видите, в этот раз шаблон простой HTML .
Теперь мы можем расширить наш Cписок-Задач.
Кстати, если вы предпочитаете использовать POST-данные: Это работает точно так же, просто используйте вместо request.POST.get().
Изменение существующих элементов
Последний пункт Списка Задач, это позволить редактирование существующих элементов.
С помощью маршрутов, которые мы знаем до сих пор это возможно, но может быть чуть сложнее. Но Bottle знает так называемых "динамические маршруты", что делает эту задачу простой.
Основные определения для динамического маршрута выглядит следующим образом:
@route('/myroute/:something')
Ключевой момент здесь состоит в использовании двоеточия. Это говорит Bottle принять за ":something" любую строку, до следующего слэша. Кроме того, значение "something" будут переданы функции, установленного для этого маршрута, поэтому данные могут быть обработаны внутри функции.
В нашем Списке Задач, мы создадим маршрут @route('/edit/:no) , где "no" идентификатор элемента для редактирования.
Код выглядит так:
@route('/edit/:no', method='GET')
def edit_item(no):
if request.GET.get('save','').strip():
edit = request.GET.get('task','').strip()
status = request.GET.get('status','').strip()
if status == 'open':
status = 1
else:
status = 0
conn = sqlite3.connect('todo.db')
c = conn.cursor()
query = "UPDATE todo SET task = '%s', status = '%s' WHERE id LIKE '%s'" % (edit,status,no)
c.execute(query)
conn.commit()
return '<p>The item number %d was successfully updated</p>' %no
else:
conn = sqlite3.connect('todo.db')
c = conn.cursor()
query = "SELECT task, status FROM todo WHERE id LIKE '%d'" %no
c.execute(query)
cur_data = c.fetchone()
return template('edit_task', old = cur_data, no = no)
Это в основном почти то же, что мы уже делали раньше при добавлении новых элементов, как использование "GET"-данных и т.д. Основным изменением здесь является использование динамического маршрута":no", который проходит здесь как номер соответствующей функции. Как видите, "no" используется в функции чтобы получить доступ к нужной строке данных в базе данных.
Шаблон "edit_task.tpl" вызываемый в функции выглядит следующим образом:
%#template for editing a task
%#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item
<p>Edit the task with ID = {{no}}</p>
<form action="/edit/{{no}}" method="get">
<input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br/>
<input type="submit" name="save" value="save">
</form>
Опять же, этот шаблон представляет собой смесь определений Python и HTML, как уже говорилось выше.
Последнее слово об динамических маршрутах: вы даже можете использовать регулярные выражения для динамического маршрута. Но эта тема обсуждается не здесь.
Проверка динамических маршрутов
Использование динамических маршрутов это хорошо, но во многих случаях имеет смысл проверить динамическую часть маршрута. Например, мы ожидаем целое число в нашем выше описанном маршруте для редактирования. Но если число с плавающей точкой, символы итд поступают, интерпретатор Python генерирует исключение, которое не то, что мы хотим.
Для таких случаев, Bottle предлагает @valdiate декоратор, который проверяют "input" (ввод) до передачи его в функцию. Для того, чтобы применить проверки, дополним код следующим образом:
from bottle import route, run, debug, template, request, validate
...
@route('/edit/:no', method='GET')
@validate(no=int)
def edit_item(no):
...
Сначала мы импортировали "validate" из модуля Bottle , затем мы применяем @validate-декоратор. Прямо здесь, мы проверяем, если "no" является целым числом. В принципе, проверка работает со всеми типами данных, т.е. числа с плавающей точкой, списки т.д.
Сохраните код и вызовите еще раз страницу, будет использовано значение "403 Forbidden" для ":no", например, с плавающей точкой. Вы получите не исключение, а "403 Forbidden" ошибка, которая говорит, что ожидалось целое число.