Задача реализации надежного хранилища данных для масштабируемого web приложения очень нетривиальна. Внешний пользователь может взаимодействовать одновременно с десятком серверов и скорее всего следующий его запрос попадет на другой сервер. В связи с этим, приложения должны иметь возможность легко оперировать с данными, собранными с этих всех систем, учитывая, что они могут располагаться в датацентрах по всему миру.
Благодаря Google App Engine нет необходимости задумываться об этой проблеме. Инфраструктура платформы берет на себя заботу о хранении, распределении и балансировке нагрузки обработки данных путем предоставления разработчику простого интерфейса, который позволяет выполнять запросы и производить транзакции.
Платформа App Engine включает в себя интерфейс API для оперирования данными на языке Python. Она похожа на модели Django, но использует для своей работы масштабируемое хранилище Google.
Для нашего приложения мы хотим реализовать возможность сохранять сообщения, полученный от пользователей. Каждое сообщение будет включать имя автора, его содержание и дату когда оно было получено. Таким образом, у нас появится возможность отобразить их в хронологическом порядке.
Откройте файл helloworld/helloworld.py и добавьте следующие операторы import в его начало:
from google.appengine.ext import db
Добавьте следующий класс сразу после определения класса MainPage:
class Greeting(db.Model): author = db.UserProperty() content = db.StringProperty(multiline=True) date = db.DateTimeProperty(auto_now_add=True)
Этот код определит модель Greeting с тремя атрибутами : author со значением объекта User, content, содержащего строковые данные и date с типом datetime.datetime.
Некоторые определения атрибутов могут принимать дополнительные параметры. Указывая в конструкторе db.StringProperty параметр multiline=True, мы сообщаем, что значение в этом атрибуте может содержать символы перевода строки. Указывая в конструкторе db.DateTimeProperty параметр auto_now_add=True, мы устанавливаем, что если при создании нового объекта не будет указано значение свойства date, то ему автоматически будет присвоено значение текущего времени. Для просмотра полного списка возможных тип свойств и их опций обратитесь к руководству по интерфейсу к хранилищу.
После того, как была создана новая модель данных, приложение может ее задействовать, создав новый объект Greeting и сохранив его в хранилище. Отредактируйте обработчик Guestbook, заменив его содержимое этим кодом:
class Guestbook(webapp.RequestHandler):
def post(self):
greeting = Greeting()
if users.get_current_user():
greeting.author = users.get_current_user()
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
Теперь наш новый обработчик Guestbook создает объект Greeting и присваивает его атрибутам author и content данные, отправленные пользователем. Мы специально не будем устанавливать значение атрибута date, так как ранее определили, чтобы он соответствовал текущему времени при создании объекта.
В завершении, метод greeting.put() сохраняет наш новый объект в хранилище. В том случае, если мы получили этот объект с помощью запроса, метод put() просто сохранит его изменения. Если объект только что был создан с помощью конструктора модели, метод put() добавит новый объект в хранилище.
Для получения объектов данных хранилище платформы App Engine поддерживает сложный язык запросов. Так как хранилище не является традиционной реляционной базой данных, мы не сможем работать с ним через SQL. Однако, имеется возможность осуществлять запросы, используя похожий на него язык, называющийся GQL. GQL предоставляет доступ ко всем возможностям хранилища App Engine, используя SQL-подобный синтаксис.
Отредактируйте обработчик MainPage, заменив его код следующим:
class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write('<html><body>')
greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")
for greeting in greetings:
if greeting.author:
self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname())
else:
self.response.out.write('An anonymous person wrote:')
self.response.out.write('<blockquote>%s</blockquote>' %
cgi.escape(greeting.content))
# Write the submission form and the footer of the page
self.response.out.write("""
<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")
Обновите страницу http://localhost:8080/ в своем браузере и проверьте работу, отправив несколько сообщений.
Запрос осуществляется строкой:
greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")
Альтернативным способом вы можете вызвать метод gql(...) класса Greeting и передать параметры к стандартному запросу SELECT * FROM Greeting:
greetings = Greeting.gql("ORDER BY date DESC LIMIT 10")
Обратите внимание, что также как и в SQL ключевые слова (типа SELECT) нечувствительны к регистру. Однако, имена параметров должны быть указаны, учитывая правильный регистр.
В связи с тем, что запрос извлекает из хранилища полный набор свойств объектов, нет возможности задать выборку каких-то определенных свойств. GQL запросы всегда начинаются со значения SELECT * FROM модель (или также подразумеваются методом gql(...) модели) и этим напоминают свои эквиванеты в SQL.
GQL запрос может содержать условие WHERE, которое произведет отбор результатов через заданный фильтр. В отличии от SQL, запросы GQL не обязательно должны содержать в своих параметрах константы, к ним можно привязывать любое значение. К примеру, можно отобразить только сообщения, отправленные текущим пользователем:
if users.get_current_user():
greetings = Greeting.gql("WHERE author = :1 ORDER BY date DESC",
users.get_current_user())
Вместо именованных параметров также можно использовать позиционные:
greetings = Greeting.gql("WHERE author = :author ORDER BY date DESC",
author=users.get_current_user())
В дополнении к языку запросов GQL, интерфейс к хранилищу данных предоставляет еще один механизм построения запросов через использование методов. Например, запрос может быть подготовлен следущим кодом:
greetings = Greeting.all()
greetings.filter("author =", users.get_current_user())
greetings.order("-date")
Для просмотра полного описания возможностей GQL и интерфейса доступа к данным обратитесь к руководству по работе с хранилищем.
Web сервер из среды разработки для своей работы и отладки приложений использует локальную версию хранилища, задействуя временные файлы. Данные сохраняются так долго, как будут существовать эти временные файлы, а web сервер не будет удалять эту информацию, пока разработчик сам этого не попросит.
Если появится необходимость перед выполнением приложения удалить данные хранилища, воспользуйтесь параметром --clear_datastore при запуске локального сервера:
dev_appserver.py --clear_datastore helloworld/
Теперь мы имеем работающую гостевую книгу, которая может авторизовать посетителей с помощью аккаунтов Google, давать возможность отправлять и отображать сообщения других пользователей. Так как платформа App Engine автоматически масштабирует наше приложение, после того как оно станет популярным не придется переписывать ни строчки кода.
Наша последняя версия смешивает в обработчике MainPage код приложения вместе с содержимым HTML. Это не позволяет быстро изменять внешний вид приложения, что особенно станет заметным при увеличении его сложности и объема. Мы сможем задействовать механизм шаблонов для управления содержанием и поместить стили CSS в отдельные статические файлы.
Перейти к Использование шаблонов.