X-Git-Url: http://git.veekun.com/zzz-pokedex.git/blobdiff_plain/ec65b5ae366174912cedf0e263d425378220d70b..5866276b607e0e1d1a5762c0da6c573b8fdce0f6:/pokedex/db/multilang.py?ds=inline diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py index e2d9009..8ac1bfc 100644 --- a/pokedex/db/multilang.py +++ b/pokedex/db/multilang.py @@ -1,11 +1,11 @@ from functools import partial from sqlalchemy.ext.associationproxy import association_proxy -from sqlalchemy.orm import compile_mappers, mapper, relationship, synonym +from sqlalchemy.orm import aliased, compile_mappers, mapper, relationship, synonym from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.session import Session, object_session from sqlalchemy.schema import Column, ForeignKey, Table -from sqlalchemy.sql.expression import and_, bindparam +from sqlalchemy.sql.expression import and_, bindparam, select from sqlalchemy.types import Integer def create_translation_table(_table_name, foreign_class, relation_name, @@ -17,7 +17,6 @@ def create_translation_table(_table_name, foreign_class, relation_name, `foreign_class` must have a `__singlename__`, currently only used to create the name of the foreign key column. -TODO remove this requirement Also supports the notion of a default language, which is attached to the session. This is English by default, for historical and practical reasons. @@ -48,6 +47,7 @@ TODO remove this requirement 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. Note that these are distinct relations. Even though the former necessarily includes the latter, SQLAlchemy doesn't treat them as linked; loading one @@ -68,21 +68,16 @@ TODO remove this requirement # want to create tables entirely separate from the pokedex metadata foreign_key_name = foreign_class.__singlename__ + '_id' - # A foreign key "language_id" will clash with the language_id we naturally - # put in every table. Rename it something else - if foreign_key_name == 'language_id': - # TODO change language_id below instead and rename this - foreign_key_name = 'lang_id' Translations = type(_table_name, (object,), { - '_language_identifier': association_proxy('language', 'identifier'), + '_language_identifier': association_proxy('local_language', 'identifier'), }) - + # 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), - Column('language_id', Integer, ForeignKey(language_class.id), + Column('local_language_id', Integer, ForeignKey(language_class.id), primary_key=True, nullable=False), ) Translations.__table__ = table @@ -98,34 +93,39 @@ TODO remove this requirement # Construct ye mapper mapper(Translations, table, properties={ - # TODO change to foreign_id - 'object_id': synonym(foreign_key_name), - # TODO change this as appropriate - 'language': relationship(language_class, - primaryjoin=table.c.language_id == language_class.id, + 'foreign_id': synonym(foreign_key_name), + 'local_language': relationship(language_class, + primaryjoin=table.c.local_language_id == language_class.id, lazy='joined', innerjoin=True), - # TODO does this need to join to the original table? }) # Add full-table relations to the original class + # Foo.bars_table + setattr(foreign_class, relation_name + '_table', Translations) # Foo.bars setattr(foreign_class, relation_name, relationship(Translations, - primaryjoin=foreign_class.id == Translations.object_id, - collection_class=attribute_mapped_collection('language'), + 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. + # 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. 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.object_id, - Translations._language_identifier == - bindparam('_default_language', required=True), + foreign_class.id == Translations.foreign_id, + Translations.local_language_id == select( + [language_class_a.id], + language_class_a.identifier == + bindparam('_default_language', required=True), + ), ), uselist=False, # TODO MORESO HERE @@ -143,7 +143,7 @@ TODO remove this requirement # these are passed as *args anyway def creator(language, value): row = Translations() - row.language = language + row.local_language = language setattr(row, name, value) return row setattr(foreign_class, name + '_map', @@ -153,11 +153,7 @@ TODO remove this requirement return Translations class MultilangSession(Session): - """A tiny Session subclass that adds support for a default language. - - Change the default_language attribute to whatever language's IDENTIFIER you - would like to be the default. - """ + """A tiny Session subclass that adds support for a default language.""" default_language = 'en' def execute(self, clause, params=None, *args, **kwargs):