Chapter 3: Обзор

Обзор

Начало работы

Linux
Mac
Windows

web2py поставляется в виде бинарных пакетов для Windows и Mac OS X. В этот бинарный пакет включен интерпретатор языка Python, поэтому вам нет необходимости предустанавливать его. Существует также версия исходного кода, которая работает в Windows, Mac, Linux и других Unix подобных системах. Пакет из исходного кода предполагает, что интерпретатор языка Python уже установлен на компьютер.

Web2py не требует инсталляции. Для запуска распакуйте скачанный zip файл для вашей операционной системы и запустите файл web2py.

В Unix и Linux (исходный дистрибутив), запустите:

python web2py.py

В OS X (бинарный дистрибутив), запустите:

open web2py.app

На Windows (бинарный web2py дистрибутив), запустите:

web2py.exe

На Windows (исходный web2py дистрибутив), запустите:

c:/Python27/python.exe web2py.py

Внимание, чтобы запустить web2py на Windows, из исходных кодов необходимо установить в первую очередь Mark Hammond's win32 расширение из http://sourceforge.net/projects/pywin32/.

Программа web2py позволяет использовать различные опции командной строки, о которых мы поговорим позднее.

По умолчанию во время запуска web2py отображает окно запуска, затем виджет графического интерфейса, который попросит вас выбрать: пароль администратора, ip адрес сетевого интерфейса для использования веб сервером, и номер порта для обслуживания запросов. По умолчанию, web2py запускает свой веб сервер по адресу 127.0.0.1:8000 (порт 8000 на localhost), но вы можете запустить его на любом другом доступном IP-адресе и порту. Для уточнения доступных ip адресов в вашей сети вы можете воспользоваться командами ipconfig в Windows или ifconfig в OS X и Linux. С настоящего момента мы предполагаем, что web2py запущен на localhost (127.0.0.1:8000). Используйте 0.0.0.0:80 для запуска web2py публично на любом из ваших сетевых интерфейсов.

image

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

Административный интерфейс - приложение admin, доступен только с локального хоста если не запущено за веб сервером Apache используя модуль mod_proxy. Если приложение admin обнаруживает работу прокси-сервера, то устанавливается куки сессии для обеспечения безопасности, и вход в административный интерфейс работает только если связь между браузером и сервером осуществляется по протоколу HTTPS (это еще одна мера безопасности). Все взаимодействия между клиентом и административным интерфейсом всегда должны быть либо локальными, либо зашифрованными; в противном случае злоумышленник может воспользоваться атакой man-in-the middle или replay атакой и выполнить произвольный код на сервере.

После того, как пароль администрирования был установлен, web2py запускает веб-браузер на странице:

http://127.0.0.1:8000/

Если на компьютере нет браузера по умолчанию, откройте веб браузер и введите URL http://127.0.0.1:8000.

image

Нажав на "Административный интерфейс" вы перейдете в административный интерфейс.

image

Пароль администратора это пароль, который вы выбрали при запуске. Заметьте, администратор в системе только один, поэтому только один пароль администратора. По соображениям безопасности, разработчику предлагается набирать новый пароль каждый раз, когда web2py запускается, если не была указана опция <recycle>. Это отличается от механизма аутентификации в приложениях web2py.

После входа администратора в web2py, браузер перенаправляется на страницу "site".

image

Эта страница содержит все установленные приложения web2py и позволяет администратору управлять ими. Web2py поставляется с тремя приложениями:

admin
examples
welcome
scaffolding

  • Приложение admin, это приложение, которое используется прямо сейчас.
  • Приложение examples, с онлайн интерактивной документацией и копией web2py официального сайта.
  • Приложение welcome. Это основной шаблон для любого другого приложения web2py. Он упоминается как скаффолдинговое приложение. Это то самое приложение, которое приветствует пользователя при запуске.
appliances

Готовые к использованию web2py приложения упоминаются как web2py приспособление (appliances). Вы можете скачать большинство приспособлений в свободном доступе с [appliances] . web2py пользователям рекомендуется передавать новые приспособления, либо в форме с открытым исходным кодом или с закрытым исходным кодом (скомпилированное и упакованное).

Со страницы site приложения admin, вы можете выполнить следующие операции:

  • Установить приложение, заполнив форму с правой стороны страницы. В форме необходимо указать имя приложения, место расположения пакета приложения или URL адрес, где расположено приложение, и нажать кнопку "Установить".
  • Удалить приложение, нажав на соответствующую кнопку. Существует страница подтверждения.
  • Создать новое приложение, выбрав имя и нажав кнопку "Создать".
  • Упаковать приложение для распространения, нажав на соответствующую кнопку. Загруженное приложение представляет собой tar файл, содержащий все, в том числе базу данных. Вам не следует распаковать файл; он автоматически распаковывается с помощью web2py при установке через admin.
  • Очистить от временных файлов приложения, таких как файлы сессии, ошибок и кэш-файлов.
  • Включить/Выключить каждое приложение. Когда приложение отключено его невозможно вызвать удаленно, но оно не отключается от локального хоста. Это означает, что отключенные приложения по-прежнему могут быть доступны за прокси-сервером. Приложение является отключенным посредством создания файла с именем "DISABLED" в папке приложения. Пользователи, которые пытаются получить доступ к отключенному приложению будет получать сообщение об ошибке 503 HTTP. Вы можете использовать routes_onerror, чтобы настроить страницу ошибок.
  • Править приложение.

Когда Вы создаете новое приложение с помощью admin, оно запускается как клон скаффолдинг-приложения "welcome" вместе с "models/db.py", что создает базу данных SQLite, подключается к ней, создает Auth, Crud, и Service, и конфигурирует его. Оно также обеспечивается файлом "controller/default.py", который предоставляет действия "index", "download", "user" для управления пользователями, и "вызова" для обслуживания. В дальнейшем мы предполагаем, что эти файлы были удалены; мы будем создавать приложения с нуля.

web2py также поставляется вместе с Мастером, обсуждаемым далее в этой главе, который помогает написать вам альтернативный скаффорлдиг-код на основе доступных в Интернете макетов и плагинов, а также высокоуровневого описания моделей.

Простые примеры

Скажем Привет

index

Здесь, в качестве примера, мы создадим простое веб-приложение, которое отображает сообщение "Hello from MyApp" пользователю. Мы будем называть это приложение "myapp". Мы также добавим счетчик, который подсчитывает, сколько раз тот же самый пользователь посетил страницу.

Вы можете создать новое приложение, просто введя его название в форме, расположенной в правом верхнем углу страницы site приложения admin.

image

После того, как вы нажмете [Создать], приложение создается как копия встроенного приложения welcome.

image

Чтобы запустить новое приложение, посетите:

http://127.0.0.1:8000/myapp

Теперь у вас есть копия welcome приложения.

Чтобы изменить приложение, нажмите на кнопку Правка для вновь созданного приложения.

Страница Правка говорит вам, что находится внутри приложения. Каждое web2py приложение состоит из нескольких файлов, большинство из которых попадают в одну из шести категорий:

  • Модели: описывает способ предоставления данных.
  • Контроллеры: описывает логику приложения и рабочий процесс.
  • Представления: описывает представление данных.
  • Языки: описывает, как перевести представление приложения на другие языки.
  • Модули: модули Python, которые принадлежат приложению.
  • Статические фалы: статические изображения, CSS файлы[css-w,css-o,css-school] , JavaScript файлы[js-w,js-b], и т.д.
  • Плагины: группы файлов, предназначенных для совместной работы.

Все файлы аккуратно организованы в соответствии с шаблоном проектирования Модель-Представление-Контроллер. Каждая секция на странице Правка соответствует подпапкам в папке приложения.

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

Каждый файл в секции ссылается на файл расположенный в подпапке приложения. Используя административный интерфейс можно производить любые операции с файлом (создание, редактирование, удаление), также можно производить эти операции, используя ваш любимый текстовый редактор.

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

Контроллеры содержат в себе логическую схему работы приложения. Каждый URL-адрес связывается с вызовом одной из функций (действий) в контроллерах. Существуют два контроллера по умолчанию: "appadmin.py" и "default.py". appadmin предоставляет интерфейс администрирования базы данных; нам это не нужно сейчас. "default.py" это контроллер, который вам нужно отредактировать, один из тех, который вызывается по умолчанию, когда ни один контроллер не указан в URL-адресе. Отредактируйте "index" функцию в файле данного контроллера следующим образом:

def index():
    return "Hello from MyApp"

Здесь онлайн редактор выглядит следующим образом:

image

Сохраните его и вернитесь на страницу Правка. Нажмите на ссылку index, чтобы посетить вновь созданную страницу.

Когда вы посещаете URL

http://127.0.0.1:8000/myapp/default/index

вызывается действие index в контроллере default приложения myapp. Оно возвращает строку, которую браузер отображает для нас. Это должно выглядеть следующим образом:

image

Сейчас, отредактируйте функцию "index" написав в нее следующее:

def index():
    return dict(message="Hello from MyApp")

Также на странице Правка, отредактируйте представление "default/index.html" (файл представления ассоциируется с действием) и полностью замените существующее содержимое этого файла на следующее:

<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
   </body>
</html>

Теперь действие возвращает словарь в котором определяется message. Когда действие возвращает словарь, web2py ищет представление с именем

[controller]/[function].[extension]

и выполняет его. Здесь [extension] является запрашиваемое расширение. Если не указано никакого расширения, то по умолчанию используется "html", и это как раз то, что предполагается в нашем примере. В соответствии с этим предположением, представлением является HTML-файл, в который встраивается код Python с помощью специальных тэгов {{ }}. В частности, для нашего примера код {{=message}} дает указание web2py заменить код тега на значение message, которое возвращается действием. Заметить, что message здесь не web2py ключевое слово, а определяется в действии. До сих пор мы не использовали никаких web2py ключевых слов.

Если web2py не находит запрашиваемый шаблон представления, то использует шаблон представления "generic.html" который есть в любом приложении.

Mac Mail
Google Maps
jsonp
Если указано расширение отличное от "html"(например "json"), и в файл view "[controller]/[function].json" не найден, то web2py использует общий файл view "generic.json". web2py содержит файлы generic.html, generic.json, generic.jsonp, generic.xml, generic.rss, generic.ics (для Mac Mail Calendar), generic.map (для встраивания Google Maps), и generic.pdf (основаный на fpdf). Эти общие представления могут быть изменены для каждого приложения в отдельности, а также могут быть легко добавлены дополнительные шаблоны Представлений.

