Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Here is what I do.</p> <p>I have a MigratingModel class, which all of my models inherit from. Here is migrating_model.py:</p> <pre><code>"""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 &lt; 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 </code></pre> <p><code>MigratingModel</code> intercepts the conversion from the raw datastore entity to the full db.Model instance. If <code>current_migration_version</code> has fallen behind the class's latest <code>migration_version</code>, then it runs a series of <code>migrate_N()</code> methods which do the heavy lifting.</p> <p>For example:</p> <pre><code>"""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 &lt; 0 or self.age &gt; 85: self.invalid = True </code></pre> <p>On a busy site, the migrate() method should retry if <code>db.put()</code> fails, and possibly log a critical error if the migration didn't work.</p> <p>I haven't gotten there yet, but at some point I would probably mix-in my migrations from a separate file.</p> <h3>Final thoughts</h3> <p>It is hard to test on App Engine. It's hard to get access to your production data in a test environment, and at this time it is difficult-to-impossible to make a coherent snapshot backup. Therefore, for major changes, consider <strong>making a new version that uses a completely different model name</strong> which imports from the old model and migrates as it needs. (For example, <code>User2</code> instead of <code>User</code>). That way, if you need to fall back to the previous version, you have an effective backup of the data.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload