From 105d9422ca8151aff88465d8ba62e64116cbe32d Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Sun, 3 Apr 2011 19:34:18 +0300
Subject: [PATCH 1/7] Change item short effect format to markdown

---
 pokedex/db/tables.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py
index d9905b6..727582c 100644
--- a/pokedex/db/tables.py
+++ b/pokedex/db/tables.py
@@ -501,7 +501,7 @@ create_translation_table('item_names', Item, 'names',
 )
 create_translation_table('item_prose', Item, 'prose',
     short_effect = Column(markdown.MarkdownColumn(256), nullable=False,
-        info=dict(description="A short summary of the effect", format='plaintext')),
+        info=dict(description="A short summary of the effect", format='markdown')),
     effect = Column(markdown.MarkdownColumn(5120), nullable=False,
         info=dict(description=u"Detailed description of the item's effect.", format='markdown')),
 )

From 6f369073412743e00d835591c2a5ebed40c1c6ce Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Sun, 3 Apr 2011 19:42:46 +0300
Subject: [PATCH 2/7] Enable nullable MarkdownColumns

---
 pokedex/db/markdown.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/pokedex/db/markdown.py b/pokedex/db/markdown.py
index 616830a..257c8de 100644
--- a/pokedex/db/markdown.py
+++ b/pokedex/db/markdown.py
@@ -106,6 +106,9 @@ class MarkdownColumn(sqlalchemy.types.TypeDecorator):
     impl = sqlalchemy.types.Unicode
 
     def process_bind_param(self, value, dialect):
+        if value is None:
+            return None
+
         if not isinstance(value, basestring):
             # Can't assign, e.g., MarkdownString objects yet
             raise NotImplementedError
@@ -113,4 +116,7 @@ class MarkdownColumn(sqlalchemy.types.TypeDecorator):
         return unicode(value)
 
     def process_result_value(self, value, dialect):
+        if value is None:
+            return None
+
         return MarkdownString(value)

From a06498cb39127eecedb0dc6e7ffb7601935e764c Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Sun, 3 Apr 2011 19:34:44 +0300
Subject: [PATCH 3/7] Make a bunch of text columns nullable to support missing
 translations

---
 pokedex/db/tables.py         | 56 ++++++++++++++++++------------------
 pokedex/tests/test_schema.py |  7 +++++
 2 files changed, 35 insertions(+), 28 deletions(-)

diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py
index 727582c..5670da9 100644
--- a/pokedex/db/tables.py
+++ b/pokedex/db/tables.py
@@ -121,9 +121,9 @@ create_translation_table('ability_names', Ability, 'names',
         info=dict(description="The name", format='plaintext', official=True, ripped=True)),
 )
 create_translation_table('ability_prose', Ability, 'prose',
-    effect = Column(markdown.MarkdownColumn(5120), nullable=False,
+    effect = Column(markdown.MarkdownColumn(5120), nullable=True,
         info=dict(description="A detailed description of this ability's effect", format='markdown')),
-    short_effect = Column(markdown.MarkdownColumn(255), nullable=False,
+    short_effect = Column(markdown.MarkdownColumn(255), nullable=True,
         info=dict(description="A short summary of this ability's effect", format='markdown')),
 )
 
@@ -232,9 +232,9 @@ class ContestEffect(TableBase):
         info=dict(description="The base number of hearts the user's opponent loses"))
 
 create_translation_table('contest_effect_prose', ContestEffect, 'prose',
-    flavor_text = Column(Unicode(64), nullable=False,
+    flavor_text = Column(Unicode(64), nullable=True,
         info=dict(description="The in-game description of this effect", official=True, format='gametext')),
-    effect = Column(Unicode(255), nullable=False,
+    effect = Column(Unicode(255), nullable=True,
         info=dict(description="A detailed description of the effect", format='plaintext')),
 )
 
@@ -250,11 +250,11 @@ class ContestType(TableBase):
 
 create_translation_table('contest_type_names', ContestType, 'names',
     relation_lazy='joined',
-    name = Column(Unicode(6), nullable=False, index=True,
+    name = Column(Unicode(6), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=True)),
-    flavor = Column(Unicode(6), nullable=False,
+    flavor = Column(Unicode(6), nullable=True,
         info=dict(description="The name of the corresponding Berry flavor", official=True, format='plaintext')),
-    color = Column(Unicode(6), nullable=False,
+    color = Column(Unicode(6), nullable=True,
         info=dict(description=u"The name of the corresponding Pokéblock color", official=True, format='plaintext')),
 )
 
@@ -500,9 +500,9 @@ create_translation_table('item_names', Item, 'names',
         info=dict(description="The name", format='plaintext', official=True, ripped=True)),
 )
 create_translation_table('item_prose', Item, 'prose',
-    short_effect = Column(markdown.MarkdownColumn(256), nullable=False,
+    short_effect = Column(markdown.MarkdownColumn(256), nullable=True,
         info=dict(description="A short summary of the effect", format='markdown')),
-    effect = Column(markdown.MarkdownColumn(5120), nullable=False,
+    effect = Column(markdown.MarkdownColumn(5120), nullable=True,
         info=dict(description=u"Detailed description of the item's effect.", format='markdown')),
 )
 create_translation_table('item_flavor_summaries', Item, 'flavor_summaries',
@@ -540,9 +540,9 @@ class ItemFlag(TableBase):
         info=dict(description="Identifier of the flag", format='identifier'))
 
 create_translation_table('item_flag_prose', ItemFlag, 'prose',
-    name = Column(Unicode(24), nullable=False, index=True,
+    name = Column(Unicode(24), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=False)),
-    description = Column(Unicode(64), nullable=False,
+    description = Column(Unicode(64), nullable=True,
         info=dict(description="Short description of the flag", format='plaintext')),
 )
 
@@ -784,9 +784,9 @@ class MoveDamageClass(TableBase):
 
 create_translation_table('move_damage_class_prose', MoveDamageClass, 'prose',
     relation_lazy='joined',
-    name = Column(Unicode(16), nullable=False, index=True,
+    name = Column(Unicode(16), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=False)),
-    description = Column(Unicode(64), nullable=False,
+    description = Column(Unicode(64), nullable=True,
         info=dict(description="A description of the class", format='plaintext')),
 )
 
@@ -799,9 +799,9 @@ class MoveEffect(TableBase):
         info=dict(description="A numeric ID"))
 
 create_translation_table('move_effect_prose', MoveEffect, 'prose',
-    short_effect = Column(Unicode(256), nullable=False,
+    short_effect = Column(Unicode(256), nullable=True,
         info=dict(description="A short summary of the effect", format='plaintext')),
-    effect = Column(Unicode(5120), nullable=False,
+    effect = Column(Unicode(5120), nullable=True,
         info=dict(description="A detailed description of the effect", format='plaintext')),
 )
 
@@ -877,9 +877,9 @@ class MoveFlagType(TableBase):
 
 create_translation_table('move_flag_type_prose', MoveFlagType, 'prose',
     relation_lazy='joined',
-    name = Column(Unicode(32), nullable=False, index=True,
+    name = Column(Unicode(32), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=False)),
-    description = Column(markdown.MarkdownColumn(128), nullable=False,
+    description = Column(markdown.MarkdownColumn(128), nullable=True,
         info=dict(description="A short description of the flag", format='markdown')),
 )
 
@@ -979,9 +979,9 @@ class MoveTarget(TableBase):
 
 create_translation_table('move_target_prose', MoveTarget, 'prose',
     relation_lazy='joined',
-    name = Column(Unicode(32), nullable=False, index=True,
+    name = Column(Unicode(32), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=False)),
-    description = Column(Unicode(128), nullable=False,
+    description = Column(Unicode(128), nullable=True,
         info=dict(description="A description", format='plaintext')),
 )
 
@@ -1072,9 +1072,9 @@ class Pokedex(TableBase):
 
 create_translation_table('pokedex_prose', Pokedex, 'prose',
     relation_lazy='joined',
-    name = Column(Unicode(16), nullable=False, index=True,
+    name = Column(Unicode(16), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=False)),
-    description = Column(Unicode(512), nullable=False,
+    description = Column(Unicode(512), nullable=True,
         info=dict(description=u"A longer description of the Pokédex", format='plaintext')),
 )
 
@@ -1202,9 +1202,9 @@ class Pokemon(TableBase):
 
 create_translation_table('pokemon_names', Pokemon, 'names',
     relation_lazy='joined',
-    name = Column(Unicode(20), nullable=False, index=True,
+    name = Column(Unicode(20), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=True, ripped=True)),
-    species = Column(Unicode(16), nullable=False,
+    species = Column(Unicode(16), nullable=True,
         info=dict(description=u'The short flavor text, such as "Seed" or "Lizard"; usually affixed with the word "Pokémon"',
         official=True, format='plaintext')),
 )
@@ -1389,7 +1389,7 @@ PokemonFormGroup.id = PokemonFormGroup.pokemon_id
 create_translation_table('pokemon_form_group_prose', PokemonFormGroup, 'prose',
     term = Column(Unicode(16), nullable=True,
         info=dict(description=u"The term for this Pokémon's forms, e.g. \"Cloak\" for Burmy or \"Forme\" for Deoxys.", official=True, format='plaintext')),
-    description = Column(markdown.MarkdownColumn(1024), nullable=False,
+    description = Column(markdown.MarkdownColumn(1024), nullable=True,
         info=dict(description=u"Description of how the forms work", format='markdown')),
 )
 
@@ -1481,9 +1481,9 @@ class PokemonMoveMethod(TableBase):
 
 create_translation_table('pokemon_move_method_prose', PokemonMoveMethod, 'prose',
     relation_lazy='joined',
-    name = Column(Unicode(64), nullable=False, index=True,
+    name = Column(Unicode(64), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=False)),
-    description = Column(Unicode(255), nullable=False,
+    description = Column(Unicode(255), nullable=True,
         info=dict(description=u"A detailed description of how the method works", format='plaintext')),
 )
 
@@ -1499,9 +1499,9 @@ class PokemonShape(TableBase):
 
 create_translation_table('pokemon_shape_prose', PokemonShape, 'prose',
     relation_lazy='joined',
-    name = Column(Unicode(24), nullable=False, index=True,
+    name = Column(Unicode(24), nullable=True, index=True,
         info=dict(description="The name", format='plaintext', official=False)),
-    awesome_name = Column(Unicode(16), nullable=False,
+    awesome_name = Column(Unicode(16), nullable=True,
         info=dict(description=u"A splendiferous name of the body shape", format='plaintext')),
 )
 
diff --git a/pokedex/tests/test_schema.py b/pokedex/tests/test_schema.py
index 83f9de7..2ad795c 100644
--- a/pokedex/tests/test_schema.py
+++ b/pokedex/tests/test_schema.py
@@ -171,6 +171,7 @@ def test_texts():
             good_formats = 'identifier latex'.split()
             assert_text = '%s is not language-specific'
         columns = sorted(cls.__table__.c, key=lambda c: c.name)
+        text_columns = []
         for column in columns:
             format = column.info.get('format', None)
             if format is not None:
@@ -183,6 +184,7 @@ def test_texts():
                     raise AssertionError('%s: identifier column name/type mismatch' % column)
                 if column.info.get('official', None) and format not in 'gametext plaintext':
                     raise AssertionError('%s: official text with bad format' % column)
+                text_columns.append(column)
             else:
                 if isinstance(column.type, (markdown.MarkdownColumn, tables.Unicode)):
                     raise AssertionError('%s: text column without format' % column)
@@ -190,6 +192,11 @@ def test_texts():
                 raise AssertionError('%s: non-plaintext name' % column)
             # No mention of English in the description
             assert 'English' not in column.info['description'], column
+        # If there's more than one text column in a translation table,
+        # they have to be nullable, to support missing translations
+        if hasattr(cls, 'local_language') and len(text_columns) > 1:
+            for column in text_columns:
+                assert column.nullable
 
 def test_identifiers_with_names():
     """Test that named tables have identifiers, and non-named tables don't

From 8cddfefc4cbb7490fd501d606e74ec40e59114fc Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Sun, 3 Apr 2011 21:00:58 +0300
Subject: [PATCH 4/7] Add __repr__ to mapped classes

This makes string representations of lists, tuples, dicts, etc. with the
DB objects make sense
---
 pokedex/db/tables.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py
index 5670da9..55cd487 100644
--- a/pokedex/db/tables.py
+++ b/pokedex/db/tables.py
@@ -62,6 +62,9 @@ class TableSuperclass(object):
     def __str__(self):
         return unicode(self).encode('utf8')
 
+    def __repr__(self):
+        return unicode(self).encode('utf8')
+
 mapped_classes = []
 class TableMetaclass(DeclarativeMeta):
     def __init__(cls, name, bases, attrs):

From 9fdb8e1bd09afa4130303354c0460bf606160f8c Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Sun, 3 Apr 2011 22:05:56 +0300
Subject: [PATCH 5/7] Make session.default_language set/get work

---
 pokedex/db/multilang.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py
index 4adb68d..b5f87e4 100644
--- a/pokedex/db/multilang.py
+++ b/pokedex/db/multilang.py
@@ -159,12 +159,15 @@ class MultilangSession(Session):
 
     @property
     def default_language(self):
-        # XXX need to get the right mapped class for this to work
-        raise NotImplementedError
+        # Need to import tables here to avoid a circular dependency
+        from pokedex.db import tables
+        query = self.query(tables.Language)
+        query = query.filter_by(id=self._default_language_id)
+        return query.one()
 
     @default_language.setter
     def default_language(self, new):
-        self._default_language_id = new#.id
+        self._default_language_id = new.id
 
     @default_language.deleter
     def default_language(self):

From 4291c33c007b0158458344f507f807633b90082e Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Mon, 4 Apr 2011 21:51:12 +0300
Subject: [PATCH 6/7] Make MultilangSession's language class configurable

---
 pokedex/db/multilang.py      | 12 +++++++++++-
 pokedex/tests/test_schema.py |  4 ++--
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py
index b5f87e4..6690b8a 100644
--- a/pokedex/db/multilang.py
+++ b/pokedex/db/multilang.py
@@ -157,11 +157,21 @@ class MultilangSession(Session):
     """A tiny Session subclass that adds support for a default language."""
     _default_language_id = 9  # English.  XXX magic constant
 
+    def __init__(self, *args, **kwargs):
+        try:
+            self.language_class = kwargs.pop('language_class')
+        except KeyError:
+            # Set the default language_class
+            # We need to import here, to prevent a circular depencency
+            from pokedex.db.tables import Language
+            self.language_class = Language
+        super(MultilangSession, self).__init__(*args, **kwargs)
+
     @property
     def default_language(self):
         # Need to import tables here to avoid a circular dependency
         from pokedex.db import tables
-        query = self.query(tables.Language)
+        query = self.query(self.language_class)
         query = query.filter_by(id=self._default_language_id)
         return query.one()
 
diff --git a/pokedex/tests/test_schema.py b/pokedex/tests/test_schema.py
index 2ad795c..bd42502 100644
--- a/pokedex/tests/test_schema.py
+++ b/pokedex/tests/test_schema.py
@@ -66,7 +66,7 @@ def test_i18n_table_creation():
 
     # OK, create all the tables and gimme a session
     Base.metadata.create_all()
-    sm = sessionmaker(class_=MultilangSession)
+    sm = sessionmaker(class_=MultilangSession, language_class=Language)
     sess = MultilangScopedSession(sm)
 
     # Create some languages and foos to bind together
@@ -82,7 +82,7 @@ def test_i18n_table_creation():
 
     # Commit so the above get primary keys filled in
     sess.commit()
-    sess.default_language = lang_en.id
+    sess.default_language = lang_en
 
     # Give our foo some names, as directly as possible
     foo_text = FooText()

From 9441ffb165298af87c930543cc1f43ee2a40230b Mon Sep 17 00:00:00 2001
From: Eevee <git@veekun.com>
Date: Tue, 5 Apr 2011 21:03:41 -0700
Subject: [PATCH 7/7] Remove the responsibility of setting a default language
 from multilang.

Caller now has to do it.  No need to avoid circular deps, no need to do
much of anything at all.
---
 pokedex/db/__init__.py  | 12 +++++++++---
 pokedex/db/multilang.py | 24 ++++++++++--------------
 2 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/pokedex/db/__init__.py b/pokedex/db/__init__.py
index e2790da..ea103e7 100644
--- a/pokedex/db/__init__.py
+++ b/pokedex/db/__init__.py
@@ -1,7 +1,7 @@
-from sqlalchemy import MetaData, Table, engine_from_config, orm
+from sqlalchemy import engine_from_config, orm
 
 from ..defaults import get_default_db_uri
-from .tables import metadata
+from .tables import Language, metadata
 from .multilang import MultilangSession, MultilangScopedSession
 
 
@@ -41,7 +41,13 @@ 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(class_=MultilangSession, **all_session_args)
+    sm = orm.sessionmaker(class_=MultilangSession, language_class=Language,
+        **all_session_args)
     session = MultilangScopedSession(sm)
 
+    # Default to English
+    session.default_language = session.query(Language) \
+        .filter_by(identifier=u'en') \
+        .one()
+
     return session
diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py
index 6690b8a..bd4266f 100644
--- a/pokedex/db/multilang.py
+++ b/pokedex/db/multilang.py
@@ -154,26 +154,22 @@ def create_translation_table(_table_name, foreign_class, relation_name,
     return Translations
 
 class MultilangSession(Session):
-    """A tiny Session subclass that adds support for a default language."""
-    _default_language_id = 9  # English.  XXX magic constant
+    """A tiny Session subclass that adds support for a default language.
+
+    Caller will need to assign something to `default_language` before this will
+    actually work.
+    """
+    _default_language_id = 0  # Better fill this in, caller
 
     def __init__(self, *args, **kwargs):
-        try:
-            self.language_class = kwargs.pop('language_class')
-        except KeyError:
-            # Set the default language_class
-            # We need to import here, to prevent a circular depencency
-            from pokedex.db.tables import Language
-            self.language_class = Language
+        self.language_class = kwargs.pop('language_class')
         super(MultilangSession, self).__init__(*args, **kwargs)
 
     @property
     def default_language(self):
-        # Need to import tables here to avoid a circular dependency
-        from pokedex.db import tables
-        query = self.query(self.language_class)
-        query = query.filter_by(id=self._default_language_id)
-        return query.one()
+        return self.query(self.language_class) \
+            .filter_by(id=self._default_language_id) \
+            .one()
 
     @default_language.setter
     def default_language(self, new):