Общие файлы views это средства разработчика. В конечном продукте каждое действие должно иметь свой собственный файл представления. На самом деле, по умолчанию общие файлы представления активируются только для localhost.

Вы можете указать свой файл отображения, указав в коде response.view = 'default/something.html'

Более подробно об этом читайте в Главе 10.

Если вы вернетесь на страницу "Правка" и нажмете index, то вы увидите следующую HTML-страницу:

image

Панель инструментов для отладки

toolbar

В целях отладки вы можете вставить

{{=response.toolbar()}}

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

Давайте подсчитаем

session

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

Web2py автоматически и прозрачно отслеживает пользователей используя сессии и куки. Для каждого нового пользователя web2py создает сессию и назначает уникальный "session_id". Сессия это контейнер для переменных который располагается на серверной стороне. Уникальный идентификатор посылается браузером используя куки. Когда посетитель запрашивает страницу этого же приложения, браузер посылает куки снова, и соответствующая сессия которая была у пользователя восстанавливается.

Для того что бы использовать сессии отредактируйте контроллер default:

def index():
    if not session.counter:
        session.counter = 1
    else:
        session.counter += 1
    return dict(message="Hello from MyApp", counter=session.counter)

Обратите внимание counter не ключевое слово web2py, однако session ключевое. Мы указываем web2py проверять наличие переменной счетчика (counter) в текущей сессии, и если его нет то создать эту переменную и установить значение переменной равной 1. Если же переменная счетчик присутствует, мы указываем web2py инкрементировать счетчик на 1. В конце мы пропускаем значение счетчика в словарь, который используется в шаблоне представления.

Более компактный код этой же функции:

def index():
    session.counter = (session.counter or 0) + 1
    return dict(message="Hello from MyApp", counter=session.counter)

Отредактируем файл представления view и добавим в него строку, которая отобразит значение счетчика:

<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
      <h2>Number of visits: {{=counter}}</h2>
   </body>
</html>

Каждый раз когда вы посещаете страницу index вы должны видеть примерно следующую HTML страницу:

image

Счётчик ассоциируется с каждым посетителем, и инкрементируется каждый раз когда посетитель перезагружает страницу. Разные посетители имею свои разные счетчики. Проверить это вы можете, открыв приложение в разных веб браузерах.

Запоминаем имя посетителя

form
request.vars

Создадим 2 страницы (first и second), на первой странице (first) создадим форму, которая будет спрашивать имя посетителя, и направлять посетителя на вторую страницу (second), которая будет приветствовать посетителя по его имени.

yUML diagram

Напишите нижеследующее действие в контроллер default:

def first():
    return dict()

def second():
    return dict()

Далее создайте файл отображения "default/first.html" для этого действия и впишите в него следующий код:

{{extend 'layout.html'}}
<h1>What is your name?</h1>
<form action="{{=URL('second')}}">
  <input name="visitor_name" />
  <input type="submit" />
</form>

И наконец, создайте файл отображения "default/second.html" для второго действия:

{{extend 'layout.html'}}
<h1>Hello {{=request.vars.visitor_name}}</h1>
layout

В обоих представлениях мы расширили основной макет "layout.html", который идет с web2py. Макет подерживает внешний вид и поведение обеих страниц в соответствии. Файл макета может быть легко отредактирован и заменен, так как он содержит в основном HTML код.

Если вы сейчас посетите первую страницу, то после ввода вашего имени:

image

и отправив форму, вы получите приветствие:

image

Постбэки (Postbacks)

redirect
URL
postback

Механизм который мы использовали очень хорош, но он не соответствует стилю хорошего программирования. А именно все вводимые данные должны быть проверены, и в предыдущем случае задача проверки правильности введенных данных в форму ложилась на действие second. Таким образом, действие, которое выполняет проверку отличается от действия, сформировавшего форму. Это стремится вызвать избыточность в коде.

Лучший образец программирования это когда отправка данных осуществляется в том же действии, которое сгенерировала форму, нашем примере это действие "first". Действие "first"должно получить данные, обработать их, сохранить на сервере, и перенаправить посетителя веб страницы на другую страницу "second", которая извлекает переменные. Этот механизм называется postback.

yUML diagram

Внесите следующие изменения в контроллер default.py

def first():
    if request.vars.visitor_name:
        session.visitor_name = request.vars.visitor_name
        redirect(URL('second'))
    return dict()

def second():
    return dict()

Далее измените файл отображения "default/first.html":

{{extend 'layout.html'}}
What is your name?
<form>
  <input name="visitor_name" />
  <input type="submit" />
</form>

и файл "default/second.html" в нем мы заменим request.vars (полученные переменные) на данные сессии session:

{{extend 'layout.html'}}
<h1>Hello {{=session.visitor_name or "anonymous"}}</h1>

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

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

Обратите внимание, что если действие "second" вызывается перед заданием имени посетителя, оно будет отображать "Hello anonymous", потому что session.visitor_name возращает None. В качестве альтернативы мы могли бы добавить следующий код в контроллере (внутрь second функции):

if not request.function=='first' and not session.visitor_name:
    redirect(URL('first'))

Это механизм по месту который вы можете использовать для обеспечения авторизации на контроллерах, хотя в Главе 9 вы можете увидеть более мощный метод.

FORM
INPUT
requires
IS_NOT_EMPTY
accepts

Web2py позволяет генерировать формы (без описаниях их в файлах html), включающие в себя проверку вводимых данных. Вообще в web2py есть следующие помощники (FORM, INPUT, TEXTAREA, и SELECT/OPTION) c похожими именами тегов HTML. Они могут быть использованы для создания формы либо в контроллере или в представлении.

Например, вот один из возможных способов переписать действие first:

def first():
    form = FORM(INPUT(_name='visitor_name', requires=IS_NOT_EMPTY()),
              INPUT(_type='submit'))
    if form.process().accepted:
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

В этом коде мы указываем, что форма содержит 2 тега INPUT. В атрибутах указывается имя аргумента указанного с символом подчеркивания в начале. Аргумент requires это не атрибут тега (видите он не начинается с символа подчеркивания), но он является валидатором - проверкой входящих данных для значений visitor_name.

Вот еще лучший способ создать такую же форму:

def first():
    form = SQLFORM.factory(Field('visitor_name',
                                 label='what is your name?',
                                 requires=IS_NOT_EMPTY()))
    if form.process().accepted:
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

Объект form может быть легко сериализован в HTML путем встраивания его в файл "default/first.html"

{{extend 'layout.html'}}
{{=form}}

Метод form.process() добавляет проверку и возвращает форму самому себе. Переменная form.accepted устанавливается в True если форма была обработана и прошла проверку. Если форма прошла проверку, то значения переменных сохраняются в сессии и перенаправляются так же. Если же данные в форме (а в данном случае сама форма) не прошли проверку то посетителю отображается та же страница, но содержащая указания на ошибки, как показано ниже:

image

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

Во всех наших примерах мы использовали сессию, чтобы передать имя пользователя из действия first в действие second. Мы могли бы использовать другой механизм и передать данные как часть URL переадресации:

def first():
    form = SQLFORM.factory(Field('visitor_name', requires=IS_NOT_EMPTY()))
    if form.process().accepted:
        name = form.vars.visitor_name
        redirect(URL('second',vars=dict(name=name)))
    return dict(form=form)

def second():
    name = request.vars.name or redirect(URL('first'))
    return dict(name=name)

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

Интернационализация

Ваш код может включать в себя жестко закодированные строки, такие как "What is your name?". Вам следует иметь возможность настройки строк без редактирования кода и, в частности, вставки переводов для этих строк на разных языках. Таким образом, если посетитель имеет предпочитаемый язык браузера, установленный на "Итальянский", то web2py будет использовать итальянский перевод для строк, если он доступен. Эта особенность web2py называется "интернационализация" и будет описано более подробно в следующей главе.

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

"What is your name?"

с помощью оператора T:

T("What is your name?")

Вы можете также пометить нуждающиеся в переводе строки, жестко прописанные в коде представлений. Например

<h1>What is your name?</h1>

становится

<h1>{{=T("What is your name?")}}</h1>

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

После того, как строки определены и размечены, web2py берет на себя заботу о почти всем остальном. Интерфейс администратора также предоставляет страницу, на которой вы можете перевести каждую строку на те языки, которые Вы желаете поддерживать.

web2py включает в себя мощный движок плюрализации (для генерации множественного числа слов), который описан в следующей главе. Он интегрирован вместе с движком интернационализации и рендерером markmin.

Блог с изображениями

upload

Здесь, в качестве другого примера, мы желаем создать веб-приложение, которое позволяет администратору добавлять изображения и давать им имя, а также позволяет посетителям веб-сайта просматривать именованные изображения и отправлять комментарии (посты).

Как и прежде, на странице site page в admin, создайте новое приложение под названием images, и перейдите на страницу Правка:

image

Мы начнем с создания модели, предоставляющей постоянные данные в приложении (изображения для загрузки, их имена и комментарии). Во-первых, вам необходимо создать / отредактировать файл модели, который, из-за отсутствия воображения, мы называем "db.py". Мы предполагаем, что код ниже заменит существующий код в "db.py". Модели и контроллеры должны иметь .py расширение, так как они являются кодом Python. Если расширение не предусмотрено, оно будет дополнено web2py. Представления наоборот имеют .html расширение, так как они в основном содержат HTML-код.

Удалите модель "menu.py".

Отредактируйте "db.py" файл, нажав соответствующую "edit" кнопку:

image

и введите следующее:

IS_EMAIL
IS_NOT_EMPTY
IS_IN_DB

db = DAL("sqlite://storage.sqlite")

db.define_table('image',
   Field('title', unique=True),
   Field('file', 'upload'),
   format = '%(title)s')

db.define_table('post',
   Field('image_id', 'reference image'),
   Field('author'),
   Field('email'),
   Field('body', 'text'))

db.image.title.requires = IS_NOT_IN_DB(db, db.image.title)
db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')
db.post.author.requires = IS_NOT_EMPTY()
db.post.email.requires = IS_EMAIL()
db.post.body.requires = IS_NOT_EMPTY()

db.post.image_id.writable = db.post.image_id.readable = False

Давайте проанализируем это строка за строкой.

