from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import aliased, compile_mappers, mapper, relationship, synonym
from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.orm.scoping import ScopedSession
from sqlalchemy.orm.session import Session, object_session
from sqlalchemy.schema import Column, ForeignKey, Table
from sqlalchemy.sql.expression import and_, bindparam, select
from sqlalchemy.types import Integer
def create_translation_table(_table_name, foreign_class, relation_name,
- language_class, **kwargs):
+ language_class, relation_lazy='select', **kwargs):
"""Creates a table that represents some kind of data attached to the given
foreign class, but translated across several languages. Returns the new
table's mapped class. It won't be declarative, but it will have a
are rows in the created tables.
- `(relation_name)_local`, a relation to the row in the new table that
matches the current default language.
- - `(relation_name)_class`, the class created by this function.
+ - `(relation_name)_table`, the class created by this function.
Note that these are distinct relations. Even though the former necessarily
includes the latter, SQLAlchemy doesn't treat them as linked; loading one
# Create the table object
table = Table(_table_name, foreign_class.__table__.metadata,
Column(foreign_key_name, Integer, ForeignKey(foreign_class.id),
- primary_key=True, nullable=False),
+ primary_key=True, nullable=False,
+ info=dict(description="ID of the %s these texts relate to" % foreign_class.__singlename__)),
Column('local_language_id', Integer, ForeignKey(language_class.id),
- primary_key=True, nullable=False),
+ primary_key=True, nullable=False,
+ info=dict(description="Language these texts are in")),
)
Translations.__table__ = table
'foreign_id': synonym(foreign_key_name),
'local_language': relationship(language_class,
primaryjoin=table.c.local_language_id == language_class.id,
- lazy='joined',
innerjoin=True),
})
setattr(foreign_class, relation_name, relationship(Translations,
primaryjoin=foreign_class.id == Translations.foreign_id,
collection_class=attribute_mapped_collection('local_language'),
- # TODO
- lazy='select',
))
# Foo.bars_local
# This is a bit clever; it uses bindparam() to make the join clause
- # modifiable on the fly. db sessions know the current language identifier
- # populates the bindparam. The manual alias and join are (a) to make the
- # condition nice (sqla prefers an EXISTS) and to make the columns play nice
- # when foreign_class == language_class.
+ # modifiable on the fly. db sessions know the current language and
+ # populate the bindparam.
+ # The 'dummy' value is to trick SQLA; without it, SQLA thinks this
+ # bindparam is just its own auto-generated clause and everything gets
+ # fucked up.
local_relation_name = relation_name + '_local'
- language_class_a = aliased(language_class)
setattr(foreign_class, local_relation_name, relationship(Translations,
primaryjoin=and_(
- foreign_class.id == Translations.foreign_id,
- Translations.local_language_id == select(
- [language_class_a.id],
- language_class_a.identifier ==
- bindparam('_default_language', required=True),
- ),
+ Translations.foreign_id == foreign_class.id,
+ Translations.local_language_id == bindparam('_default_language_id',
+ value='dummy', type_=Integer, required=True),
),
+ foreign_keys=[Translations.foreign_id, Translations.local_language_id],
uselist=False,
- # TODO MORESO HERE
- lazy='select',
+ #innerjoin=True,
+ lazy=relation_lazy,
))
# Add per-column proxies to the original class
setattr(foreign_class, name + '_map',
association_proxy(relation_name, name, creator=creator))
+ # Add to the list of translation classes
+ foreign_class.translation_classes.append(Translations)
+
# Done
return Translations
class MultilangSession(Session):
- """A tiny Session subclass that adds support for a default language."""
- default_language = 'en'
+ """A tiny Session subclass that adds support for a default language.
+
+ Needs to be used with `MultilangScopedSession`, below.
+ """
+ default_language_id = None
+
+ def __init__(self, *args, **kwargs):
+ if 'default_language_id' in kwargs:
+ self.default_language_id = kwargs.pop('default_language_id')
+
+ super(MultilangSession, self).__init__(*args, **kwargs)
def execute(self, clause, params=None, *args, **kwargs):
if not params:
params = {}
- params.setdefault('_default_language', self.default_language)
+ params.setdefault('_default_language_id', self.default_language_id)
+
return super(MultilangSession, self).execute(
clause, params, *args, **kwargs)
+
+class MultilangScopedSession(ScopedSession):
+ """Dispatches language selection to the attached Session."""
+
+ def __init__(self, *args, **kwargs):
+ super(MultilangScopedSession, self).__init__(*args, **kwargs)
+
+ @property
+ def default_language_id(self):
+ """Passes the new default language id through to the current session.
+ """
+ return self.registry().default_language_id
+
+ @default_language_id.setter
+ def default_language_id(self, new):
+ self.registry().default_language_id = new