- identifier: A fan-made identifier in the [-_a-z0-9]* format. Not intended
for translation.
- latex: A formula in LaTeX syntax.
+
+A localizable text column is visible as two properties:
+The plural-name property (e.g. Pokemon.names) is a language-to-name dictionary:
+ bulbasaur.names['en'] == "Bulbasaur" and bulbasaur.names['de'] == "Bisasam".
+ You can use Pokemon.names['en'] to filter a query.
+The singular-name property returns the name in the default language, English.
+ For example bulbasaur.name == "Bulbasaur"
+ Setting pokedex.db.tables.default_lang changes the default language.
"""
# XXX: Check if "gametext" is set correctly everywhere
-import operator
+import collections
-from sqlalchemy import Column, ForeignKey, MetaData, PrimaryKeyConstraint, Table
+from sqlalchemy import Column, ForeignKey, MetaData, PrimaryKeyConstraint, Table, UniqueConstraint
from sqlalchemy.ext.declarative import (
declarative_base, declared_attr, DeclarativeMeta,
)
from sqlalchemy.orm import (
backref, eagerload_all, relation, class_mapper, synonym, mapper,
)
-from sqlalchemy.orm.session import Session
+from sqlalchemy.orm.session import Session, object_session
from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.sql import and_
+from sqlalchemy.sql.expression import ColumnOperators
+from sqlalchemy.schema import ColumnDefault
from sqlalchemy.types import *
from inspect import isclass
__singlename__ = 'move_effect_changelog'
id = Column(Integer, primary_key=True, nullable=False,
info=dict(description="A numeric ID"))
- effect_id = Column(Integer, ForeignKey('move_effects.id'), primary_key=True, nullable=False,
+ effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=False,
info=dict(description="The ID of the effect that changed"))
- changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False,
+ changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False,
info=dict(description="The ID of the version group in which the effect changed"))
effect = ProseColumn(markdown.MarkdownColumn(512), plural='effects', nullable=False,
info=dict(description="A description of the old behavior", format='markdown'))
+ __table_args__ = (
+ UniqueConstraint(effect_id, changed_in_version_group_id),
+ {},
+ )
+
class MoveFlag(TableBase):
u"""Maps a move flag to a move
"""
info=dict(description=u"ID of the highest stat"))
gene_mod_5 = Column(Integer, nullable=False, index=True,
info=dict(description=u"Value of the highest stat modulo 5"))
- text = TextColumn(Unicode(24), plural='texts', nullable=False, index=True, unique=True,
+ message = TextColumn(Unicode(24), plural='messages', nullable=False, index=True, unique=True,
info=dict(description=u"The text displayed", official=True, format='plaintext'))
class SuperContestCombo(TableBase):
__singlename__ = 'type'
id = Column(Integer, primary_key=True, nullable=False,
info=dict(description=u"A unique ID for this type."))
- identifier = Column(Unicode(8), nullable=False,
+ identifier = Column(Unicode(12), nullable=False,
info=dict(description=u"An identifier", format='identifier'))
generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False,
info=dict(description=u"The ID of the generation this type first appeared in."))
Move.target = relation(MoveTarget, backref='moves')
Move.type = relation(Type, back_populates='moves')
-Move.effect = markdown.MoveEffectProperty('effect')
-Move.effects = markdown.MoveEffectsProperty('effect')
-Move.short_effect = markdown.MoveEffectProperty('short_effect')
-Move.short_effects = markdown.MoveEffectsProperty('short_effect')
-
MoveChangelog.changed_in = relation(VersionGroup, backref='move_changelog')
MoveChangelog.move_effect = relation(MoveEffect, backref='move_changelog')
MoveChangelog.type = relation(Type, backref='move_changelog')
-MoveChangelog.effect = markdown.MoveEffectProperty('effect')
-MoveChangelog.effects = markdown.MoveEffectsProperty('effect')
-MoveChangelog.short_effect = markdown.MoveEffectProperty('short_effect')
-MoveChangelog.short_effects = markdown.MoveEffectsProperty('short_effect')
-
MoveEffect.category_map = relation(MoveEffectCategoryMap)
MoveEffect.categories = association_proxy('category_map', 'category')
MoveEffect.changelog = relation(MoveEffectChangelog,
else:
continue
table.name = cls(Unicode(class_mapper(table).c.identifier.type.length),
- plural='names', nullable=False, info=info)
+ plural='names', index=True, nullable=False, info=info)
### Add text/prose tables
+default_lang = u'en'
+
def makeTextTable(object_table, name_plural, name_singular, columns, lazy):
# With "Language", we'd have two language_id. So, rename one to 'lang'
safe_name = object_table.__singlename__
class Strings(object):
__tablename__ = tablename
__singlename__ = singlename
+ _attrname = name_plural
+ _language_identifier = association_proxy('language', 'identifier')
for name, plural, column in columns:
column.name = name
+ if not column.nullable:
+ # A Python side default value, so that the strings can be set
+ # one by one without the DB complaining about missing values
+ column.default = ColumnDefault(u'')
table = Table(tablename, metadata,
Column(safe_name + '_id', Integer, ForeignKey(object_table.id),
primary_key=True, nullable=False),
Column('language_id', Integer, ForeignKey(Language.id),
- primary_key=True, nullable=False),
+ primary_key=True, index=True, nullable=False),
*(column for name, plural, column in columns)
)
))
Strings.object = getattr(Strings, safe_name)
- # Link the tables themselves, so we can get to them
+ # Link the tables themselves, so we can get them if needed
Strings.object_table = object_table
setattr(object_table, name_singular + '_table', Strings)
for colname, pluralname, column in columns:
- # Provide a relation with all the names, and an English accessor
+ # Provide a property with all the names, and an English accessor
# for backwards compatibility
- def scope(colname, pluralname, column):
- def get_strings(self):
- return dict(
- (l, getattr(t, colname))
- for l, t in getattr(self, name_plural).items()
- )
-
- def get_english_string(self):
- try:
- return get_strings(self)['en']
- except KeyError:
- raise AttributeError(colname)
-
- setattr(object_table, pluralname, property(get_strings))
- setattr(object_table, colname, property(get_english_string))
- scope(colname, pluralname, column)
+ setattr(object_table, pluralname, StringProperty(
+ object_table, Strings, colname,
+ ))
+ setattr(object_table, colname, DefaultLangProperty(pluralname))
if colname == 'name':
object_table.name_table = Strings
return Strings
+class StringProperty(object):
+ def __init__(self, cls, stringclass, colname):
+ self.cls = cls
+ self.colname = colname
+ self.stringclass = stringclass
+
+ def __get__(self, instance, cls):
+ if instance:
+ return StringMapping(instance, self)
+ else:
+ return self
+
+ def __getitem__(self, lang):
+ return StringExpression(self, lang)
+
+ def __str__(self):
+ return '<StringDict %s.%s>' % (self.cls, self.colname)
+
+class StringMapping(collections.MutableMapping):
+ def __init__(self, instance, prop):
+ self.stringclass = prop.stringclass
+ self.instance = instance
+ self.strings = getattr(instance, prop.stringclass._attrname)
+ self.colname = prop.colname
+
+ def __len__(self):
+ return len(self.strings)
+
+ def __iter__(self):
+ return iter(self.strings)
+
+ def __contains__(self, lang):
+ return lang in self.strings
+
+ def __getitem__(self, lang):
+ return getattr(self.strings[lang], self.colname)
+
+ def __setitem__(self, lang, value):
+ try:
+ # Modifying an existing row
+ row = self.strings[lang]
+ except KeyError:
+ # We need do add a whole row for the language
+ row = self.stringclass()
+ row.object_id = self.instance.id
+ session = object_session(self.instance)
+ if isinstance(lang, basestring):
+ lang = session.query(Language).filter_by(
+ identifier=lang).one()
+ row.language = lang
+ self.strings[lang] = row
+ session.add(row)
+ return setattr(row, self.colname, value)
+
+ def __delitem__(self, lang):
+ raise NotImplementedError('Cannot delete a single string. '
+ 'Perhaps you wan to delete all of %s.%s?' %
+ (self.instance, self.stringclass._attrname)
+ )
+
+class StringExpression(ColumnOperators):
+ def __init__(self, prop, lang):
+ self.prop = prop
+ self.column = getattr(prop.stringclass, prop.colname)
+ self.lang_column = prop.stringclass._language_identifier
+ if isinstance(lang, basestring):
+ self.lang = lang
+ else:
+ self.lang = lang.identifier
+
+ def operate(self, op, *values, **kwargs):
+ return getattr(self.prop.cls, self.prop.stringclass._attrname).any(and_(
+ self.lang_column == self.lang,
+ op(self.column, *values, **kwargs),
+ ))
+
+class DefaultLangProperty(object):
+ def __init__(self, colname):
+ self.colname = colname
+
+ def __get__(self, instance, cls):
+ if instance:
+ return getattr(instance, self.colname)[default_lang]
+ else:
+ return getattr(cls, self.colname)[default_lang]
+
+ def __set__(self, instance, value):
+ getattr(instance, self.colname)[default_lang] = value
+
+ def __delete__(self, instance):
+ del getattr(instance, self.colname)[default_lang]
+
for table in list(table_classes):
# Find all the language-specific columns, keeping them in the order they
# were defined
for table in list(table_classes):
if issubclass(table, LanguageSpecific):
table.language = relation(Language, primaryjoin=table.language_id == Language.id)
+
+Move.effect = DefaultLangProperty('effects')
+Move.effects = markdown.MoveEffectsProperty('effect')
+Move.short_effect = DefaultLangProperty('short_effects')
+Move.short_effects = markdown.MoveEffectsProperty('short_effect')
+
+MoveChangelog.effect = DefaultLangProperty('effects')
+MoveChangelog.effects = markdown.MoveEffectsProperty('effect')
+MoveChangelog.short_effect = DefaultLangProperty('short_effects')
+MoveChangelog.short_effects = markdown.MoveEffectsProperty('short_effect')