• Официальный сайт SDK
  • Сайт с примерами кода

Библиотека Django Forms в Google App Engine

Александр Пауэр
Апрель 2008

Введение

Эта статья рассказывает о том, как можно использовать стандартную библиотеку для работы с формами в платформе Google App Engine. Библиотека Django Forms позволяет конструировать формы для заполнения данных в HTML разметке, основываясь на информации о конфигурации моделей, и обрабатывать отправленную пользователем информацию, скрывая от разработчика детали взаимодействия с хранилищем.

Что представляет собой библиотека Django Forms?

Библиотека Django Forms включена в состав фреймворка Django. Она использует конфигурацию моделей базы данных и формирует из них HTML формы. Также она содержит серверную функциональность для проверки допустимости введенных значений и помещения этих данных в хранилище. Работая с этой библиотекой, вы можете простым способом интегрировать модели данных в страницы сайта и использовать их для добавления информации в хранилище. После того, как данные будут помещены в хранилище, можно как обычно осуществлять к ним запросы, сформированные на языке GQL.

Как Django Forms взаимодействует с хранилищем?

Класс модели Google App Engine и класс db.Model, используемый Django, не являются идентичными. В результате вы не можете напрямую использовать библиотеку Django для работы с формами в приложении Google App Engine. Однако, в поставку Google App Engine включен собственный модуль db.djangoforms, который стирает различия между спецификацией моделей, используемых Google App Engine и моделями Django. В большинстве случаев, вы можете использовать класс db.djangoforms.ModelForm тем же самым способом, как в обычном приложении Django.

Пример - корзина покупателя

Мы собираемся создать простое приложение, которое будет использовать Django forms и создавать страницы, где пользователь сможет добавлять и объекты в покупательскую корзину, после чего эта информация будет помещена в хранилище. В завершении мы будем использовать язык запросов GQL для загрузки данных и отображения их пользователю.

Сначала мы выполняем импортирование стандартных модулей языка Python и пакетов Google App Engine. Обратите внимание на то, что вы должны импортировать модуль google.appengine.webapp.template до того, как будете загружать остальные модули Django:

import cgi
import wsgiref.handlersfrom google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import templatefrom google.appengine.ext.db import djangoforms

Определение классов модели и форм для нашего примера

Далее, мы определим новую модель для покупательской корзины как наследник класса db.Model. Для каждого объекта, помещенного в корзину, мы будем хранить его имя, количество штук к покупке, цену, дату выбора товара и кем он был выбран:

class Item(db.Model):
  name = db.StringProperty()
  quantity = db.IntegerProperty(default=1)
  target_price = db.FloatProperty()
  priority = db.StringProperty(default='Medium',choices=[
    'High', 'Medium', 'Low'])
  entry_time = db.DateTimeProperty(auto_now_add=True)
  added_by = db.UserProperty()

Далее, мы создадим объект формы, основанный на данных модели. Чтобы сделать это, создаем новый класс - потомок класса djangoforms.ModelForm, и уже в нем дочерний класс Meta, в котором будет задана базовая модель и те поля, которые мы не хотим отображать в форме. В нашем случае, мы будем использовать интерфейс Users API для определения пользователя, который добавил объект в корзину, поэтому не хотим, чтобы поле added_by field отображалось в форме:

class ItemForm(djangoforms.ModelForm):
  class Meta:
    model = Item
    exclude = ['added_by']

Нам также потребуется указать время добавления объекта пользователем. Однако, свойства типа DateTime с установленными параметрами auto_now или auto_now_add автоматически убираются из отображаемой формы. Поэтому нет необходимости включать поле entry_time в наш список исключений.

Определение обработчиков запросов

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

Теперь давайте создадим обработчики запросов для нашей корзины покупателя. Пустая форма будет загружаться через URL и метод GET протокола HTTP, а пользователь будет отправлять данные в приложение через метод POST и тот же самый URL.

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

Определение обработчика запроса метода GET протокола HTTP:

class MainPage(webapp.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>'
                            '<form method="POST" '
                            'action="/">'
                            '<table>')
    # Генерируем форму для нашей корзины и выдаем ее код
    self.response.out.write(ItemForm())
    self.response.out.write('</table>'
                            '<input type="submit">'
                            '</form></body></html>')

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

Далее напишем код метода, обрабатывающего запрос типа POST:

  def post(self):
    data = ItemForm(data=self.request.POST)
    if data.is_valid():
      # Сохраняем данные и перенаправляем пользователя на страницу просмотра
      entity = data.save(commit=False)
      entity.added_by = users.get_current_user()
      entity.put()
      self.redirect('/items.html')
    else:
      # Повторно выводим форму
      self.response.out.write('<html><body>'
                              '<form method="POST" '
                              'action="/">'
                              '<table>')
      self.response.out.write(data)
      self.response.out.write('</table>'
                              '<input type="submit">'
                              '</form></body></html>')

Для проверки, содержит ли введенная информация ошибки, мы используем функцию data.is_valid(). Если ошибки будут обнаружены, то пользователь повторно увидит ту же форму с соответствующими сообщениями об ошибках.

Если все поля были корректно заполнены, метод data.save поместит в хранилище новый объект. Так как мы хотим перед его сохранением указать дополнительную информацию о данных пользователя, то задаем параметр commit=False. Без него, вызов метода save() приведет к тому, что объект сразу же будет сохранен. Мы добавляем информацию о пользователе и затем вызываем метод put(), который производит сохранение данных.