Строка 1 определяет глобальную переменную с именем db, представляющую соединение с базой данных. В этом случае подключение к базе данных SQLite хранится в файле "applications/images/databases/storage.sqlite". При использовании SQLite, если файл базы данных не существует, он будет создан. Вы можете изменить имя файла, а также имя глобальной переменной db, но удобно дать ему такое же имя, чтобы сделать его легким для запоминания.

Строки 3-6 определяют таблицу "image". define_table является методом db объекта.Первый аргумент, "image", это определенное нами имя таблицы. Остальные аргументы это поля, принадлежащие к этой таблице. Данная таблица имеет поле с названием "title", поле с названием "file", и поле с названием "id" который служит в качестве первичного ключа таблицы ("id" явным образом не объявлено, поскольку все таблицы имеют поле id по умолчанию). Поле "title" это строка, а поле "file" имеет тип "upload". "upload" представляет собой особый тип поля, используемый на Абстрактном уровне данных web2py (DAL) для хранения имен загруженных файлов. web2py умеет загружать файлы (с помощью потоковой передачи, если они большие), переименовывать их безопасно, и сохранять их.

Когда таблица определена, web2py принимает одно из нескольких возможных действий:

  • Если таблица не существует, то таблица создается;
  • Если таблица существует и не соответствует определению, то таблица изменяется соответствующим образом, а если поле имеет другой тип, то web2py попытается преобразовать его содержимое;
  • Если таблица существует и соответствует определению, то web2py ничего не делает.

Такое поведение называется "migration". В web2py миграции являются автоматическими, но могут быть отключены для каждой таблицы путем передачи migrate=False в качестве последнего аргумента define_table.

Строка 6 определяет формат строки для таблицы. Она определяет, как запись должна быть представлена в виде строки. Обратите внимание на то, что аргумент format также может быть функцией, которая принимает запись и возвращает строку. Например:

format=lambda row: row.title

Строки 8-12 определяют другую таблицу под названием "post". Пост имеет поля "author", "email" (мы намерены сохранять адрес электронной почты автора поста), "body" с типом "text" (мы намерены использовать его для хранения актуального комментария, опубликованного автором), и поле "image_id" типа ссылки, которая указывает на db.image через поле "id".

В строке 14, db.image.title представляет поле "title" в таблице "image". Атрибут requires позволяет устанавливать требования/ограничения, которые будут применены посредством web2py форм. Здесь мы требуем, чтобы поле "title" было уникальным:

IS_NOT_IN_DB(db, db.image.title)

Заметьте, что это является необязательным, поскольку оно устанавливается автоматически с учетом, что Field ('title', unique = True).

Объекты, представляющие эти ограничения называются валидаторами. Многочисленные валидаторы могут быть сгруппированы в виде списка. Валидаторы выполняются в порядке их появления. IS_NOT_IN_DB(a, b) это специальный валидатор, который проверяет, что значение поля b для новой записи нет еще в a.

Строка 15 требует, чтобы поле "image_id" таблицы "post" находилось в db.image.id. Что касается базы данных, то мы уже заявили об этом, когда мы определили таблицу "post". Сейчас мы в явном виде говорим модели, что это условие должно быть обеспечено посредством web2py, конечно же, на уровне обработки форм, когда новый комментарий будет размещен, так что недопустимые значения не передастся от формы ввода в базу данных. Мы также требуем, чтобы "image_id" было представлено посредством "title", '%(title)s', для соответствующей записи.

Строка 20 указывает на то, что поле "image_id" таблицы "post" не должны быть показано в формах, writable=False и нет даже в формах только для чтения, readable=False.

Смысл валидаторов в строках 17-18 должен быть очевиден.

format

Обратите внимание на то, что валидатор

db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')

может быть упущен (и быть автоматическим), если мы укажем формат для указанной таблицы:

db.define_table('image', ..., format='%(title)s')

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

appadmin

Как только модель определена, и если нет никаких ошибок, то web2py создает интерфейс управления приложением для управления базой данных. Вы получаете доступ к нему через ссылка "Администрирование базы данных" на странице Правка или напрямую:

http://127.0.0.1:8000/images/appadmin

Вот скриншот интерфейса appadmin:

image

Этот интерфейс кодируется в контроллере под названием "appadmin.py" и соответствующем представлении "appadmin.html". С этого момента, мы будем ссылаться на этот интерфейс просто как appadmin. Это позволяет администратору вставлять новые записи базы данных, редактировать и удалять существующие записи, просматривать таблицы, а также выполнять присоединение к базе данных.

При первый доступе в appadmin, выполняется модель и создаются таблицы. Web2py DAL переводит код Python в операторы SQL, которые специфичны для выбранной базы данных на стороне сервера (SQLite в данном примере). Вы можете увидеть сгенерированный SQL на странице Правка, кликнув по ссылке "sql.log" под "models". Заметим, что ссылка не отображается до тех пор, пока не будут созданы таблицы.

image

Если Вы изменили модель и доступ appadmin снова, web2py сгенерирует SQL для изменения существующих таблиц. Сгенерированный SQL пишет логи в "sql.log".

Теперь вернемся к appadmin и попытаемся вставить новую запись изображения:

image

web2py превращает поле db.image.file типа "upload" в форму для загрузки файла. Когда форма будет передана и файл изображения загружен, то имя файла переименовывается в безопасный путь, в котором сохраняется расширение файла, изображение сохраняется с новым именем в папку "uploads" приложения, а новое имя сохраняется в поле db.image.file. Этот процесс предназначен для предотвращения атак обхода каталогов.

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

При нажатии на имя таблицы в appadmin, web2py выполняет выбор всех записей из текущей таблицы, которые были определены по запросу DAL

db.image.id > 0

и отображает результат.

image

Вы можете выбрать другой набор записей путем редактирования запроса DAL и нажав клавишу [Отправить].

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

Благодаря IS_IN_DB валидатору, ссылочное поле "image_id" отображается посредством выпадающего меню. Элементы в выпадающем меню хранятся как ключи (db.image.id), но представлены с помощью их заголовков db.image.title, как обусловлено валидатором.

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

На следующем рисунке показано, что происходит, когда вы отправляете форму, которая не прошла проверку:

image

Одни и те же формы, которые автоматически генерируются appadmin также могут быть сгенерированы программным способом с помощью SQLFORM помощника и встроены в пользовательские приложения. Эти формы CSS дружественные, и могут быть персонализированы.

Каждое приложение имеет свой собственный appadmin; следовательно, appadmin сам по себе может быть изменен без воздействия на другие приложения.

До сих пор приложение знает, как хранить данные, и мы видели, как получить доступ к базе данных через appadmin. Доступ к ** appadmin ** ограничивается администратором, и он не предназначен для производства веб-интерфейса приложения; следовательно, перейдем к следующей части этого пошагового руководства. А именно нам необходимо создать:

  • Страница "index" на которой перечислены все доступные изображения, отсортированные по названию, и ссылки на страницы детализации изображений.
  • Страница "show/[id]" которая показывает посетителю запрошенное изображение и позволяет посетителю просматривать и оставлять комментарии.
  • Действие "download/[name]" для скачивания загруженных изображений.

Это представлено схематически здесь:

yUML diagram

Вернитесь на страницу Правка и отредактируйте "default.py" контроллер, заменив его содержимое следующим:

select
def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)

Это действие возвращает словарь. Ключи элементов в словаре интерпретируются как переменные, которые передаются в связанное с действием представление. При разработке, если файл представления отсутствует, то действие отображается посредством шаблона представления "generic.html", который поставляется в комплекте с каждым web2py приложением.

Действие index выполняет выбор всех изображений в поле (db.image.ALL) из таблицы, с упорядочением по db.image.title. Результатом выборки является объект Rows, содержащий все записи. Назначим ему локальную переменную с именем images, которая будет возвращаться действием в представление. Объект images является итерируемым и его элементы являются выбранными строками. Для каждой строки столбцы могут быть доступны как словарь: images[0]['title'] или, что эквивалентно, как images[0].title.

Если вы не пишете представление, словарь отображается посредством "views/generic.html" и вызов действия index будет выглядеть следующим образом:

image

Вы еще не создали представление для этого действия, поэтому web2py визуализирует набор записей в простой табличной форме.

Перейдем к созданию представления для действия index. Вернитесь в администратор, измените "default/index.html" и замените его содержимое следующим:

{{extend 'layout.html'}}
<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>

