Skip to content

Instantly share code, notes, and snippets.

@hetsch
Created January 11, 2012 09:51
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 hetsch/1593960 to your computer and use it in GitHub Desktop.
Save hetsch/1593960 to your computer and use it in GitHub Desktop.
Multilingual model for appengine ndb
import functools
import unittest2
import os
from google.appengine.api import datastore_errors
from google.appengine.ext.ndb import model
from google.appengine.ext.ndb import tasklets
from google.appengine.ext import testbed
#---------------------------------------
# M U L T I V A L U E
# This snippet was born by the following discussion
# https://groups.google.com/forum/#!topic/appengine-ndb-discuss/b8vUNZPzZoc
#---------------------------------------
class InvalidKeyError(Exception):
pass
class MultiValueProperty(model.PickleProperty):
def __init__(self, property_instance, *args, **kwargs):
self.property_instance = property_instance
super(MultiValueProperty, self).__init__(*args, **kwargs)
def _set_value(self, entity, value, key=None):
key = key or entity.curr_key
curr_val = self._get_user_value(entity)
if curr_val is None:
curr_val = {}
curr_val[key] = value
super(MultiValueProperty, self)._set_value(entity, curr_val)
def _get_value(self, entity, key=None):
key = key or entity.curr_key
curr_val = super(MultiValueProperty, self)._get_value(entity)
return curr_val.get(key, None)
def _validate(self, value):
if not isinstance(value, dict):
raise datastore_errors.BadValueError('Expected dict, got %r' %
(value,))
# @TODO check if this is the right place to validate the values
# what's the difference between _validate() and _do_validate() ?
for key, value in value.iteritems():
if self.property_instance._repeated:
if not isinstance(value, (list, tuple, set, frozenset)):
raise datastore_errors.BadValueError('Expected list or tuple, got %r' %
(value,))
value = [self.property_instance._do_validate(v) for v in value]
else:
value = self.property_instance._do_validate(value)
return super(MultiValueProperty, self)._validate(self._to_base_type(value))
class MultiValueMetaModel(model.MetaModel):
def __init__(cls, name, bases, classdict):
super(MultiValueMetaModel, cls).__init__(name, bases, classdict)
for name in set(dir(cls)):
attr = getattr(cls, name, None)
if isinstance(attr, MultiValueProperty):
for key in cls.VALID_KEYS:
key_attr_name = '%s_%s' % (name, key)
# creating shorthand getters and setters for language property
# currying the getter and setter functions
# seems like we have to proxy that calls over the model instance
# to know the instance type
setattr(cls, key_attr_name, property(
functools.partial(cls._get_key_value, attr=attr, key=key),
functools.partial(cls._set_key_value, attr=attr, key=key)
))
class MultiValueModel(model.Model):
__metaclass__ = MultiValueMetaModel
VALID_KEYS = ()
def __init__(self, curr_key=None, *args, **kwargs):
self.curr_key = curr_key
super(MultiValueModel, self).__init__(*args, **kwargs)
def _get_curr_key(self):
return self._curr_key
def _set_curr_key(self, value):
if not value in self.VALID_KEYS:
raise InvalidKeyError('Key is not defined in VALID_KEYS list!')
self._curr_key = value
curr_key = property(_get_curr_key, _set_curr_key)
# see metaclass for more info
def _get_key_value(self, attr=None, key=None):
if attr and key:
return attr._get_value(self, key)
# see metaclass for more info
def _set_key_value(self, value, attr=None, key=None):
if attr and key:
attr._set_value(self, value, key)
#---------------------------------------
# M U L T I L I N G U A L
#---------------------------------------
class InvalidLanguageError(InvalidKeyError):
pass
class MultilingualProperty(MultiValueProperty):
pass
class MultilingualModel(MultiValueModel):
VALID_KEYS = ('en', 'de')
DEFAULT_LANGUAGE = 'en'
def __init__(self, curr_key=DEFAULT_LANGUAGE, *args, **kwargs):
super(MultilingualModel, self).__init__(curr_key, *args, **kwargs)
def _get_language(self):
return self.curr_key
def _set_language(self, value):
try:
self.curr_key = value
except InvalidKeyError, e:
raise InvalidLanguageError('Language is not defined in VALID_KEYS list!')
language = property(_get_language, _set_language)
#---------------------------------------
# T E S T I N G
#---------------------------------------
class MultilingualModelDummy(MultilingualModel):
title = MultilingualProperty(model.StringProperty())
class BaseTest(unittest2.TestCase):
def setUp(self):
os.environ['HTTP_HOST'] = 'localhost'
self.testbed = testbed.Testbed()
self.testbed.activate()
self.testbed.init_datastore_v3_stub()
# Only when testing ndb.
self.reset_kind_map()
self.setup_context_cache()
def tearDown(self):
self.testbed.deactivate()
def reset_kind_map(self):
model.Model._reset_kind_map()
def setup_context_cache(self):
ctx = tasklets.get_context()
ctx.set_cache_policy(False)
ctx.set_memcache_policy(False)
def register_model(self, name, cls):
model.Model._kind_map[name] = cls
class MutlilingualTest(BaseTest):
def setUp(self):
super(MutlilingualTest, self).setUp()
self.register_model('MultilingualModelDummy', MultilingualModelDummy)
def testInsertEntityNoRepeat(self):
entity = MultilingualModelDummy()
entity.put()
self.assertEqual(1, len(MultilingualModelDummy.query().fetch(2)))
entity.language = 'en'
entity.title = 'title_en'
entity.put()
db_saved = MultilingualModelDummy.query().get()
db_saved.language = 'en'
self.assertEqual('title_en', db_saved.title)
entity.language = 'de'
entity.title = 'title_de'
entity.put()
db_saved = MultilingualModelDummy.query().get()
db_saved.language = 'de'
self.assertEqual('title_de', db_saved.title)
with self.assertRaises(InvalidLanguageError):
entity.language = 'jp'
with self.assertRaises(AttributeError):
entity.title_jp
with self.assertRaises(datastore_errors.BadValueError):
entity.title = 21
with self.assertRaises(datastore_errors.BadValueError):
entity.title = None
def testInsertEntityShorthandNoRepeat(self):
entity = MultilingualModelDummy()
entity.put()
entity.title_de = 'title_de'
entity.put()
entity.title_en = 'title_en'
entity.put()
self.assertEqual('title_de', entity.title_de)
self.assertEqual('title_en', entity.title_en)
entity.title_de = 'aaa'
self.assertEqual('aaa', entity.title_de)
self.assertEqual('title_en', entity.title_en)
# Testing default language
entity = MultilingualModelDummy()
self.assertEqual('en', entity.language)
entity.title_de = 'title_de'
entity.title_en = 'title_en'
entity.put()
# Testing if the model language changed after setting shorthand
# attributes
self.assertEqual('en', entity.language)
# Testing if setting the model language affects the attributes in
# the expected manner
entity.language = 'de'
entity.title = 'aaa'
self.assertEqual('de', entity.language)
self.assertEqual('aaa', entity.title)
self.assertEqual('aaa', entity.title_de)
self.assertEqual('title_en', entity.title_en)
@hetsch
Copy link
Author

hetsch commented Jan 11, 2012

Please leave some comments here if you use this snippet and where...
If you have any ideas how we can make the code more stable or you wanna share some ideas you are more than welcome to comment on that gist!

Regards,
Gernot

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