Использование значений по умолчанию является эффективным способом инициализировать объекты в хранилище. Однако, не допускается задание значения по умолчанию текущего пользователя с помощью метода users.get_current_user(), так как это значение будет прокэшировано между запросами. Раздел Класс Property содержит по этому вопросу дополнительную информацию.

Отображение списка

После того, как мы добавили объекты в покупательскую корзину, необходимо отобразить их при запросе страницы по адресу /items.html. Мы создадим еще один обработчик для этой страницы и выполним в нем запрос с использованием на языка GQL.

class ItemPage(webapp.RequestHandler):
  def get(self):
    query = db.GqlQuery("SELECT * FROM Item ORDER BY name")
    for item in query:
      self.response.out.write("%s - к покупке %d шт, стоимостью $%0.2f каждый<br>" %
                              (item.name, item.quantity, item.target_price))

Создание функции main()

После того, как мы закончили определять классы обработчиков запросов, необходимо задать основную функцию приложения main(), для того, чтобы оно корректно обрабатывало CGI запросы:

def main():
  application = webapp.WSGIApplication(
                                       [('/', MainPage),
                                        ('/items.html', ItemPage),
                                        ],
                                       debug=True)
  wsgiref.handlers.CGIHandler().run(application)if __name__=="__main__":
  main()

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

Создание файла app.yaml

Для приложений платформы Google App Engine файл app.yaml содержит конфигурацию того, какой скрипт должен быть задан для конкретного входящего запроса. Наше приложение содержит только один скрипт form.py, и таким образом мы определяем, что все запросы будут обрабатываться им:

application: shoppinglist
version: 1
runtime: python
api_version: 1handlers:
- url: .*
  script: form.py

Редактирование существующих объектов

Мы можем дополнить наш пример, позволив пользователям редактировать существующие в корзине объекты.

Изменение класса ItemPage

Сначала мы добавим после каждого выводимого объекта ссылку, при нажатии которой появится возможность его изменить. Ссылка будет указывать на URL /edit и включать в параметр запроса идентификатор этого объекта.

class ItemPage(webapp.RequestHandler):
  def get(self):
    query = db.GqlQuery("SELECT * FROM Item ORDER BY name")
    for item in query:
      self.response.out.write('<a href="/edit?id=%d">Изменить</a> - ' %
                              item.key().id())
      self.response.out.write("%s - к покупке %d шт, стоимостью $%0.2f каждый<br>" %
                              (item.name, item.quantity, item.target_price))

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

Теперь для того, чтобы обрабатывать запросы на редактирование объекта, нам нужно добавить определение класса EditPage. Этот обработчик будет похож на другие, используемые для добавления нового объекта.

В методе get() нашего класса EditPage мы сначала загрузим редактируемый объект из хранилища, затем передадим его обработчику формы, который произведет отображение данных. Также мы добавим в форму скрытое поле идентификатора объекта для того, чтобы потом его можно было однозначно идентифицировать:

class EditPage(webapp.RequestHandler):
  def get(self):
    id = int(self.request.get('id'))
    item = Item.get(db.Key.from_path('Item', id))
    self.response.out.write('<html><body>'
                            '<form method="POST" '
                            'action="/edit">'
                            '<table>')
    self.response.out.write(ItemForm(instance=item))
    self.response.out.write('</table>'
                            '<input type="hidden" name="_id" value="%s">'
                            '<input type="submit">'
                            '</form></body></html>' % id)

Далее напишем код метода, выполняющего обработку HTTP запроса метода POST. Мы выполним загрузку редактируемого объекта из хранилища, затем проверим достоверность введенной информации с помощью функционала библиотеки Django forms. Также как и ранее, если информация будет корректна, мы сохраним изменения в хранилище, в противном случае заставим пользователя исправить свои ошибки:

  def post(self):
    id = int(self.request.get('_id'))
    item = Item.get(db.Key.from_path('Item', id))
    data = ItemForm(data=self.request.POST, instance=item)
    if data.is_valid():
      # Сохраняем данные и перенаправляем пользователя на страницу просмотра
      entity = data.save(commit=False)
      entity.added_by = users.get_current_user()
      entity.put()
      self.redirect('/items.html')
    else:
      # Повторно выводим форму
      self.response.out.write('<html><body>'
                              '<form method="POST" '
                              'action="/edit">'
                              '<table>')
      self.response.out.write(data)
      self.response.out.write('</table>'
                              '<input type="hidden" name="_id" value="%s">'
                              '<input type="submit">'
                              '</form></body></html>' % id)

Изменение функции main()

Напоследок, нам необходимо добавить определение нового URL и его обработчика в основную функцию программы:

def main():
  application = webapp.WSGIApplication(
                                       [('/', MainPage),
                                        ('/edit', EditPage),
                                        ('/items.html', ItemPage),
                                        ],
                                       debug=True)  wsgiref.handlers.CGIHandler().run(application)

Дополнительные примечания по работе с Django Forms

Библиотека Django Forms выполняет строгую проверку типов данных введенной информации. Однако, она не реализует функции языка Javascript для проведения проверки на стороне клиента. В результате, некоторые типы данных, такие как даты и списки, бывает сложно использовать с Django forms, так как пользователи могут ввести их совершенно не в том формате, который ожидает приложение.