Первое, что нужно заметить это то, что представление является чистым HTML со специальными {{{...}} тегами. Код, внедренный в {{...}} является чистым кодом Python с одной оговоркой: отступы не имеет значения. Блоки кода начинаются со строк, заканчивающихся двоеточие (:), и заканчиваются в строках, начинающихся с ключевого слова pass. В некоторых случаях конец блока очевиден из контекста и использование pass не требуется.

В строках 5-7 выполняется цикл над строками с изображениями и отображается каждая строка с изображением:

LI(A(image.title, _href=URL('show', args=image.id))

Эта запись в конечном итоге преобразуется в HTML тег <li> ... </ li>, который будет содержать тег <a href="..."> ... </a>, внутри которого будет находится image.title. Значением гипертекстовой ссылки (href атрибут) является:

URL('show', args=image.id)

т.е. URL в пределах одного приложения и контроллера в представлении текущего запроса, который вызывает функцию под названием "show", передавая один аргумент в функцию, args = image.id. LI, A и т.д. являются web2py помощниками, которые сопоставляются с соответствующими HTML-тегами. Их безымянные аргументы интерпретируются как объекты для сериализации и вставлены в innerHTML теги. Именованные аргументы, начинающиеся с символа нижнего подчеркивания (например, _href) интерпретируются как имена атрибутов HTML тега, но без подчеркивания. Например _href является HTML-атрибутом href, а _class является HTML-атрибутом class и т.д.

В качестве примера, следующее утверждение:

{{=LI(A('something', _href=URL('show', args=123))}}

визуализируется как:

<li><a href="/images/default/show/123">something</a></li>

Горстка помощников (INPUT, TEXTAREA, OPTION и SELECT) также поддерживает некоторые специальные именованные атрибуты, не начинающиеся с подчеркивания (value, and requires). Они имеют важное значение для создания пользовательских форм и будут обсуждаться позже.

Перейдите назад на страницу Правка. Она теперь указывает что "default.py выставляет index". При нажатии на "index", вы можете посетить только что созданную страницу:

http://127.0.0.1:8000/images/default/index

которая выглядит как:

image

Если вы нажмете на ссылку с именем изображения, то вы будете направлены на:

http://127.0.0.1:8000/images/default/show/1

и это приведет к ошибке, так как вы еще не создали действие под названием "show" в контроллере "default.py".

Давайте отредактируем "default.py" контроллер и заменим его содержимое:

SQLFORM
accepts
response.flash
request.args

response.download
def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)

def show():
    image = db.image(request.args(0,cast=int)) or redirect(URL('index'))
    db.post.image_id.default = image.id
    form = SQLFORM(db.post)
    if form.process().accepted:
        response.flash = 'your comment is posted'
    comments = db(db.post.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)

def download():
    return response.download(request, db)

Контроллер содержит два действия: "show" и "download". Действие "show" выбирает изображения по id, полученному из разбора аргументов запроса, и все комментарии, связанные с изображением. Результат работы функции "show" передается в представление "default/show.html".

id изображения ссылается на:

URL('show', args=image.id)

в "default/index.html", может быть доступно как:

request.args(0,cast=int)

от действия "show". Аргумент cast=int является необязательным, но очень важным. В нем делается попытка привести строковое значение, переданное в PATH_INFO, в целое. При неудаче он поднимает должное исключение, вместо вызова талона ошибки. Можно также указать переадресацию в случае неудачи, дополнив код:

request.args(0,cast=int,otherwise=URL('error'))

Более того db.image(...) является сокращением для

db(db.image.id==...).select().first()

Действие "download" ожидает имя файла в request.args(0), строит путь к месту, где этот файл должен быть, и отправляет его обратно клиенту. Если файл слишком большой, он передает файл в потоке без каких либо накладных расходов на память.

Обратите внимание на следующие выражения:

  • Строка 6 устанавливает значение для справочного поля, которое не является частью формы ввода, поскольку оно не в списке полей, указанных выше.
  • Строка 7 создает вставляемую форму SQLFORM для таблицы db.post, используя только указанные поля.
  • Строка 8 обрабатывает предоставленные формы (переменные, предоставляемые формой, находятся в request.vars) в текущей сессии (сессия используется для предотвращения двойных представлений, а также для обеспечения навигации). Если переменные, предоставляемые формой, проверены, то новый комментарий вставляется в таблицу db.post; в противном случае форма изменяется для того, чтобы включить сообщения об ошибках (например, если адрес электронной почты автора является недействительным). Все это делается в строке 9!
  • Строка 9 выполняется только в том случае, если форма будет принята, после того, как запись вставляется в таблицу базы данных. response.flash является переменной web2py, которая отображается в представлениях и используется для уведомления посетителя о том, что что-то произошло.
  • Строка 10 выбирает все комментарии, которые ссылаются на текущее изображение.

Действие "download" уже определено в контроллере "default.py" скаффолдингового приложения.

Действие "download" не возвращает словарь, поэтому оно не нуждается в представлении. Действие "show", однако, должно иметь представление, поэтому вернемся к admin и создадим новое представление под названием "default/show.html".

Отредактируйте этот новый файл и замените его содержимое на следующее:

{{extend 'layout.html'}}
<h1>Image: {{=image.title}}</h1>
<div style="text-align:center">
<img width="200px"
     src="{{=URL('download', args=image.file)}}" />
</div>
{{if len(comments):}}
  <h2>Comments</h2><br /><p>
  {{for post in comments:}}
    <p>{{=post.author}} says <i>{{=post.body}}</i></p>
  {{pass}}</p>
{{else:}}
  <h2>No comments posted yet</h2>
{{pass}}
<h2>Post a comment</h2>
{{=form}}

Это представление отображает image.file путем вызова действия "download" внутри тега <img... />. Если есть комментарии, то оно перебирает их и отображает каждый комментарий.

Вот как это все будет выглядеть для посетителя.

image

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

Добавление аутентификации

В web2py API для управления доступом на основе ролей довольно сложный, но сейчас мы ограничимся только ограничением доступа к действия show для аутентифицированных пользователей, и отложим более подробное обсуждение до Главы 9.

Для того, чтобы ограничить доступ аутентифицированных пользователей, нам необходимо выполнить три шага. В модели, например "db.py", нам нужно добавить:

from gluon.tools import Auth
auth = Auth(db)
auth.define_tables(username=True)

В нашем контроллере, нам нужно добавить одно действие:

def user():
    return dict(form=auth())

Этого вполне достаточно для включения страниц входа в систему, регистрации, выхода из системы и других страниц. По умолчанию макет также будет показывать варианты на соответствующих страницах в правом верхнем углу.

image

Теперь мы можем декорировать функции, которые мы хотим ограничить, например:

@auth.requires_login()
def show():
    ...

Любая попытка доступа

http://127.0.0.1:8000/images/default/show/[image_id]

потребует логин. Если пользователь не вошел в нее, то пользователь будет перенаправлен на

http://127.0.0.1:8000/images/default/user/login

image

Функция user также предоставляет, помимо всего прочего, следующие действия:

http://127.0.0.1:8000/images/default/user/logout
http://127.0.0.1:8000/images/default/user/register
http://127.0.0.1:8000/images/default/user/profile
http://127.0.0.1:8000/images/default/user/change_password
http://127.0.0.1:8000/images/default/user/request_reset_password
http://127.0.0.1:8000/images/default/user/retrieve_username
http://127.0.0.1:8000/images/default/user/retrieve_password
http://127.0.0.1:8000/images/default/user/verify_email
http://127.0.0.1:8000/images/default/user/impersonate
http://127.0.0.1:8000/images/default/user/not_authorized

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

Объект auth и функция user уже определены в скаффолдинговом приложении. Объект auth является легко настраиваемым и может иметь дело с проверкой электронной почты, подтверждением регистрации, CAPTCHA, и альтернативными методам входа в систему через плагины.

Добавление сетки

Мы можем улучшить это дополнительно с помощью гаджетов SQLFORM.grid и SQLFORM.smartgrid, чтобы создать интерфейс управления для нашего приложения:

@auth.requires_membership('manager')
def manage():
    grid = SQLFORM.smartgrid(db.image,linked_tables=['post'])
    return dict(grid=grid)

который ассоциируется с "views/default/manage.html"

{{extend 'layout.html'}}
<h2>Management Interface</h2>
{{=grid}}

Используя appadmin создайте группу "manager" и сделайте некоторых пользователей членами группы. Они будут иметь доступ к

http://127.0.0.1:8000/images/default/manage

а также возможность просматривать, искать:

image

создавать, обновлять и удалять изображения и их комментарии:

image

Настройка макета

Вы можете настроить макет по умолчанию, отредактировав "views/layout.html", но вы также можете настроить его без редактирования HTML. По факту, таблица стилей "static/css/web2py.css" хорошо задокументирована и описана в главе 5. Вы можете изменить цвет, столбцы, размер, границы и фон без редактирования HTML. Если вы хотите отредактировать меню, заголовок или подзаголовок, вы можете сделать это в любом файле модели. Скаффолдинговое приложение, устанавливает значения этих параметров по умолчанию в файле "models/menu.py":

response.title = request.application
response.subtitle = 'customize me!'
response.meta.author = 'you'
response.meta.description = 'describe your app'
response.meta.keywords = 'bla bla bla'
response.menu = [ [ 'Index', False, URL('index') ] ]

Простой wiki

wiki
RSS
Ajax
XMLRPC

В этом разделе мы создадим простой wiki с нуля, используя только API низкого уровня (в отличие от использования встроенных возможностей Вики web2py, продемонстрированных в следующем разделе). Посетитель сможет создавать страницы, искать их (по названию), а также редактировать их. Посетитель также будет иметь возможность размещать комментарии (точно так же, как и в предыдущих приложениях), а также размещать документы (в виде вложений в страницах) и ссылаться на них с других страниц. Согласно соглашению, мы принимаем синтаксис Markmin для нашего синтаксиса вики. Мы также реализуем поиск страницы с помощью Ajax, RSS-канал для страниц и обработчик для поиска страниц через XML-RPC[xmlrpc] . На следующей диаграмме перечислены действия, которые мы должны реализовать и связи, которые мы намерены построить между ними.

yUML diagram

Начнем с создания нового скаффолдингового приложения, назвав его "mywiki".

Модель должна содержать три таблицы: page, comment и document. Обе таблицы comment и document ссылаются на страницу, потому что они принадлежат странице. Документ содержит поле для загрузки файла, как для изображения в предыдущем приложении.

Вот полная модель:

db = DAL('sqlite://storage.sqlite')

from gluon.tools import *
auth = Auth(db)
auth.define_tables()
crud = Crud(db)

db.define_table('page',
    Field('title'),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id),
    format='%(title)s')

db.define_table('post',
    Field('page_id', 'reference page'),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id))

db.define_table('document',
    Field('page_id', 'reference page'),
    Field('name'),
    Field('file', 'upload'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id),
    format='%(name)s')

db.page.title.requires = IS_NOT_IN_DB(db, 'page.title')
db.page.body.requires = IS_NOT_EMPTY()
db.page.created_by.readable = db.page.created_by.writable = False
db.page.created_on.readable = db.page.created_on.writable = False

db.post.body.requires = IS_NOT_EMPTY()
db.post.page_id.readable = db.post.page_id.writable = False
db.post.created_by.readable = db.post.created_by.writable = False
db.post.created_on.readable = db.post.created_on.writable = False

db.document.name.requires = IS_NOT_IN_DB(db, 'document.name')
db.document.page_id.readable = db.document.page_id.writable = False
db.document.created_by.readable = db.document.created_by.writable = False
db.document.created_on.readable = db.document.created_on.writable = False

Отредактируйте контроллер "default.py" и создайте следующие действия:

  • index: список всех страниц вики
  • create: добавляет новую страницу
  • show: показывает вики-страницу и ее комментарии, а также добавляет новые комментарии
  • edit: редактирует существующую страницу
  • documents: управляет документами, прикрепленными к странице
  • download: скачивает документ (как в примере с изображениями)
  • search: отображает окно поиска и, с помощью обратного вызова Ajax, возвращает все заголовки посетителя соответствующего типа
  • callback: функция обратного вызова Ajax. Она возвращает HTML, который получает встроенные в страницу поиска типы посетителя.

Вот контроллер "default.py":

