1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
import sqlalchemy as sa
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm.util import has_identity
from .exc import UnknownLocaleError
from .utils import get_current_locale, get_fallback_locale
class Translatable(object):
__translatable__ = {
'fallback_locale': 'en'
}
@hybrid_property
def locale(self):
raise NotImplementedError(
'Your translatable model needs to define locale property.'
)
# Current translation getters and setters
@hybrid_property
def current_translation(self):
return self.translations[get_current_locale(self)]
@current_translation.setter
def current_translation(self, obj):
self.translations[get_current_locale(self)] = obj
@current_translation.expression
def current_translation(cls):
return cls._current_translation
# Fallback translation getters and setters
@hybrid_property
def fallback_translation(self):
return self.translations[get_fallback_locale(self)]
@fallback_translation.setter
def fallback_translation(self, obj):
self.translations[get_fallback_locale(self)] = obj
@fallback_translation.expression
def fallback_translation(cls):
return cls._fallback_translation
# Translations getters and setters
@hybrid_property
def translations(self):
if not hasattr(self, '_translations_mapping'):
self._translations_mapping = TranslationsMapping(self)
return self._translations_mapping
@translations.setter
def translations(self, translations_mapping):
self._translations = translations_mapping
@translations.expression
def translations(self):
return self._translations
@sa.event.listens_for(sa.orm.mapper, 'expire')
def receive_expire(target, attrs):
if isinstance(target, Translatable):
try:
del target._translations_mapping
except AttributeError:
pass
class TranslationsMapping(object):
def __init__(self, obj):
self.obj = obj
self.manager = self.obj.__translatable__['manager']
def __contains__(self, locale):
return locale in self.manager.option(self.obj, 'locales')
def fetch(self, locale):
session = sa.orm.object_session(self.obj)
# If the object has no identity and its not in session or if the object
# has _translations relationship loaded get the locale object from the
# relationship.
if '_translations' not in sa.inspect(self.obj).unloaded or (
not session or not has_identity(self.obj)
):
return self.obj._translations.get(locale)
return session.query(self.obj.__translatable__['class']).get(
sa.inspect(self.obj).identity + (locale, )
)
def __getitem__(self, locale):
if locale in self:
locale_obj = self.fetch(locale)
if locale_obj is not None:
return locale_obj
class_ = self.obj.__translatable__['class']
locale_obj = class_(
translation_parent=self.obj,
locale=locale
)
self.obj._translations[locale] = locale_obj
return locale_obj
raise UnknownLocaleError(locale, self.obj)
def __getattr__(self, locale):
return self.__getitem__(locale)
# Added for py2.6 compatibility
def __length_hint__(self):
return len(self)
def __len__(self):
return len(self.manager.option(self.obj, 'locales'))
@property
def all(self):
return list(self.values())
def values(self):
for locale in self.manager.option(self.obj, 'locales'):
yield self[locale]
def __setitem__(self, locale, translation_obj):
if locale in self:
translation_obj.translation_parent = self.obj
translation_obj.locale = locale
self.obj._translations[locale] = translation_obj
def __repr__(self):
return 'TranslationsMapping(%r)' % self.obj
def items(self):
return [
(locale, self[locale])
for locale in self.manager.option(self.obj, 'locales')
]
def iteritems(self):
for locale in self.manager.option(self.obj, 'locales'):
yield locale, self[locale]
def __iter__(self):
for locale in self.manager.option(self.obj, 'locales'):
yield locale, self[locale]
|