Если вы занимаетесь сопровождением успешного проекта, то часто появляется необходимость в обновлении существующей схемы данных. Эта статья рассказывает о двух основных шагах, которые потребуется сделать, чтобы обновить существующую схему:
Пока мы занимаемся изменением схемы данных, может потребоваться заблокировать возможность пользователям их редактировать в приложении. Необходимо ли это делать или нет зависит только от вашего приложения, но существуют несколько ситуаций (к примеру, попытка добавления последовательно увеличивающегося значения к каждому объекту), когда будет проще обновить существующие данные в то время, пока их никто не может изменить.
Например, имеется простая модель, содержащая данные изображения:
class Picture(db.Model): author = db.UserProperty() png_data = db.BlobProperty() name = db.StringProperty(default='') # Уникальное имя.
Необходимо добавить возможность присваивать каждому изображению свой рейтинг. Для этого нам потребуется сохранить значение общего числа проголосовавших посетителей и их среднюю оценку. Процесс обновления модели очень прост, мы всего лишь добавим к ней два новых свойства:
class Picture(db.Model): author = db.UserProperty() png_data = db.BlobProperty() name = db.StringProperty(default='') # Уникальное имя. num_votes = db.IntegerProperty(default=0) avg_rating = db.FloatProperty(default=0)
Теперь все новые объекты, помещаемые в хранилище, получат значение рейтинга по умолчанию - 0. Обратите внимание, что уже существующие в хранилище объекты не будут автоматически изменены и не получат новые свойства.
Хранилище платформы App Engine не требует того, чтобы все объекты одной модели имели одинаковый набор типов свойств. После обновления структуры моделей и добавлении к ней новых свойств, существующие объекты будут использовать старый набор. В некоторых ситуациях это не доставляет проблем, так как вам не требуется выполнять лишнюю работу. Когда может понадобиться после изменения структуры провести дополнительные действия и обновить существующие объекты, для того чтобы они получили новые свойства? Одна из таких ситуаций возникает, если вы захотите выполнять запросы, которые будут задействовать добавленные свойства. В нашем случае с моделью Picture, запросы по значениям "Самое популярное" или "Менее популярное" не вернут никаких результатов, так как объекты (пока) не имеют новых свойств с их рейтингом. Чтобы исправить это, необходимо обновить существующие в хранилище объекты.
Концептуально, процедура обновления очень проста. Необходимо написать новый обработчик запросов, который будет загружать каждый объект, присваивать новому свойству какое-то значение и сохранять изменения. Однако существуют две проблемы, которые мы должны принять во внимание:
Небольшое предостережение: при написании запроса, который будет получать данные порциями, избегайте использования параметра OFFSET (который не будет правильно работать с большими объемами данных), вместо чего ограничивайте результаты запроса с помощью конструкции WHERE с условием. Если данные содержат свойство с уникальным значением для каждого объекта, это будет сделать довольно просто. К примеру, свойство name модели Picture должно содержать уникальные значения и мы будем использовать выражение WHERE с условием, основывающимся на значении name.
Пример кода:
# Обработчик запроса URL /update_datastore
def get(self):
name = self.request.get('name', None)
if name is None:
# Это первый запрос, извлекаем первый объект из хранилища.
pic = models.Picture.gql('ORDER BY name DESC').get()
name = pic.name
q = models.Picture.gql('WHERE name <= :1 ORDER BY name DESC', name)
pics = q.fetch(limit=2)
current_pic = pics[0]
if len(pics) == 2:
next_name = pics[1].name
next_url = '/update_datastore?name=%s' % urllib.quote(next_name)
else:
next_name = 'FINISHED'
next_url = '/' # Процесс закончен, переходим к основной странице.
# В нашем примере для свойств num_votes и avg_rating используются значения
# по умолчанию - 0, поэтому объект может без их изменения сохранен методом put().
current_pic.put()
context = {
'current_name': name,
'next_name': next_name,
'next_url': next_url,
}
self.response.out.write(template.render('update_datastore.html', context))
Ниже представлен шаблон, который показывает текущий обновляемый объект и перенаправляет браузер с помощью мета-тэга к следующему объекту:
<html>
<head>
<meta http-equiv="refresh" content="0;url="/>
</head>
<body>
<h3>Обновление хранилища</h3>
<ul>
<li>Обновлено: </li>
<li>Следующий: </li>
</ul>
</body>
</html>
Если обновляемые объекты не имеют свойства с уникальным значением, приведенный выше пример не будет работать (он застопорится на том месте, где совпадают значения двух объектов). Необходимо будет дополнить этот код для того, чтобы он работал с объектами, которые могут содержать одинаковые значения. Общая концепция останется та же, однако с помощью конструкции WHERE нужно будет ограничивать количество получаемых результатов в ответ на запрос и производить работу с порциями, вызывая метод put() для каждого объекта.
Если вы произведете удаление свойства из модели, то увидите, что существующие объекты сохранят прежний набор свойств. Это будет видно в Административной консоли, что данные сохранят свое присутствие в хранилище. Для того, чтобы принудительно выполнить очистку старых данных, потребуется запустить цикл, который будет последовательно перебирать все объекты и удалять из них устаревшее содержимое.
delattr для удаления старого свойства, после чего сохраните изменения.Этот метод циклического обновления данных объектов на сегодняшний день является неким "костылем". Однако, в данный момент мы работаем над решением, которое позволит автоматизировать этот процесс. После того, как оно станет доступным, появится более действенный метод для изменения структуры данных без повышенной нагрузки на сервер, которую оказывает вышеописанный метод. Для того, чтобы оставаться в курсе новых возможностей для разработчиков, не забудьте подписаться на блог App Engine.