def index():
     """ этот контроллер возвращает словарь, визуализируемый через представление
         в нем перечислены все страницы вики
     >>> index().has_key('pages')
     True
     """
     pages = db().select(db.page.id,db.page.title,orderby=db.page.title)
     return dict(pages=pages)

@auth.requires_login()
def create():
     """создает новую пустую страницу вики"""
     form = SQLFORM(db.page).process(next=URL('index'))
     return dict(form=form)

def show():
     """показывает вики-страницы"""
     this_page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     db.post.page_id.default = this_page.id
     form = SQLFORM(db.post).process() if auth.user else None
     pagecomments = db(db.post.page_id==this_page.id).select()
     return dict(page=this_page, comments=pagecomments, form=form)

@auth.requires_login()
def edit():
     """редактирует существующую вики страницу"""
     this_page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     form = SQLFORM(db.page, this_page).process(
         next = URL('show',args=request.args))
     return dict(form=form)

@auth.requires_login()
def documents():
     """просмотреть, отредактировать все документы, 
	 прикрепленные к определенной странице"""
     page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     db.document.page_id.default = page.id
     db.document.page_id.writable = False
     grid = SQLFORM.grid(db.document.page_id==page.id,args=[page.id])
     return dict(page=page, grid=grid)

def user():
     return dict(form=auth())

def download():
     """позволяет скачивание документов"""
     return response.download(request, db)

def search():
     """Ajax страница поиска вики"""
     return dict(form=FORM(INPUT(_id='keyword',_name='keyword',
              _onkeyup="ajax('callback', ['keyword'], 'target');")),
              target_div=DIV(_id='target'))

def callback():
     """Ajax обратного вызова, который возвращает <ul> ссылки на вики-страницы"""
     query = db.page.title.contains(request.vars.keyword)
     pages = db(query).select(orderby=db.page.title)
     links = [A(p.title, _href=URL('show',args=p.id)) for p in pages]
     return UL(*links)

Строки 2-6 представляют собой комментарий для действия index. Строки 4-5 внутри комментария интерпретируются в виде Python тестового кода (doctest). Тесты могут быть запущены через интерфейс admin. В этом случае тесты проверяют, что действие index работает без ошибок.

Строки 18, 27 и 35 пытаются внести page запись с id в request.args(0).

Строки 13, 20 определяют и обрабатывают создание форм для новой страницы и нового комментария и

Строка 28 определяет и обрабатывает форму обновления для вики-страниц.

Строка 38 создает grid объект, который позволяет просматривать, добавлять и обновлять комментарии, связанные со страницей.

Некоторая магия происходит в строке 51. Атрибут onkeyup тега INPUT устанавливается на "keyword". Каждый раз, когда посетитель выпускает ключ, выполняется код JavaScript внутри атрибута onkeyup, на стороне клиента. Вот код JavaScript:

ajax('callback', ['keyword'], 'target');

ajax является функцией JavaScript, определенной в файле "web2py.js" которая включена по умолчанию в "layout.html". Он принимает три параметра: URL-адрес действия, которое выполняет синхронную функцию обратного вызова, список идентификаторов переменных, которые будут отправлены в обратном вызове (["keyword"]), и идентификатор, куда вставляется ответ ("target").

Как только вы введете что-нибудь в поле поиска и отпустите клавишу, клиент вызывает сервер и передает содержимое поля 'keyword', и, когда север отвечает, ответ встраивается в саму страницу как innerHTML тега 'target'.

Тег 'target' является DIV, определенным в строке 52. Он может быть также определен в представлении.

Вот код для представления "default/create.html":

{{extend 'layout.html'}}
<h1>Create new wiki page</h1>
{{=form}}

Предположим, что вы зарегистрированы и вошли в систему, то при посещении страницы create, вы увидите следующее:

image

Вот код для представления "default/index.html":

{{extend 'layout.html'}}
<h1>Available wiki pages</h1>
[ {{=A('search', _href=URL('search'))}} ]<br />
<ul>{{for page in pages:}}
     {{=LI(A(page.title, _href=URL('show', args=page.id)))}}
{{pass}}</ul>
[ {{=A('create page', _href=URL('create'))}} ]

Он генерирует следующую страницу:

image

Вот код для представления "default/show.html":

markdown
MARKMIN

{{extend 'layout.html'}}
<h1>{{=page.title}}</h1>
[ {{=A('edit', _href=URL('edit', args=request.args))}} ]<br />
{{=MARKMIN(page.body)}}
<h2>Comments</h2>
{{for post in comments:}}
  <p>{{=db.auth_user[post.created_by].first_name}} on {{=post.created_on}}
     says <i>{{=post.body}}</i></p>
{{pass}}
<h2>Post a comment</h2>
{{=form}}

Если вы хотите использовать синтаксис markdown вместо markmin синтаксиса:

from gluon.contrib.markdown import WIKI as MARKDOWN

и использовать MARKDOWN вместо MARKMIN помощника. В качестве альтернативы, вы можете выбрать сырой HTML вместо синтаксиса markmin. В этом случае вы должны заменить:

{{=MARKMIN(page.body)}}

на:

{{=XML(page.body)}}
sanitize

(так что XML не сможет получить побег, который web2py обычно делает по умолчанию по соображениям безопасности).

Это можно сделать лучше с:

{{=XML(page.body, sanitize=True)}}

Путем установки sanitize=True, вы сообщаете web2py избегать небезопасные теги XML, такие как "<script>", тем самым предотвращая XSS уязвимости.

Теперь, если на странице index, вы нажмете на заголовок страницы, то вы можете увидеть страницу, которую вы создали:

image

Вот код для представления "default/edit.html":

{{extend 'layout.html'}}
<h1>Edit wiki page</h1>
[ {{=A('show', _href=URL('show', args=request.args))}}
| {{=A('documents', _href=URL('documents', args=request.args))}} ]<br />
{{=form}}

Он генерирует страницу, которая выглядит почти идентично странице create.

Вот код для представления "default/documents.html":

{{extend 'layout.html'}}
<h1>Documents for page: {{=page.title}}</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
<h2>Documents</h2>
{{=grid}}

Если, со страницы "show", вы нажмете на документы, то теперь вы можете управлять документами, прикрепленными к странице.

image

Наконец, вот код для представления "default/search.html":

{{extend 'layout.html'}}
<h1>Search wiki pages</h1>
[ {{=A('listall', _href=URL('index'))}}]<br />
{{=form}}<br />{{=target_div}}

который генерирует следующую Ajax форму поиска:

image

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

http://127.0.0.1:8000/mywiki/default/callback?keyword=wiki

Если вы посмотрите на исходный код страницы, то вы видите HTML, возвращаемый посредством обратного вызова:

<ul><li><a href="/mywiki/default/show/4">I made a Wiki</a></li></ul>
rss

Генерирование RSS потока ваших вики страниц с помощью web2py выполняется легко, потому что web2py включает в себя gluon.contrib.rss2. Просто добавьте следующее действие к контроллеру по умолчанию:

def news():
    """генерирует RSS-канал от вики-страниц"""
    response.generic_patterns = ['.rss']
    pages = db().select(db.page.ALL, orderby=db.page.title)
    return dict(
       title = 'mywiki rss feed',
       link = 'http://127.0.0.1:8000/mywiki/default/index',
       description = 'mywiki news',
       created_on = request.now,
       items = [
          dict(title = row.title,
               link = URL('show', args=row.id, scheme=True,
	                  host=True, extension=False),
               description = MARKMIN(row.body).xml(),
               created_on = row.created_on
               ) for row in pages])

и когда вы посетите страницу

http://127.0.0.1:8000/mywiki/default/news.rss

вы увидите канал (точность выхода зависит от программы чтения канала). Обратите внимание на то, что dict автоматически преобразуется в RSS, благодаря .rss расширению в URL.

image

web2py также включает в себя feedparser для чтения сторонних каналов.

Обратите внимание что строка:

response.generic_patterns = ['.rss']

дает указание web2py использовать общие представления (в нашем случае "views/generic.rss"), когда URL заканчивается в glob шаблоном ".rss". По умолчанию общие представления разрешены только с локального хоста для целей разработки.

XMLRPC

Наконец, давайте добавим обработчик XML-RPC, который позволяет осуществлять поиск вики программно:

service = Service()

@service.xmlrpc
def find_by(keyword):
     """находит страницы, которые содержат ключевое слово XML-RPC"""
     return db(db.page.title.contains(keyword)).select().as_list()

def call():
    """выставляет все зарегистрированные сервисы, в том числе XML-RPC"""
    return service()

Здесь действие обработчика просто публикует (через XML-RPC), функции, указанные в списке. В данном случае, find_by. Find_by не является действием (потому что он принимает аргумент). Он запрашивает базу данных с .select(), а затем извлекает записи в виде списка с .response и возвращает список.

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

>>> import xmlrpclib
>>> server = xmlrpclib.ServerProxy(
    'http://127.0.0.1:8000/mywiki/default/call/xmlrpc')
>>> for item in server.find_by('wiki'):
        print item['created_on'], item['title']

Обработчик может быть доступен из многих других языков программирования, которые понимают XML-RPC, включая C, C ++, C # и Java.

О формате date, datetime и time

Существуют три различных представления для каждого поля типа date, datetime и time:

  • Представление базы данных
  • Внутреннее представление web2py
  • Строковое представление в формах и таблицах

Представление базы данных является внутренним вопросом и не влияет на код. Внутри на уровне web2py, они хранятся в виде datetime.date, datetime.datetime и datetime.time объекта соответственно, и ими можно манипулировать как таковыми:

for page in db(db.page).select():
    print page.title, page.day, page.month, page.year

Когда даты преобразуются в строки в формах, они преобразуются с использованием представления ISO

%Y-%m-%d %H:%M:%S

но это представление интернационализируется и вы можете использовать административную страницу перевода, чтобы изменить формат на любой альтернативный. Например:

%m/%d/%Y %H:%M:%S

Имейте в виду, что по умолчанию английский язык не переводится, потому что web2py предполагает, что приложения написаны на английском языке. Если вы хотите интернационализировать работу для английского языка вам необходимо создать файл перевода (с помощью администратора) и вам нужно объявить, что текущий язык приложения есть нечто иное, чем английский, например:

T.current_languages = ['null']

Встроенный web2py вики

Теперь вы можете забыть код, который мы разобрали в предыдущем разделе (не то, что вы узнали о web2py API, просто код конкретного примера), так как мы собираемся привести пример встроенного в web2py вики.

