Skip to content

Instantly share code, notes, and snippets.

@erichiggins
Last active August 29, 2015 14:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erichiggins/11407998 to your computer and use it in GitHub Desktop.
Save erichiggins/11407998 to your computer and use it in GitHub Desktop.
GAE NDB Revisions: Create automatic versions of your datastore models, allowing you to undo changes!
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Create automatic versions of your App Engine Datastore Models, allowing you to undo changes!
Usage:
import gae_ndb_revisions
# Add two properties to your Model(s).
class MyModel(ndb.Model):
...
ndb_rev_number = ndb.IntegerProperty(default=0) # First rev will be 1, see pre-put.
ndb_rev_latest = ndb.KeyProperty(kind=gae_ndb_revisions.Revision)
ndb_rev_previous = ndb.KeyProperty(kind=gae_ndb_revisions.Revision)
# Add the method calls to your Model hooks.
def _pre_put_hook(self):
# Tasks to run before saving.
gae_ndb_revisions.incr_revision(self)
def _post_put_hook(self, future):
# Tasks to run after saving.
gae_ndb_revisions.save_revision(self, future)
"""
__author__ = 'Eric Higgins'
__copyright__ = 'Copyright 2013, Eric Higgins'
__version__ = '0.0.2'
__email__ = 'erichiggins@gmail.com'
__status__ = 'Development'
import logging
from google.appengine.ext import ndb
__all__ = [
'incr_revision',
'save_revision',
'Revision',
]
def incr_revision(obj):
"""Increment the version number. Intended to be called by pre_put_hook."""
obj.ndb_rev_number += 1 # Increment revision number.
obj.ndb_rev_previous = obj.ndb_rev_latest
_, new_id = Revision.allocate_ids(1)
obj.ndb_rev_latest = ndb.Key(Revision, new_id)
logging.debug(u'%s revision is now %d with ID %d', obj.key, obj.ndb_rev_number, new_id)
def save_revision(obj, future):
"""Save a new Revision. Intended to be called by post_put_hook."""
logging.debug('Asserting a successful put.')
future.get_result()
logging.debug(u'Making call to save revision of %s', obj.key)
Revision.save(obj)
class Revision(ndb.Expando):
"""Stored copy of a revisioned Model, stored copy of latest version."""
obj_key = ndb.KeyProperty()
obj_dict = ndb.PickleProperty()
revision = ndb.IntegerProperty()
previous_key = ndb.KeyProperty(kind=Revision)
created = ndb.DateTimeProperty(auto_now_add=True)
modified = ndb.DateTimeProperty(auto_now=True)
@classmethod
def save(cls, obj):
"""Save a copy of the object instance dictionary as a new Revision."""
logging.info(u'Saving revision %d of %s', obj.ndb_rev_number, obj.key)
obj_dict = obj.to_dict()
rev = cls(key=obj.ndb_rev_latest, obj_key=obj_key, revision=obj.ndb_rev_number, previous=obj.ndb_rev_previous, obj_dict=obj_dict)
# TODO(eric): Use put_async.
return rev.put()
@tylerbrandt
Copy link

I think it would be useful to have the user as a standard property of the Revision model.

@tylerbrandt
Copy link

Also the Kind might be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment