mirror of
https://github.com/veekun/pokedex.git
synced 2024-08-20 18:16:34 +00:00
Started switching to create_translation_table.
- Moved the function to its own file. - Implemented the session-based default language switching. - Migrated a couple tables.
This commit is contained in:
parent
6a9172151a
commit
68e14e663e
4 changed files with 232 additions and 192 deletions
|
@ -2,6 +2,7 @@ from sqlalchemy import MetaData, Table, engine_from_config, orm
|
|||
|
||||
from ..defaults import get_default_db_uri
|
||||
from .tables import metadata
|
||||
from .multilang import MultilangSession
|
||||
|
||||
|
||||
def connect(uri=None, session_args={}, engine_args={}, engine_prefix=''):
|
||||
|
@ -40,7 +41,7 @@ def connect(uri=None, session_args={}, engine_args={}, engine_prefix=''):
|
|||
|
||||
all_session_args = dict(autoflush=True, autocommit=False, bind=engine)
|
||||
all_session_args.update(session_args)
|
||||
sm = orm.sessionmaker(**all_session_args)
|
||||
sm = orm.sessionmaker(class_=MultilangSession, **all_session_args)
|
||||
session = orm.scoped_session(sm)
|
||||
|
||||
return session
|
||||
|
|
168
pokedex/db/multilang.py
Normal file
168
pokedex/db/multilang.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
from functools import partial
|
||||
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.orm import 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.types import Integer
|
||||
|
||||
def create_translation_table(_table_name, foreign_class, relation_name,
|
||||
language_class, **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
|
||||
`__table__` attribute so you can retrieve the Table object.
|
||||
|
||||
`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.
|
||||
|
||||
Usage looks like this:
|
||||
|
||||
class Foo(Base): ...
|
||||
|
||||
create_translation_table('foo_bars', Foo, 'bars',
|
||||
name = Column(...),
|
||||
)
|
||||
|
||||
# Now you can do the following:
|
||||
foo.name
|
||||
foo.name_map['en']
|
||||
foo.foo_bars['en']
|
||||
|
||||
foo.name_map['en'] = "new name"
|
||||
del foo.name_map['en']
|
||||
|
||||
q.options(joinedload(Foo.bars_local))
|
||||
q.options(joinedload(Foo.bars))
|
||||
|
||||
The following properties are added to the passed class:
|
||||
|
||||
- `(relation_name)`, a relation to the new table. It uses a dict-based
|
||||
collection class, where the keys are language identifiers and the values
|
||||
are rows in the created tables.
|
||||
- `(relation_name)_local`, a relation to the row in the new table that
|
||||
matches the current default language.
|
||||
|
||||
Note that these are distinct relations. Even though the former necessarily
|
||||
includes the latter, SQLAlchemy doesn't treat them as linked; loading one
|
||||
will not load the other. Modifying both within the same transaction has
|
||||
undefined behavior.
|
||||
|
||||
For each column provided, the following additional attributes are added to
|
||||
Foo:
|
||||
|
||||
- `(column)_map`, an association proxy onto `foo_bars`.
|
||||
- `(column)`, an association proxy onto `foo_bars_local`.
|
||||
|
||||
Pardon the naming disparity, but the grammar suffers otherwise.
|
||||
|
||||
Modifying these directly is not likely to be a good idea.
|
||||
"""
|
||||
# n.b.: language_class only exists for the sake of tests, which sometimes
|
||||
# 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'),
|
||||
})
|
||||
|
||||
# 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),
|
||||
primary_key=True, nullable=False),
|
||||
)
|
||||
Translations.__table__ = table
|
||||
|
||||
# Add ye columns
|
||||
# Column objects have a _creation_order attribute in ascending order; use
|
||||
# this to get the (unordered) kwargs sorted correctly
|
||||
kwitems = kwargs.items()
|
||||
kwitems.sort(key=lambda kv: kv[1]._creation_order)
|
||||
for name, column in kwitems:
|
||||
column.name = name
|
||||
table.append_column(column)
|
||||
|
||||
# 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,
|
||||
lazy='joined',
|
||||
innerjoin=True),
|
||||
# TODO does this need to join to the original table?
|
||||
})
|
||||
|
||||
# Add full-table relations to the original class
|
||||
# Foo.bars
|
||||
setattr(foreign_class, relation_name, relationship(Translations,
|
||||
primaryjoin=foreign_class.id == Translations.object_id,
|
||||
collection_class=attribute_mapped_collection('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.
|
||||
local_relation_name = relation_name + '_local'
|
||||
setattr(foreign_class, local_relation_name, relationship(Translations,
|
||||
primaryjoin=and_(
|
||||
foreign_class.id == Translations.object_id,
|
||||
Translations._language_identifier ==
|
||||
bindparam('_default_language', required=True),
|
||||
),
|
||||
uselist=False,
|
||||
# TODO MORESO HERE
|
||||
lazy='select',
|
||||
))
|
||||
|
||||
# Add per-column proxies to the original class
|
||||
for name, column in kwitems:
|
||||
# Class.(column) -- accessor for the default language's value
|
||||
setattr(foreign_class, name,
|
||||
association_proxy(local_relation_name, name))
|
||||
|
||||
# Class.(column)_map -- accessor for the language dict
|
||||
# Need a custom creator since Translations doesn't have an init, and
|
||||
# these are passed as *args anyway
|
||||
def creator(language, value):
|
||||
row = Translations()
|
||||
row.language = language
|
||||
setattr(row, name, value)
|
||||
return row
|
||||
setattr(foreign_class, name + '_map',
|
||||
association_proxy(relation_name, name, creator=creator))
|
||||
|
||||
# Done
|
||||
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.
|
||||
"""
|
||||
default_language = 'en'
|
||||
|
||||
def execute(self, clause, params=None, *args, **kwargs):
|
||||
if not params:
|
||||
params = {}
|
||||
params.setdefault('_default_language', self.default_language)
|
||||
return super(MultilangSession, self).execute(
|
||||
clause, params, *args, **kwargs)
|
|
@ -47,7 +47,7 @@ from sqlalchemy.schema import ColumnDefault
|
|||
from sqlalchemy.types import *
|
||||
from inspect import isclass
|
||||
|
||||
from pokedex.db import markdown
|
||||
from pokedex.db import markdown, multilang
|
||||
|
||||
# A list of all table classes will live in table_classes
|
||||
table_classes = []
|
||||
|
@ -113,6 +113,30 @@ class TextColumn(LanguageSpecificColumn):
|
|||
"""A column that will appear in the corresponding _text table"""
|
||||
|
||||
|
||||
### Need Language first, to create the partial() below
|
||||
|
||||
class Language(TableBase):
|
||||
u"""A language the Pokémon games have been transleted into
|
||||
"""
|
||||
__tablename__ = 'languages'
|
||||
__singlename__ = 'language'
|
||||
id = Column(Integer, primary_key=True, nullable=False,
|
||||
info=dict(description="A numeric ID"))
|
||||
iso639 = Column(Unicode(2), nullable=False,
|
||||
info=dict(description="The two-letter code of the country where this language is spoken. Note that it is not unique.", format='identifier'))
|
||||
iso3166 = Column(Unicode(2), nullable=False,
|
||||
info=dict(description="The two-letter code of the language. Note that it is not unique.", format='identifier'))
|
||||
identifier = Column(Unicode(16), nullable=False,
|
||||
info=dict(description="An identifier", format='identifier'))
|
||||
official = Column(Boolean, nullable=False, index=True,
|
||||
info=dict(description=u"True iff games are produced in the language."))
|
||||
order = Column(Integer, nullable=True,
|
||||
info=dict(description=u"Order for sorting in foreign name lists."))
|
||||
name = TextColumn(Unicode(16), nullable=False, index=True, plural='names',
|
||||
info=dict(description="The name", format='plaintext', official=True))
|
||||
|
||||
create_translation_table = partial(multilang.create_translation_table, language_class=Language)
|
||||
|
||||
### The actual tables
|
||||
|
||||
class Ability(TableBase):
|
||||
|
@ -126,12 +150,17 @@ class Ability(TableBase):
|
|||
info=dict(description="An identifier", format='identifier'))
|
||||
generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
|
||||
info=dict(description="The ID of the generation this ability was introduced in", detail=True))
|
||||
effect = ProseColumn(markdown.MarkdownColumn(5120), plural='effects', nullable=False,
|
||||
info=dict(description="A detailed description of this ability's effect", format='markdown'))
|
||||
short_effect = ProseColumn(markdown.MarkdownColumn(255), plural='short_effects', nullable=False,
|
||||
info=dict(description="A short summary of this ability's effect", format='markdown'))
|
||||
name = TextColumn(Unicode(24), nullable=False, index=True, plural='names',
|
||||
info=dict(description="The name", format='plaintext', official=True))
|
||||
|
||||
create_translation_table('ability_texts', Ability, 'names',
|
||||
name = Column(Unicode(24), nullable=False, index=True,
|
||||
info=dict(description="The name", format='plaintext', official=True)),
|
||||
)
|
||||
create_translation_table('ability_prose', Ability, 'prose',
|
||||
effect = Column(markdown.MarkdownColumn(5120), nullable=False,
|
||||
info=dict(description="A detailed description of this ability's effect", format='markdown')),
|
||||
short_effect = Column(markdown.MarkdownColumn(255), nullable=False,
|
||||
info=dict(description="A short summary of this ability's effect", format='markdown')),
|
||||
)
|
||||
|
||||
class AbilityChangelog(TableBase):
|
||||
"""History of changes to abilities across main game versions."""
|
||||
|
@ -550,26 +579,6 @@ class ItemPocket(TableBase):
|
|||
name = TextColumn(Unicode(16), nullable=False, index=True, plural='names',
|
||||
info=dict(description="The name", format='plaintext', official=True))
|
||||
|
||||
class Language(TableBase):
|
||||
u"""A language the Pokémon games have been transleted into
|
||||
"""
|
||||
__tablename__ = 'languages'
|
||||
__singlename__ = 'language'
|
||||
id = Column(Integer, primary_key=True, nullable=False,
|
||||
info=dict(description="A numeric ID"))
|
||||
iso639 = Column(Unicode(2), nullable=False,
|
||||
info=dict(description="The two-letter code of the country where this language is spoken. Note that it is not unique.", format='identifier'))
|
||||
iso3166 = Column(Unicode(2), nullable=False,
|
||||
info=dict(description="The two-letter code of the language. Note that it is not unique.", format='identifier'))
|
||||
identifier = Column(Unicode(16), nullable=False,
|
||||
info=dict(description="An identifier", format='identifier'))
|
||||
official = Column(Boolean, nullable=False, index=True,
|
||||
info=dict(description=u"True iff games are produced in the language."))
|
||||
order = Column(Integer, nullable=True,
|
||||
info=dict(description=u"Order for sorting in foreign name lists."))
|
||||
name = TextColumn(Unicode(16), nullable=False, index=True, plural='names',
|
||||
info=dict(description="The name", format='plaintext', official=True))
|
||||
|
||||
class Location(TableBase):
|
||||
u"""A place in the Pokémon world
|
||||
"""
|
||||
|
@ -869,8 +878,12 @@ class Move(TableBase):
|
|||
info=dict(description="ID of the move's Contest effect"))
|
||||
super_contest_effect_id = Column(Integer, ForeignKey('super_contest_effects.id'), nullable=True,
|
||||
info=dict(description="ID of the move's Super Contest effect"))
|
||||
name = TextColumn(Unicode(24), nullable=False, index=True, plural='names',
|
||||
|
||||
create_translation_table('move_texts', Move, 'names',
|
||||
name = Column(Unicode(24), nullable=False, index=True,
|
||||
info=dict(description="The name", format='plaintext', official=True))
|
||||
)
|
||||
|
||||
|
||||
class MoveChangelog(TableBase):
|
||||
"""History of changes to moves across main game versions."""
|
||||
|
@ -992,9 +1005,6 @@ class Pokemon(TableBase):
|
|||
info=dict(description=u"The height of the Pokémon, in decimeters (tenths of a meter)"))
|
||||
weight = Column(Integer, nullable=False,
|
||||
info=dict(description=u"The weight of the Pokémon, in tenths of a kilogram (decigrams)"))
|
||||
species = TextColumn(Unicode(16), nullable=False, plural='species_names',
|
||||
info=dict(description=u'The short flavor text, such as "Seed" or "Lizard"; usually affixed with the word "Pokémon"',
|
||||
official=True, format='plaintext'))
|
||||
color_id = Column(Integer, ForeignKey('pokemon_colors.id'), nullable=False,
|
||||
info=dict(description=u"ID of this Pokémon's Pokédex color, as used for a gimmick search function in the games."))
|
||||
pokemon_shape_id = Column(Integer, ForeignKey('pokemon_shapes.id'), nullable=True,
|
||||
|
@ -1017,8 +1027,6 @@ class Pokemon(TableBase):
|
|||
info=dict(description=u"Set iff the species exhibits enough sexual dimorphism to have separate sets of sprites in Gen IV and beyond."))
|
||||
order = Column(Integer, nullable=False, index=True,
|
||||
info=dict(description=u"Order for sorting. Almost national order, except families and forms are grouped together."))
|
||||
name = TextColumn(Unicode(20), nullable=False, index=True, plural='names',
|
||||
info=dict(description="The name", format='plaintext', official=True))
|
||||
|
||||
### Stuff to handle alternate Pokémon forms
|
||||
|
||||
|
@ -1102,6 +1110,14 @@ class Pokemon(TableBase):
|
|||
else:
|
||||
return None
|
||||
|
||||
create_translation_table('pokemon_texts', Pokemon, 'names',
|
||||
name = Column(Unicode(20), nullable=False, index=True,
|
||||
info=dict(description="The name", format='plaintext', official=True)),
|
||||
species = Column(Unicode(16), nullable=False,
|
||||
info=dict(description=u'The short flavor text, such as "Seed" or "Lizard"; usually affixed with the word "Pokémon"',
|
||||
official=True, format='plaintext')),
|
||||
)
|
||||
|
||||
class PokemonAbility(TableBase):
|
||||
u"""Maps an ability to a Pokémon that can have it
|
||||
"""
|
||||
|
@ -1524,7 +1540,7 @@ Ability.generation = relation(Generation, backref='abilities')
|
|||
Ability.all_pokemon = relation(Pokemon,
|
||||
secondary=PokemonAbility.__table__,
|
||||
order_by=Pokemon.order,
|
||||
back_populates='all_abilities',
|
||||
#back_populates='all_abilities',
|
||||
)
|
||||
Ability.pokemon = relation(Pokemon,
|
||||
secondary=PokemonAbility.__table__,
|
||||
|
@ -1533,7 +1549,7 @@ Ability.pokemon = relation(Pokemon,
|
|||
PokemonAbility.is_dream == False
|
||||
),
|
||||
order_by=Pokemon.order,
|
||||
back_populates='abilities',
|
||||
#back_populates='abilities',
|
||||
)
|
||||
Ability.dream_pokemon = relation(Pokemon,
|
||||
secondary=PokemonAbility.__table__,
|
||||
|
@ -1542,7 +1558,7 @@ Ability.dream_pokemon = relation(Pokemon,
|
|||
PokemonAbility.is_dream == True
|
||||
),
|
||||
order_by=Pokemon.order,
|
||||
back_populates='dream_ability',
|
||||
#back_populates='dream_ability',
|
||||
)
|
||||
|
||||
AbilityChangelog.changed_in = relation(VersionGroup, backref='ability_changelog')
|
||||
|
@ -1578,7 +1594,7 @@ EncounterSlot.version_group = relation(VersionGroup)
|
|||
|
||||
EvolutionChain.growth_rate = relation(GrowthRate, backref='evolution_chains')
|
||||
EvolutionChain.baby_trigger_item = relation(Item, backref='evolution_chains')
|
||||
EvolutionChain.pokemon = relation(Pokemon, order_by=Pokemon.order, back_populates='evolution_chain')
|
||||
EvolutionChain.pokemon = relation(Pokemon, order_by=Pokemon.order)#, back_populates='evolution_chain')
|
||||
|
||||
Experience.growth_rate = relation(GrowthRate, backref='experience_table')
|
||||
|
||||
|
@ -1638,7 +1654,7 @@ Move.super_contest_effect = relation(SuperContestEffect, backref='moves')
|
|||
Move.super_contest_combo_next = association_proxy('super_contest_combo_first', 'second')
|
||||
Move.super_contest_combo_prev = association_proxy('super_contest_combo_second', 'first')
|
||||
Move.target = relation(MoveTarget, backref='moves')
|
||||
Move.type = relation(Type, back_populates='moves')
|
||||
Move.type = relation(Type)#, back_populates='moves')
|
||||
|
||||
MoveChangelog.changed_in = relation(VersionGroup, backref='move_changelog')
|
||||
MoveChangelog.move_effect = relation(MoveEffect, backref='move_changelog')
|
||||
|
@ -1681,7 +1697,7 @@ NatureBattleStylePreference.battle_style = relation(MoveBattleStyle, backref='na
|
|||
NaturePokeathlonStat.pokeathlon_stat = relation(PokeathlonStat, backref='nature_effects')
|
||||
|
||||
Pokedex.region = relation(Region, backref='pokedexes')
|
||||
Pokedex.version_groups = relation(VersionGroup, order_by=VersionGroup.id, back_populates='pokedex')
|
||||
Pokedex.version_groups = relation(VersionGroup, order_by=VersionGroup.id)#, back_populates='pokedex')
|
||||
|
||||
Pokemon.all_abilities = relation(Ability,
|
||||
secondary=PokemonAbility.__table__,
|
||||
|
@ -1709,7 +1725,7 @@ Pokemon.dex_numbers = relation(PokemonDexNumber, order_by=PokemonDexNumber.poked
|
|||
Pokemon.egg_groups = relation(EggGroup, secondary=PokemonEggGroup.__table__,
|
||||
order_by=PokemonEggGroup.egg_group_id,
|
||||
backref=backref('pokemon', order_by=Pokemon.order))
|
||||
Pokemon.evolution_chain = relation(EvolutionChain, back_populates='pokemon')
|
||||
Pokemon.evolution_chain = relation(EvolutionChain)#, back_populates='pokemon')
|
||||
Pokemon.child_pokemon = relation(Pokemon,
|
||||
primaryjoin=Pokemon.id==PokemonEvolution.from_pokemon_id,
|
||||
secondary=PokemonEvolution.__table__,
|
||||
|
@ -1731,7 +1747,7 @@ Pokemon.shape = relation(PokemonShape, backref='pokemon')
|
|||
Pokemon.stats = relation(PokemonStat, backref='pokemon', order_by=PokemonStat.stat_id.asc())
|
||||
Pokemon.types = relation(Type, secondary=PokemonType.__table__,
|
||||
order_by=PokemonType.slot.asc(),
|
||||
back_populates='pokemon')
|
||||
)#back_populates='pokemon')
|
||||
|
||||
PokemonDexNumber.pokedex = relation(Pokedex)
|
||||
|
||||
|
@ -1829,7 +1845,7 @@ Type.generation = relation(Generation, backref='types')
|
|||
Type.damage_class = relation(MoveDamageClass, backref='types')
|
||||
Type.pokemon = relation(Pokemon, secondary=PokemonType.__table__,
|
||||
order_by=Pokemon.order,
|
||||
back_populates='types')
|
||||
)#back_populates='types')
|
||||
Type.moves = relation(Move, back_populates='type', order_by=Move.id)
|
||||
|
||||
Version.version_group = relation(VersionGroup, back_populates='versions')
|
||||
|
@ -1846,149 +1862,6 @@ VersionGroup.pokedex = relation(Pokedex, back_populates='version_groups')
|
|||
|
||||
default_lang = u'en'
|
||||
|
||||
def create_translation_table(_table_name, foreign_class,
|
||||
_language_class=Language, **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.
|
||||
TODO give it a __table__ or __init__?
|
||||
|
||||
`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.
|
||||
|
||||
Usage looks like this:
|
||||
|
||||
class Foo(Base): ...
|
||||
|
||||
create_translation_table('foo_bars', Foo,
|
||||
name = Column(...),
|
||||
)
|
||||
|
||||
# Now you can do the following:
|
||||
foo.name
|
||||
foo.name_map['en']
|
||||
foo.foo_bars['en']
|
||||
|
||||
foo.name_map['en'] = "new name"
|
||||
del foo.name_map['en']
|
||||
|
||||
q.options(joinedload(Foo.default_translation))
|
||||
q.options(joinedload(Foo.foo_bars))
|
||||
|
||||
In the above example, the following attributes are added to Foo:
|
||||
|
||||
- `foo_bars`, a relation to the new table. It uses a dict-based collection
|
||||
class, where the keys are language identifiers and the values are rows in
|
||||
the created tables.
|
||||
- `foo_bars_local`, a relation to the row in the new table that matches the
|
||||
current default language.
|
||||
|
||||
Note that these are distinct relations. Even though the former necessarily
|
||||
includes the latter, SQLAlchemy doesn't treat them as linked; loading one
|
||||
will not load the other. Modifying both within the same transaction has
|
||||
undefined behavior.
|
||||
|
||||
For each column provided, the following additional attributes are added to
|
||||
Foo:
|
||||
|
||||
- `(column)_map`, an association proxy onto `foo_bars`.
|
||||
- `(column)`, an association proxy onto `foo_bars_local`.
|
||||
|
||||
Pardon the naming disparity, but the grammar suffers otherwise.
|
||||
|
||||
Modifying these directly is not likely to be a good idea.
|
||||
"""
|
||||
# n.b.: _language_class only exists for the sake of tests, which sometimes
|
||||
# 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'),
|
||||
})
|
||||
|
||||
# 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),
|
||||
primary_key=True, nullable=False),
|
||||
)
|
||||
|
||||
# Add ye columns
|
||||
# Column objects have a _creation_order attribute in ascending order; use
|
||||
# this to get the (unordered) kwargs sorted correctly
|
||||
kwitems = kwargs.items()
|
||||
kwitems.sort(key=lambda kv: kv[1]._creation_order)
|
||||
for name, column in kwitems:
|
||||
column.name = name
|
||||
table.append_column(column)
|
||||
|
||||
# Construct ye mapper
|
||||
mapper(Translations, table, properties={
|
||||
# TODO change to foreign_id
|
||||
'object_id': synonym(foreign_key_name),
|
||||
# TODO change this as appropriate
|
||||
'language': relation(_language_class,
|
||||
primaryjoin=table.c.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
|
||||
# Class.foo_bars
|
||||
setattr(foreign_class, _table_name, relation(Translations,
|
||||
primaryjoin=foreign_class.id == Translations.object_id,
|
||||
collection_class=attribute_mapped_collection('language'),
|
||||
# TODO
|
||||
lazy='select',
|
||||
))
|
||||
# Class.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.
|
||||
local_relation_name = _table_name + '_local'
|
||||
setattr(foreign_class, local_relation_name, relation(Translations,
|
||||
primaryjoin=and_(
|
||||
foreign_class.id == Translations.object_id,
|
||||
Translations._language_identifier ==
|
||||
bindparam('_default_language', required=True),
|
||||
),
|
||||
uselist=False,
|
||||
# TODO MORESO HERE
|
||||
lazy='select',
|
||||
))
|
||||
|
||||
# Add per-column proxies to the original class
|
||||
for name, column in kwitems:
|
||||
# Class.(column) -- accessor for the default language's value
|
||||
setattr(foreign_class, name,
|
||||
association_proxy(local_relation_name, name))
|
||||
|
||||
# Class.(column)_map -- accessor for the language dict
|
||||
# Need a custom creator since Translations doesn't have an init, and
|
||||
# these are passed as *args anyway
|
||||
def creator(language, value):
|
||||
row = Translations()
|
||||
row.language = language
|
||||
setattr(row, name, value)
|
||||
return row
|
||||
setattr(foreign_class, name + '_map',
|
||||
association_proxy(_table_name, name, creator=creator))
|
||||
|
||||
# Done
|
||||
return Translations
|
||||
|
||||
def makeTextTable(foreign_table_class, table_suffix_plural, table_suffix_singular, columns, lazy, Language=Language):
|
||||
# With "Language", we'd have two language_id. So, rename one to 'lang'
|
||||
foreign_key_name = foreign_table_class.__singlename__
|
||||
|
|
|
@ -8,7 +8,6 @@ from sqlalchemy.ext.declarative import declarative_base
|
|||
|
||||
from pokedex.db import tables, markdown
|
||||
from pokedex.db.multilang import create_translation_table
|
||||
from pokedex.db.tables import create_translation_table
|
||||
|
||||
def test_variable_names():
|
||||
"""We want pokedex.db.tables to export tables using the class name"""
|
||||
|
@ -49,21 +48,20 @@ def test_i18n_table_creation():
|
|||
id = Column(Integer, primary_key=True, nullable=False)
|
||||
|
||||
FooText = create_translation_table('foo_text', Foo,
|
||||
_language_class=Language,
|
||||
language_class=Language,
|
||||
name = Column(String(100)),
|
||||
)
|
||||
|
||||
# TODO move this to the real code
|
||||
class DurpSession(Session):
|
||||
class FauxSession(Session):
|
||||
def execute(self, clause, params=None, *args, **kwargs):
|
||||
if not params:
|
||||
params = {}
|
||||
params.setdefault('_default_language', 'en')
|
||||
return super(DurpSession, self).execute(clause, params, *args, **kwargs)
|
||||
return super(FauxSession, self).execute(clause, params, *args, **kwargs)
|
||||
|
||||
# OK, create all the tables and gimme a session
|
||||
Base.metadata.create_all()
|
||||
sess = sessionmaker(engine, class_=DurpSession)()
|
||||
sess = sessionmaker(engine, class_=FauxSession)()
|
||||
|
||||
# Create some languages and foos to bind together
|
||||
lang_en = Language(identifier='en')
|
||||
|
|
Loading…
Reference in a new issue