На самом деле, web2py идет с такими возможностями вики, как медиа-вложения, теги, облако тегов, страница разрешения, а также поддержкой oembed [oembed] и компонентов (Глава 14). Этот вики может быть использован с любым приложением web2py.

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

Здесь мы предположим, что мы начинаем с нуля, от простого клона "welcome" приложения под названием "wikidemo". Отредактируйте контроллер и замените "index" действие на следующее

def index(): return auth.wiki()

Готово! У вас есть полностью рабочий вики. Обратите внимание, что вики нуждается в некоторых таблицах, которые будут определены, и они будут определяться только при доступе к контроллеру. Если вы хотите, чтобы они были легко доступны, используйте auth.wiki(resolve=False), и убедитесь, что таблица миграции включена: подробнее об этом в следующем разделе Extending-the-auth-wiki-feature.

На данный момент ни страница еще не была создана и для того, чтобы создать страницы, вы должны быть авторизованы, а также вы должны быть членом группы под названием "wiki_editor" или "wiki_author". Если вы вошли в систему как администратор, то группа "wiki_editor" создается автоматически, и вы являетесь ее членом. Различие между редакторами и авторами в том, что редакторы могут создавать страницы, редактировать и удалять любые страницы, в то время как авторы могут создавать (с некоторыми дополнительными ограничениями) и редактировать/удалять только те страницы, которые они создали.

Функция auth.wiki() возвращает словарь с ключом content, который понимается через скаффолдинг "views/default/index.html". Вы можете сделать свое собственное представление для этого действия:

{{extend 'layout.html'}}
{{=content}}

и добавить дополнительный HTML или код по мере необходимости. Вы не должны использовать действие "index", чтобы выставить вики. Вы можете использовать действие с другим именем.

Чтобы попробовать вики, просто войдите под администратором, посетив страницу

http://127.0.0.1:8000/wikidemo/default/index

Затем выберите слизняка (в издательском бизнесе, слизняк это краткое название данное статье, которая находится в производстве) и вы будете перенаправлены на пустую страницу, где вы можете редактировать содержимое с помощью синтаксиса MARKMIN вики. Новый пункт меню называется "[wiki]" позволит вам создавать, искать и редактировать страницы. Вики-страницы имеют URL-адреса, такие как:

http://127.0.0.1:8000/wikidemo/default/index/[slug]

Сервисные страницы имеют имена, которые начинаются с подчеркивания:

http://127.0.0.1:8000/wikidemo/default/index/_create
http://127.0.0.1:8000/wikidemo/default/index/_search
http://127.0.0.1:8000/wikidemo/default/index/_could
http://127.0.0.1:8000/wikidemo/default/index/_recent
http://127.0.0.1:8000/wikidemo/default/index/_edit/...
http://127.0.0.1:8000/wikidemo/default/index/_editmedia/...
http://127.0.0.1:8000/wikidemo/default/index/_preview/...

Попробуйте создать больше страниц, таких как "index", "aboutus", и "contactus". Попробуйте отредактировать их.

Метод wiki имеет следующую подпись:

def wiki(self, slug=None, env=None, render='markmin',
         manage_permissions=False, force_prefix='',
         restrict_search=False, resolve=True,
         extra=None, menu_groups=None)

Она принимает следующие аргументы:

  • render который по умолчанию 'markmin', но может быть установлен равным 'html'. Он определяет синтаксис вики. Мы обсудим markmin вики-разметку позже. Если вы измените его на HTML вы можете использовать в режиме полного соответствия редактор JavaScript, такой как TinyMCE или NicEdit.
  • manage_permissions. Устанавливается в False по умолчанию и распознает только разрешения для "wiki_editor" и "wiki_author". Если вы измените его на True страница создания/редактирования даст возможность указать по имени группы, члены которой имеют разрешение на чтение и редактирование страницы. Это группа "everybody" которая включает в себя всех пользователей.
  • force_prefix. Если установлено что-то вроде'%(id)s-', то он будет ограничивать авторов (не редакторов) в создании страниц с префиксом вроде "[user id]-[page name]". Префикс может содержать id ("%(id)s") или имя пользователя ("%(username)s") или любое другое поле из таблицы auth_user, до тех пор, пока соответствующий столбец содержит допустимую строку, которая пройдет проверку URL.
  • restrict_search. Значение по умолчанию False и любой зарегистрированный пользователь может найти все страницы вики (но не обязательно читать или изменять их). Если установлен в True, авторы могут выполнять поиск только своих собственных страниц, редакторы смогут найти все, другие пользователи не смогут ничего найти.
  • menu_groups. Значение по умолчанию None и это указывает на то, что в меню управления вики отображается всегда (поиск, создание, редактирование и т.д.). Вы можете задать его со списком имен группы, только члены которой могут видеть это меню, например ['wiki_editor','wiki_author']. Обратите внимание на то, что даже если меню выставляется всем, то это не значит, что всем дозволено выполнять действия, указанные в меню, так как они регулируются системой контроля доступа.

Метод wiki имеет некоторые дополнительные параметры, которые будут объяснены позже: slug, env, и extra.

Основы MARKMIN

Синтаксис MARKMIN позволяет разметить жирный текст, используя **жирный**, курсивный текст, используя ''курсивный'', и текст программного кода должен быть ограничен двойными инвертированными кавычками. Заголовки должны начинаться с #, разделы с ##, а подразделы с ###. Используйте минус (-) перед неупорядоченными элементами и плюс (+) перед упорядоченными элементами. URL-адреса автоматически преобразуются в ссылки. Ниже приведен пример текста markmin:

# Это заголовок
## Это заголовок раздела
### Это заголовок подраздела

Текст может быть **жирным**, ''курсивом'', ``кодом`` и т.д.
Узнайте больше на:

http://web2py.com

Вы можете использовать extra параметр из auth.wiki для перехода к правилам экстра-визуализации MARKMIN помощника.

Вы можете найти более подробную информацию о синтаксисе MARKMIN в главе 5.

auth.wiki является более мощным, чем голокостные MARKMIN помощники, поддерживает oembed и компоненты.

Вы можете использовать env параметр из auth.wiki, чтобы выставить функции вики. Например:

auth.wiki(env=dict(join=lambda a:"-".join(a.split(","))))

позволяет использовать синтаксис разметки:

@(join:1,2,3)

Это вызывает функцию join в env с параметрами a,b,c=1,2,3, которые будут отрисованы, как 1-2-3.

Протокол Oembed

Вы можете ввести (или вырезать и вставить) любой URL-адрес в вики-страницу и отобразить в виде ссылки на URL-адрес. Существуют исключения:

  • Если URL-адрес имеет расширение изображения, то ссылка вкладывается как изображение <img/>.
  • Если URL-адрес имеет аудио расширение, то ссылка вкладывается как HTML5 аудио <audio/>.
  • Если URL-адрес имеет расширение видео, то ссылка вкладывается как HTML5 видео <video/>.
  • Если URL-адрес имеет расширение MS Office или PDF, то встроенным Google Doc Viewer, показывается содержание документа (работает только для публичных документов).
  • Если URL-адрес указывает на страницу YouTube, Vimeo страницу или страницу Flickr, то web2py контактируется с соответствующей веб-службой и запрашивает ее о надлежащем пути для вставки содержимого. Это делается с помощью oembed протокола.

Ниже приведен полный список поддерживаемых форматов:

Image (.PNG, .GIF, .JPG, .JPEG)
Audio (.WAV, .OGG, .MP3)
Video (.MOV, .MPE, .MP4, .MPG, .MPG2, .MPEG, .MPEG4, .MOVIE)

Поддержка через Google Doc Viewer:

Microsoft Excel (.XLS and .XLSX)
Microsoft PowerPoint 2007 / 2010 (.PPTX)
Apple Pages (.PAGES)
Adobe PDF (.PDF)
Adobe Illustrator (.AI)
Adobe Photoshop (.PSD)
Autodesk AutoCad (.DXF)
Scalable Vector Graphics (.SVG)
PostScript (.EPS, .PS)
TrueType (.TTF)
xml Paper Specification (.XPS)

Поддержка через oembed:

flickr.com
youtube.com
hulu.com
vimeo.com
slideshare.net
qik.com
polleverywhere.com
wordpress.com
revision3.com
viddler.com

Это реализуется в файле web2py gluon.contrib.autolinks и, в частности, в функции expand_one. Вы можете расширить oembed поддержку зарегистрировав больше сервисов. Это делается путем добавления во входной лист EMBED_MAPS:

from gluon.contrib.autolinks import EMBED_MAPS
EMBED_MAPS.append((re.compile('http://vimeo.com/\S*'),
                   'http://vimeo.com/api/oembed.json'))

Создание ссылок на содержание вики

Если вы создали вики-страницу со слизняком "contactus", вы можете сослаться на эту страницу, как

@////contactus

Здесь @//// обозначает, тоже что и

@/app/controller/function/

но "app", "controller", и "function" опущены, так как предполагаются по умолчанию.

Точно так же вы можете использовать меню вики для загрузки медиа-файла (например, изображение), связанного со страницей. На странице "управление медиа" будут показаны все файлы, которые вы загрузили и также отображается правильное выражение, чтобы связать мультимедийный файл. Если, например, вы загружаете файл с именем "test.jpg" с заголовком "beach", выражением ссылки будет что-то вроде:

@////15/beach.jpg

@//// это такой же префикс, как и описанный выше. 15 это идентификатор записи хранения медиа-файла. beach это название медиа-файла. .jpg является расширением исходного файла.

Если вы вырежете и вставите @////15/beach.jpg в вики-страницы, то вы встраиваете изображение.

Имейте в виду, что медиа-файлы связаны со страницами и наследуют права доступа от страницы.

Wiki меню

Если вы создаете страницу со слизняком страницы "wiki-menu", то оно будет интерпретироваться как описание меню. Здесь приведен пример:

- Home > @////index
- Info > @////info
- web2py > http://www.web2py.com
- - About us > @////aboutus
- - Contact us > @////contactus

Каждая строка это пункт меню. Мы использовали двойное тире для вложенных меню. Символами > отделяется название пункта меню от ссылки пункта меню.

Имейте в виду, что меню добавляется response.menu. Она не заменяет его. Пункт меню [wiki] добавляется автоматически с помощью сервисных функций.

Сервисные функции

