Вот что я делаю.
У меня есть класс MigratingModel, от которого наследуются все мои модели. Вот migrating_model.py:
"""Models which know how to migrate themselves"""
import logging
from google.appengine.ext import db
from google.appengine.api import memcache
class MigrationError(Exception):
"""Error migrating"""
class MigratingModel(db.Model):
"""A model which knows how to migrate itself.
Subclasses must define a class-level migration_version integer attribute.
"""
current_migration_version = db.IntegerProperty(required=True, default=0)
def __init__(self, *args, **kw):
if not kw.get('_from_entity'):
# Assume newly-created entities needn't migrate.
try:
kw.setdefault('current_migration_version',
self.__class__.migration_version)
except AttributeError:
msg = ('migration_version required for %s'
% self.__class__.__name__)
logging.critical(msg)
raise MigrationError, msg
super(MigratingModel, self).__init__(*args, **kw)
@classmethod
def from_entity(cls, *args, **kw):
# From_entity() calls __init__() with _from_entity=True
obj = super(MigratingModel, cls).from_entity(*args, **kw)
return obj.migrate()
def migrate(self):
target_version = self.__class__.migration_version
if self.current_migration_version < target_version:
migrations = range(self.current_migration_version+1, target_version+1)
for self.current_migration_version in migrations:
method_name = 'migrate_%d' % self.current_migration_version
logging.debug('%s migrating to %d: %s'
% (self.__class__.__name__,
self.current_migration_version, method_name))
getattr(self, method_name)()
db.put(self)
return self
MigratingModel
перехватывает преобразование необработанного объекта хранилища данных в полный экземпляр db.Model. Если current_migration_version
отстает от последнего migration_version
класса, то он запускает ряд методов migrate_N()
, которые выполняют тяжелую работу.
Например:
"""Migrating model example"""
# ...imports...
class User(MigratingModel):
migration_version = 3
name = db.StringProperty() # deprecated: use first_name and last_name
first_name = db.StringProperty()
last_name = db.StringProperty()
age = db.IntegerProperty()
invalid = db.BooleanProperty() # to search for bad users
def migrate_1(self):
"""Convert the unified name to dedicated first/last properties."""
self.first_name, self.last_name = self.name.split()
def migrate_2(self):
"""Ensure the users' names are capitalized."""
self.first_name = self.first_name.capitalize()
self.last_name = self.last_name.capitalize()
def migrate_3(self):
"""Detect invalid accounts"""
if self.age < 0 or self.age > 85:
self.invalid = True
На загруженном сайте метод migrate() должен повторить попытку в случае сбоя db.put()
и, возможно, зарегистрировать критическую ошибку, если миграция не сработала.
Я еще не добрался до этого, но в какой-то момент я, вероятно, смешал бы свои миграции из отдельного файла.
Последние мысли
Трудно тестировать на App Engine. Трудно получить доступ к вашим производственным данным в тестовой среде, и в настоящее время сложно или даже невозможно сделать целостную резервную копию моментального снимка. Таким образом, для серьезных изменений рассмотрите возможность создания новой версии с совершенно другим именем модели, которая импортирует старую модель и переносит ее по мере необходимости. (Например, User2
вместо User
). Таким образом, если вам нужно вернуться к предыдущей версии, у вас будет эффективная резервная копия данных.
person
JasonSmith
schedule
14.10.2009