Если, например, вы хотите использовать вики, чтобы создать изменяемую боковую панель. то вы можете создать страницу с slug="sidebar", а затем встроить его в вашем layout.html при помощи кода

{{=auth.wiki(slug='sidebar')}}

Обратите внимание на то, что нет ничего особенного в слове "sidebar". Любая страница вики может быть извлечена и встроена в любой точке вашего кода. Это позволяет Вам смешивать и сопоставлять функциональные возможности вики с регулярными web2py функциональными возможностями.

Также отметим, что использование

auth.wiki('sidebar')
равносильно использованию
auth.wiki(slug='sidebar')
так как аргумент kwarg слизняка является первым в методе подписи. Верхний пример имеет несколько более простой синтаксис.

Можно также встроить специальные функции вики, такие как поиск по тэгам:

{{=auth.wiki('_search')}}

или облако тегов:

{{=auth.wiki('_cloud')}}

Расширенные возможности auth.wiki

Когда ваше вики-приложение становится более сложным, то возможно, вам потребуется настроить вики записи в db, управляемые с помощью интерфейса Auth или выставить индивидуальные формы для задач CRUD вики. Например, вы можете захотеть настроить представления записи вики в таблице или добавить новый валидатор для поля. Это не допускается по умолчанию, так как модель вики определяется только после того, как интерфейс вики запрашивается методом auth.wiki(). Для того, чтобы разрешить доступ к конкретной установки db вики в рамках модели вашего приложения, вы должны добавить следующее предложение в файл модели (т.е. в db.py)

# Убедитесь, что этот вызов выполняется после создания экземпляра auth
# и до внесения каких-либо изменений в таблицы вики
auth.wiki(resolve=False)

Используя приведенную выше строку в вашей модели, таблицы вики будут доступны (т.е. wiki_page) для пользовательских CRUD или других задач db.

Обратите внимание, что вам все равно придется вызвать auth.wiki() в контроллере или представлении для того, выставить интерфейс вики, так как resolve=False параметр указывает объекту auth просто построить модель вики без дополнительной настройки интерфейса.

Кроме того, установив resolve на False в вызове метода, таблицы вики теперь будут доступны через интерфейс db приложения по умолчанию в <app>/appadmin для управления вики записями.

Еще одна настройка возможна путем добавления дополнительных полей к стандартным таблицам вики (таким же образом, как и с auth_user таблицей, как описано в Главе 9). А именно, вот так:

# Поместите это после инициализации объекта auth
auth.settings.extra_fields["wiki_page"] = [Field("ablob", "blob"),]

Строка выше добавляет blob поле к wiki_page таблице. Здесь нет необходимости вызывать

auth.wiki(resolve=False)
для этой опции, если вам не требуется доступ к модели вики для других настроек.

Компоненты

Одной из самых мощных функций нового web2py состоит в способности встраивания действия внутрь другого действия. Мы называем это компонентом.

Рассмотрим следующую модель:

db.define_table('thing',Field('name',requires=IS_NOT_EMPTY()))

и следующее действие:

@auth.requires_login()
def manage_things():
    return SQLFORM.grid(db.thing)

Это действие является особенным, потому что оно возвращает widget/helper не как dict объекты. Теперь мы можем встроить это manage_things действие в любое представление, добавив

{{=LOAD('default','manage_things',ajax=True)}}

Это позволяет посетителю взаимодействовать с компонентом через Ajax без перезагрузки страницы хоста, который встраивает виджет.Действие вызывается через Ajax, наследует стиль страницы хоста, и захватывает все формы представления и флэш-сообщения так, чтобы они обрабатываются в пределах текущей страницы. Вдобавок к этому, SQLFORM.grid виджет использует цифровую подпись URL-адресов для ограничения доступа. Более подробную информацию о компонентах можно найти в Главе 13.

Такие компоненты, как один из приведенных выше могут быть встроены в вики-страницы с помощью синтаксиса MARKMIN:

@{component:default/manage_things}

Данное выражение просто говорит web2py, что мы хотим включить действие "manage_things", определенное в контроллере "default" в качестве "компонента" Ajax.

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

Подробнее об admin

admin

Административный интерфейс обеспечивает дополнительные функциональные возможности, которые мы кратко рассмотрим здесь.

Страница Site

site

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

Первая из них показывает версию web2py и предлагает обновить его, если новые версии доступны. Конечно, перед обновлением обязательно иметь полную рабочую резервную копию! Также существуют две другие формы, которые позволяют создавать новое приложение (просто или с помощью онлайн мастера), указав его имя.

Instant Press
Movuca
Следующая форма позволяет загружать существующее приложение либо из локального файла или с удаленного URL-адреса. При загрузке приложения, необходимо указать имя для него (использование различных имен позволяет установить несколько копий одного и того же приложения). Вы можете попробовать, например, загрузить приложение Movuca Social Networking application созданное Bruno Rocha:

https://github.com/rochacbruno/Movuca

или Instant Press CMS созданное Martin Mulone:

http://code.google.com/p/instant-press/

или одно из множества примеров приложений, доступных в:

http://web2py.com/appliances

Web2py файлы упакованы как .w2p файлы. Они представляют собой файлы tar gzip архивы. Web2py использует .w2p расширение вместо .tgz расширения для предупреждения браузера от разархивации при загрузке. Они могут быть распакованы вручную с tar zxvf [filename] хотя в этом нет необходимости.

image

После успешной загрузки web2py отображает контрольную MD5 сумму загруженного файла. Вы можете использовать ее, чтобы убедиться, что файл не был поврежден во время загрузки. Имя InstantPress появится в списке установленных приложений.

Если вы запускаете web2py из исходного кода и у вас есть установленный gitpython(При необходимости, установите его командой 'easy_install gitpython'), вы можете устанавливать приложения непосредственно из Git репозиториев, используя .git URL в форме загрузки. В этом случае вы также будете иметь возможность использовать интерфейс администратора, чтобы подтолкнуть изменения обратно в репозиторий, но это экспериментальная возможность.

Например, вы можете локально установить приложение, которое показывает эту книгу на сайте web2py с URL:

https://github.com/mdipierro/web2py-book.git

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

Для каждого установленного приложения вы можете использовать страницу site, чтобы:

  • Перейти непосредственно к приложению, нажав на его название.
  • Удалить приложение.
  • Перейти к странице Об(читайте ниже).
  • Перейти к странице Правка (читайте ниже).
  • Перейти к странице Ошибки (читайте ниже).
  • Очистить временные файлы (сессий, ошибок и cache.disk файлов).
  • Упаковать все. Возвращает архивный tar файл, содержащий полную копию приложения. Мы предлагаем вам очистить временные файлы перед упаковкой приложения.
  • Скомпилировать приложение. Если ошибок нет, то эта опция компилирует в байткод все модели, контроллеры и представления. Поскольку представления могут расширяться и включать в себя другие представления в дереве , перед компиляцией байт-код, просмотрите дерево чтобы каждый контроллер был свернут в один файл. Результирующий эффект состоит в том, что байткод скомпилированные приложения работают быстрее, потому что нет больше синтаксического анализа шаблонов или строк замещений, происходящих во время выполнения.
  • Упаковать скомпилированное. Эта опция присутствует только для байт-код скомпилированных приложений. Она позволяет упаковывать приложение без исходного кода для распространения в качестве закрытого исходного кода. Обратите внимание, что Python (как и любой другой язык программирования) может быть технически декомпилирован; поэтому компиляция не обеспечивает полную защиту исходного кода. Тем не менее, декомпиляции может быть затруднена и может быть незаконной.
  • Удалить скомпилированное. Он просто удаляет байткод скомпилированных моделей, представлений и контроллеров из приложения. Если приложение было упаковано с исходным кодом или отредактированные локально, то нет никакого вреда в удалении байткод скомпилированных файлов, и приложение будет продолжать работать. Если приложение было установлено в форме упакованного скомпилированного файла, то это не безопасно, так как нет исходного кода, чтобы возвратиться назад, и приложение больше не будет работать.
admin.py

Все функциональные возможности доступны на странице администратора сайта web2py, а также доступны программно через API, определенного в модуле gluon/admin.py. Просто откройте оболочку Python и импортируйте этот модуль.

Если Google App Engine SDK установлен, то на странице администратора site показывается кнопка, которая подталкивает ваши приложения на GAE. Если установлен модуль python-git, то также показывается кнопка, которая подталкивает ваши приложения на Open Shift. Для установки приложения на Heroku или другую систему хостинга вы должны заглянуть в папку "scripts" для соответствующего сценария.

Страница "Об"

about
license

Вкладка About позволяет редактировать описание приложения и его лицензию. Они написаны соответственно в файлах ABOUT and LICENSE в папке приложения.

image

Вы можете использовать MARKMIN, или gluon.contrib.markdown.WIKI синтаксис для этих файлов, как описано в ссылке.[markdown2] .

Дизайн

EDIT

Вы уже использовали страницу Правка в этой главе. Здесь мы хотим отметить несколько больше функциональных возможностей на странице Правка.

  • Если вы нажмете на любое имя файла, то вы сможете увидеть содержимое файла с подсветкой синтаксиса.
  • Если вы нажмете на редактирования, то вы сможете редактировать файл через веб-интерфейс.
  • Если вы нажмете на удаление, то вы сможете удалить файл (постоянно).
  • Если вы нажмете на тест, то web2py запустит тесты. Тесты написаны разработчиком с использованием Python модуля doctests, и каждая функция должна иметь свои собственные тесты.
  • Вы можете добавлять языковые файлы, сканировать приложение, чтобы найти все строки, а также редактировать переводы строк через веб-интерфейс.
  • Если статические файлы организованы в папки и подпапки, то иерархия папок может быть переключена при нажатии на имя папки.

На рисунке ниже показан вывод тестовой страницы для приложения welcome.

image

На рисунке ниже показана вкладка Языки для приложения welcome.

image

На рисунке ниже показано, как отредактировать файл языка, в данном случае "it" (итальянский) язык для приложения welcome.

image

Встроенный веб-отладчик

(требуется Python 2.7 или ниже)

Web2py администратор включает в себя веб-отладчик.

debugger

С помощью прилагаемого веб-редактора вы можете добавить точки останова в коде Python, и из соответствующего отладчика консоли, вы можете проверить системные переменные на этих контрольных точках и возобновить выполнение. Это проиллюстрировано на следующем снимке экрана: Интерактивная консоль также служит как Python блокнот.

image

Эта функциональность основана на Qdb отладчик, созданным Mariano Reingart. Он использует multiprocessing.connection для обмена данными между внутренним интерфейсом и внешним интерфейсом, с JSON-RPC-подобным протоколом потока. [qdb]

Установка точек прерывания с помощью кода
breakpoints

Включите это:

from gluon.debug import dbg

и войдите в отладчик, поместите это в нужное место:

dbg.set_trace()

Отладчик приложения имеет менеджер точек останова.

Примечание: web2py не знает, действительно ли у вас есть открытое в вашем браузере окно отладки; исполнение приостанавливается независимо от этого.

Интегрированные среды разработки (IDEs), как правило, имеют свой собственный межпроцессный отладчик, например PyCharm или PyDev. Они могут пожаловаться, если вы включаете библиотеку gluon.

Веб-оболочка Python

Если вы нажмете на ссылку "Оболочка" под вкладкой контроллеров на странице Правка, то web2py откроет веб-оболочку Python и выполнит модели для текущего приложения. Это позволит вам интерактивно общаться с вашим приложением.

image

Будьте осторожны, используя веб-оболочку - потому что разные запросы оболочки будут выполняться в разных потоках. Это легко дает ошибки, особенно если вы играете с созданием и подключениями к базам данных. Для действий, подобным этим (то есть, если вам нужна устойчивость), то гораздо лучше использовать командную строку Python.

Задания по расписанию Crontab

Кроме того, под вкладкой контроллеров на странице Правка есть ссылка на "crontab". Нажав на эту ссылку вы сможете отредактировать web2py файл crontab. В нем придерживается тот же синтаксис, что и в crontab Unix, но не опирается на Unix. На самом деле, он требует только web2py и работает на Windows. Он позволяет регистрировать действия, которые должны выполняться в фоновом режиме по расписанию. Для получения дополнительной информации об этом смотрите следующую главу.

Ошибки Errors

errors

При программировании web2py, вы неизбежно будете совершать ошибки и вносить ошибки. Web2py помогает двумя способами:

  • 1) Он позволяет создавать тесты для каждой функции, которые могут быть запущены в браузере на странице Правка;
  • 2) Когда ошибка проявляется, то посетителю выдается билет ошибки и ошибка регистрируется.

Преднамеренно введем ошибку в приложении изображений, как показано ниже:

def index():
    images = db().select(db.image.ALL,orderby=db.image.title)
    1/0
    return dict(images=images)

При доступе к действию index, вы получите следующий билет:

image

Только администратор может получить доступ к билету:

image

Билет показывает traceback и содержимое файла, вызвавшего проблему, и полное состояние системы (переменные, запрос, сессия и т.д.) Если ошибка возникает в представлении, web2py показывает представление преобразованное из HTML в код Python. Это позволяет легко определить логическую структуру файла.

По умолчанию билеты хранятся в файловой системе и отображаются сгруппированными по traceback. Административный интерфейс обеспечивает суммарные представления (типа traceback и числа случаев) и детальное представление (все билеты перечислены по ID билета). Администратор может переключаться между этими двумя представлениями.

Обратите внимание на то, что admin везде показывает синтаксически-подсвеченный код (например, в отчетах об ошибках, ключевые слова web2py отображаются оранжевым цветом). Если вы нажмете на ключевое слово web2py, то вы будете перенаправлены на страницу документации по ключевому слову.

Если вы исправите ошибку деления на ноль в действии index и внесете одну в представлении index:

{{extend 'layout.html'}}

<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{1/0}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>

вы получите следующий билет:

image

Обратите внимание, что web2py преобразовал представление из HTML в файл Python, и ошибка, описанная в билете относится к сгенерированному коду Python, а не к первоначальному файлу представления:

image

Это может показаться странным на первый взгляд, но на практике это делает отладку проще, потому что отступы Python подчеркивают логическую структуру кода, который вы внедрили в представления.

Код показан в нижней части той же страницы.

Все билеты перечислены под администратором на странице "ошибки" для каждого приложения:

image

Контроль версий Mercurial

Mercurial

Если вы работаете из исходников, то в административном интерфейсе отображается еще один пункт меню под названием "Контроль версий (Versioning)".

images

Введя комментарий и нажав кнопку "Зафиксировать" в открывшейся странице будет зафиксировано текущее приложение. С первой фиксации, будет создан локальный репозиторий Mercurial для конкретного приложения. Под капотом, Mercurial хранит информацию об изменениях, которые вы делаете в вашем коде в скрытой папке ".hg" в подпапке вашего приложения. Каждое приложение имеет свой собственную папку ".hg" и свой собственный файл ".hgignore" (говорит Mercurial, какие файлы необходимо игнорировать). Для того, чтобы использовать эту функцию, у вас должны быть установлены библиотеки Mercurial для управления версиями(по крайней мере версия 1.9):

pip install mercurial

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

Вы можете прочитать больше о Mercurial здесь:

http://mercurial.selenic.com/

Интеграция с Git

git

Приложение администратора также включает в себя интеграцию GIT. Необходимы Python Git библиотеки, например

pip install gitpython

и затем для каждого приложения, вам необходимо выполнить клонирование или иным образом настроить репозиторий Git.

После выполнения этих шагов, в меню Управление для каждого git-управляемого приложения будут отображаться кнопки git push и git pull. Приложения, которые не управляются Git игнорируются. Вы можете вытаскивать (pull) и заталкивать (push) приложения в удаленный репозиторий по умолчанию.

Мастер приложений (экспериментальный)

Интерфейс администратора включает в себя мастер, который может помочь вам создать новые приложения. Вы можете получить доступ к Мастеру со страницы "site", как показано на изображении ниже.

image

Мастер проведет вас через ряд шагов, связанных с созданием нового приложения:

  • Выбор имени приложения
  • Настройка приложение и выбор необходимых плагинов
  • Построение требуемых моделей (это создаст CRUD страницы для каждой модели)
  • Редактирование представления этих страниц с помощью синтаксиса MARKMIN

На рисунке ниже показан второй шаг процесса.

image

Вы можете увидеть выпадающее меню выбора плагина макета (из web2py.com/layouts), выпадающее меню с множественным выбором для проверки других плагинов (из web2py.com/plugins) и поле "login config" для ввода Janrain "domain:key".

Остальные шаги в значительной степени говорят сами за себя.

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

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

В любом случае мастер представляет собой удобный инструмент для быстрого создания прототипов и он может быть использован для создания нового приложения с альтернативной компоновкой и дополнительными плагинами.

Конфигурирование admin

Как правило, нет необходимости выполнять конфигурацию admin, но несколько пользовательских настроек все же возможны. После входа в интерфейс администратора вы можете отредактировать файл конфигурации интерфейса администратора через URL:

http://127.0.0.1:8000/admin/default/edit/admin/models/0.py

Обратите внимание на то, что приложение admin может быть использовано для редактирования самого себя. На самом деле admin это приложение, как и любое другое.

Файл "0.py" более или менее самодокументированный, во всяком случае, вот некоторые из наиболее важных возможных настроек:

GAE_APPCFG = os.path.abspath(os.path.join('/usr/local/bin/appcfg.py'))

Этим указывается на расположение файла "appcfg.py", который поставляется с Google App Engine SDK. Если у вас есть SDK, то вы можете изменить эти параметры конфигурации на правильное значение. Это позволит вам развернуть сервер на GAE из интерфейса администратора.

DEMO_MODE

Вы можете также установить web2py Администратор в демонстрационном режиме:

DEMO_MODE = True
FILTER_APPS = ['welcome']

И только приложения, перечисленные в FILTER_APPS будут доступны и они будут доступны в режиме только для чтения.

MULTI_USER_MODE
virtual laboratory

Если вы учитель и хотите выставить административный интерфейс для студентов с тем чтобы учащиеся смогли совместно использовать один административный интерфейс для своих проектов (подразумевается виртуальная лаборатория), то можете сделать это путем установки:

MULTI_USER_MODE = True

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

В многопользовательском режиме, вы можете зарегистрировать студентов используя ссылку "bulk register" на панели Администратора и управлять ими используя ссылку "manage students". Кроме того, система отслеживает, когда студенты вошли и сколько строк кода они добавили в код/удалили из своего кода. Эти данные представлены администратору в виде графиков под страницей "about" приложения.

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

Мобильная панель Администратора

Обратите внимание на то, что приложение admin включает в себя "plugin_jqmobile" который входит в пакет jQuery Mobile. Когда admin получает доступ с мобильного устройства, это обнаруживается web2py и интерфейс отображается с помощью мобильно-дружественного макета:

image

Подробнее об appadmin

appadmin

appadmin не предназначен для общего доступа. Он предназначен, чтобы помочь вам, предоставляя легкий доступ к базе данных. Он состоит только из двух файлов: контроллер "appadmin.py" и представление "appadmin.html" которое используются всеми действиями в контроллере.

Контроллер appadmin является относительно небольшим и читаемым; он обеспечивает пример разработки интерфейса базы данных.

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

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

Для обновления записей, введите задание SQL в строковое поле Query:

title = 'test'

где строковые значения должны быть заключены в одиночные кавычки. Несколько полей могут должны быть разделены запятыми.

Чтобы удалить запись, нажмите на соответствующий флажок, чтобы подтвердить, что вы уверены,.

appadmin может также выполнять присоединение, если SQL FILTER содержит SQL условие, которое включает в себя две или более таблиц. Например, попробуйте:

db.image.id == db.post.image_id

web2py передает это наряду с DAL, и он понимает, что запрос связывает две таблицы; следовательно, обе таблицы выбираются с INNER JOIN. Вот результат:

image

Если вы нажмете на номере в поле id, то вы получите страницу редактирования для записи с соответствующим id.

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

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

В дополнение к своим возможностям администрирования баз данных, appadmin также позволяет просматривать подробную информацию о содержании cache приложения (из /yourapp/appadmin/cache) а также содержимое текущих request, response, и session объектов (из /yourapp/appadmin/state).

appadmin заменяет response.menu своим собственным меню, которое содержит ссылки на страницу Правка приложения в панели admin, страницу db (администрирование баз данных), страницу state, и страницу cache. Если макет вашего приложения не генерирует меню с помощью response.menu, то вы не увидите меню appadmin. В этом случае, вы можете изменить appadmin.html файл и добавить {{=MENU(response.menu)}} для отображения меню.

 top