From 70b44584ae4a81e53d39481781c63aec23b23884 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Thu, 29 Dec 2011 11:15:55 -0600 Subject: [PATCH 01/67] Big ol' start of the SQL migrations system. Things definitely don't work yet, but should be heading in the right direction. --- mediagoblin/db/sql/migrations.py | 17 +++++ mediagoblin/db/sql/models.py | 21 +++++++ mediagoblin/db/sql/util.py | 59 +++++++++++++++++ mediagoblin/gmg_commands/dbupdate.py | 84 +++++++++++++++++++++++++ mediagoblin/media_types/image/models.py | 17 +++++ mediagoblin/media_types/video/models.py | 16 +++++ 6 files changed, 214 insertions(+) create mode 100644 mediagoblin/db/sql/migrations.py create mode 100644 mediagoblin/db/sql/util.py create mode 100644 mediagoblin/gmg_commands/dbupdate.py create mode 100644 mediagoblin/media_types/image/models.py create mode 100644 mediagoblin/media_types/video/models.py diff --git a/mediagoblin/db/sql/migrations.py b/mediagoblin/db/sql/migrations.py new file mode 100644 index 00000000..67a02c96 --- /dev/null +++ b/mediagoblin/db/sql/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +MIGRATIONS = [] diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py index 9abd8ec7..3573bc3f 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/sql/models.py @@ -216,6 +216,27 @@ class MediaComment(Base): get_author = relationship(User) +MODELS = [ + User, MediaEntry, Tag, MediaTag, MediaComment] + + +###################################################### +# Special, migrations-tracking table +# +# Not listed in MODELS because this is special and not +# really migrated, but used for migrations (for now) +###################################################### + +class MigrationData(Base): + __tablename__ = "migrations" + + id = Column(Integer, primary_key=True) + name = Column(Unicode, nullable=False, unique=True) + version = Column(Integer, nullable=False, default=0) + +###################################################### + + def show_table_init(): from sqlalchemy import create_engine engine = create_engine('sqlite:///:memory:', echo=True) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py new file mode 100644 index 00000000..a0cea111 --- /dev/null +++ b/mediagoblin/db/sql/util.py @@ -0,0 +1,59 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +class MigrationManager(object): + pass + + +class RegisterMigration(object): + """ + Tool for registering migrations + + Call like: + + @RegisterMigration(33) + def update_dwarves(database): + [...] + + This will register your migration with the default migration + registry. Alternately, to specify a very specific + migration_registry, you can pass in that as the second argument. + + Note, the number of your migration should NEVER be 0 or less than + 0. 0 is the default "no migrations" state! + """ + def __init__(self, migration_number, migration_registry): + assert migration_number > 0, "Migration number must be > 0!" + assert migration_number not in migration_registry, \ + "Duplicate migration numbers detected! That's not allowed!" + + self.migration_number = migration_number + self.migration_registry = migration_registry + + def __call__(self, migration): + self.migration_registry[self.migration_number] = migration + return migration + + +def assure_migrations_table_setup(db): + """ + Make sure the migrations table is set up in the database. + """ + from mediagoblin.db.sql.models import MigrationData + + if not MigrationData.__table__.exists(db): + MigrationData.metadata.create_all( + db, tables=[MigrationData.__table__]) diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py new file mode 100644 index 00000000..52819c6d --- /dev/null +++ b/mediagoblin/gmg_commands/dbupdate.py @@ -0,0 +1,84 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from mediagoblin.db.sql.open import setup_connection_and_db_from_config +from mediagoblin.db.sql.util import ( + MigrationManager, assure_migrations_table_setup) +from mediagoblin.init import setup_global_and_app_config +from mediagoblin.tools.common import import_component + + +class DatabaseData(object): + def __init__(self, name, models, migrations): + self.name = name + self.models = models + self.migrations = migrations + + def make_migration_manager(self, db): + return MigrationManager( + self.name, self.models, self.migrations, db) + + +def gather_database_data(self, media_types): + """ + Gather all database data relevant to the extensions we have + installed so we can do migrations and table initialization. + + Returns a list of DatabaseData objects. + """ + managed_dbdata = [] + + # Add main first + from mediagoblin.db.sql.models import MODELS as MAIN_MODELS + from mediagoblin.db.sql.migrations import MIGRATIONS as MAIN_MIGRATIONS + + managed_dbdata.append( + DatabaseData( + '__main__', MAIN_MODELS, MAIN_MIGRATIONS)) + + # Then get all registered media managers (eventually, plugins) + for media_type in media_types: + models = import_component('%s.models:MODELS' % media_type) + migrations = import_component('%s.migrations:MIGRATIONS' % media_type) + managed_dbdata.append( + DatabaseData(media_type, models, migrations)) + + return managed_dbdata + + +def dbupdate(args): + """ + Initialize or migrate the database as specified by the config file. + + Will also initialize or migrate all extensions (media types, and + in the future, plugins) + """ + globa_config, app_config = setup_global_and_app_config(args.conf_file) + + # Gather information from all media managers / projects + dbdatas = gather_database_data(app_config['media_types']) + + # Set up the database + connection, db = setup_connection_and_db_from_config(app_config) + + # If migrations table not made, make it! + assure_migrations_table_setup(db) + + # Setup media managers for all dbdata, run init/migrate and print info + # For each component, create/migrate tables + for dbdata in dbdatas: + migration_manager = dbdata.make_migration_manager(db) + migration_manager.init_or_migrate() diff --git a/mediagoblin/media_types/image/models.py b/mediagoblin/media_types/image/models.py new file mode 100644 index 00000000..96b5cdf2 --- /dev/null +++ b/mediagoblin/media_types/image/models.py @@ -0,0 +1,17 @@ +from mediagoblin.db.sql.models import Base + +from sqlalchemy import ( + Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, + UniqueConstraint) + + +class ImageData(Base): + __tablename__ = "image__data" + + id = Column(Integer, primary_key=True) + width = Column(Integer) + height = Column(Integer) + + +DATA_MODEL = ImageData +MODELS = [ImageData] diff --git a/mediagoblin/media_types/video/models.py b/mediagoblin/media_types/video/models.py new file mode 100644 index 00000000..c44f1919 --- /dev/null +++ b/mediagoblin/media_types/video/models.py @@ -0,0 +1,16 @@ +from mediagoblin.db.sql.models import Base + +from sqlalchemy import ( + Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, + UniqueConstraint) + + +class VideoData(Base): + __tablename__ = "video__data" + + id = Column(Integer, primary_key=True) + integer + + +DATA_MODEL = VideoData +MODELS = [VideoData] From def13c549a99c36da06f80191fed3bb607ff0cce Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Thu, 29 Dec 2011 14:44:21 -0600 Subject: [PATCH 02/67] Beginnings of the SQL migration manager --- mediagoblin/db/sql/util.py | 101 ++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index a0cea111..ec3d6dcf 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -14,8 +14,107 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . + class MigrationManager(object): - pass + """ + Migration handling tool. + + Takes information about a database, lets you update the database + to the latest migrations, etc. + """ + + def __init__(self, name, models, migration_registry, database): + """ + Args: + - name: identifier of this section of the database + - database: database we're going to migrate + - migration_registry: where we should find all migrations to + run + """ + self.name = name + self.models = models + self.database = database + self.migration_registry = migration_registry + self._sorted_migrations = None + + # For convenience + from mediagoblin.db.sql.models import MigrationData + + self.migration_model = MigrationData + self.migration_table = MigrationData.__table__ + + @property + def sorted_migrations(self): + """ + Sort migrations if necessary and store in self._sorted_migrations + """ + if not self._sorted_migrations: + self._sorted_migrations = sorted( + self.migration_registry.items(), + # sort on the key... the migration number + key=lambda migration_tuple: migration_tuple[0]) + + return self._sorted_migrations + + def latest_migration(self): + """ + Return a migration number for the latest migration, or 0 if + there are no migrations. + """ + if self.sorted_migrations: + return self.sorted_migrations[-1][0] + else: + # If no migrations have been set, we start at 0. + return 0 + + def database_current_migration(self): + """ + Return the current migration in the database. + """ + # TODO + + def set_current_migration(self, migration_number): + """ + Set the migration in the database to migration_number + """ + # TODO + pass + + def migrations_to_run(self): + """ + Get a list of migrations to run still, if any. + + Note that calling this will set your migration version to the + latest version if it isn't installed to anything yet! + """ + ## TODO + # self._ensure_current_migration_record() + # + # db_current_migration = self.database_current_migration() + # + # return [ + # (migration_number, migration_func) + # for migration_number, migration_func in self.sorted_migrations + # if migration_number > db_current_migration] + pass + + def init_or_migrate(self): + # Find out what migration number, if any, this database data is at, + # and what the latest is. + + # Is this our first time? Is there even a table entry for + # this identifier? + # + # If so: + # - create all tables + # - create record in migrations registry + # - print / inform the user + # - return 'inited' + + # Run migrations, if appropriate. + + # If ran migrations, return 'migrated'. Otherwise, return None. + pass class RegisterMigration(object): From 705689b96faedc952f039b433287b0e2b852b11d Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sat, 31 Dec 2011 08:45:41 -0600 Subject: [PATCH 03/67] More work on migration manager, including adding a dry run function --- mediagoblin/db/sql/util.py | 80 +++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index ec3d6dcf..585c3b89 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -15,6 +15,16 @@ # along with this program. If not, see . +import sys + +def _simple_printer(string): + """ + Prints a string, but without an auto \n at the end. + """ + sys.stdout.write(string) + sys.stdout.flush() + + class MigrationManager(object): """ Migration handling tool. @@ -23,7 +33,8 @@ class MigrationManager(object): to the latest migrations, etc. """ - def __init__(self, name, models, migration_registry, database): + def __init__(self, name, models, migration_registry, database, + printer=_simple_printer): """ Args: - name: identifier of this section of the database @@ -36,6 +47,7 @@ class MigrationManager(object): self.database = database self.migration_registry = migration_registry self._sorted_migrations = None + self.printer = printer # For convenience from mediagoblin.db.sql.models import MigrationData @@ -98,23 +110,81 @@ class MigrationManager(object): # if migration_number > db_current_migration] pass - def init_or_migrate(self): + def init_tables(self): + ## TODO + pass + + def create_new_migration_record(self): + ## TODO + pass + + def dry_run(self): + """ + Print out a dry run of what we would have upgraded. + """ + if self.database_current_migration() is None: + self.printer( + u'~> Woulda initialized: %s\n' % self.name_for_printing()) + return u'inited' + + migrations_to_run = self.migrations_to_run() + if migrations_to_run: + self.printer( + u'~> Woulda updated %s:\n' % self.name_for_printing()) + + for migration_number, migration_func in migrations_to_run(): + self.printer( + u' + Would update %s, "%s"\n' % ( + migration_number, migration_func.func_name)) + + return u'migrated' + + def name_for_printing(self): + if self.name == u'__main__': + return u"main mediagoblin tables" + else: + # TODO: Use the friendlier media manager "human readable" name + return u'media type "%s"' % self.name + + def init_or_migrate(self, printer=_simple_printer): + """ + Initialize the database or migrate if appropriate. + + Returns information about whether or not we initialized + ('inited'), migrated ('migrated'), or did nothing (None) + """ # Find out what migration number, if any, this database data is at, # and what the latest is. + migration_number = self.database_current_migration() # Is this our first time? Is there even a table entry for # this identifier? - # # If so: # - create all tables # - create record in migrations registry # - print / inform the user # - return 'inited' + if migration_number is None: + self.printer(u"-> Initializing %s... " % self.name_for_printing()) + + self.init_tables() + # auto-set at latest migration number + self.create_new_migration_record() + + self.printer(u"done.\n") + return u'inited' # Run migrations, if appropriate. + migrations_to_run = self.migrations_to_run() + if migrations_to_run: + self.printer( + u'~> Updating %s:\n' % self.name_for_printing()) + for migration_number, migration_func in migrations_to_run(): + self.printer( + u' + Running migration %s, "%s"\n' % ( + migration_number, migration_func.func_name)) - # If ran migrations, return 'migrated'. Otherwise, return None. - pass + return u'migrated' class RegisterMigration(object): From a315962f0db38ba89bdc38740709a6124e58557c Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 1 Jan 2012 11:47:05 -0600 Subject: [PATCH 04/67] Finishing the init_or_migrate function --- mediagoblin/db/sql/util.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 585c3b89..296c8b78 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -181,11 +181,17 @@ class MigrationManager(object): u'~> Updating %s:\n' % self.name_for_printing()) for migration_number, migration_func in migrations_to_run(): self.printer( - u' + Running migration %s, "%s"\n' % ( + u' + Running migration %s, "%s"... ' % ( migration_number, migration_func.func_name)) + migration_func(self.database) + self.printer('done.') return u'migrated' + # Otherwise return None. Well it would do this anyway, but + # for clarity... ;) + return None + class RegisterMigration(object): """ From 4c86905789a4732af38c618ec0132d431f60b04d Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 1 Jan 2012 11:47:39 -0600 Subject: [PATCH 05/67] Removing printer argument now that we use self.printer --- mediagoblin/db/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 296c8b78..d4cbffea 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -146,7 +146,7 @@ class MigrationManager(object): # TODO: Use the friendlier media manager "human readable" name return u'media type "%s"' % self.name - def init_or_migrate(self, printer=_simple_printer): + def init_or_migrate(self): """ Initialize the database or migrate if appropriate. From 3635ccdf346ceea12358a410a39f6edb34255182 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 1 Jan 2012 16:02:14 -0600 Subject: [PATCH 06/67] More work on SQL MigrationManager Added methods: - migration_data - database_current_migration - migrations_to_run --- mediagoblin/db/sql/util.py | 40 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index d4cbffea..db66776d 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -68,6 +68,17 @@ class MigrationManager(object): return self._sorted_migrations + @property + def migration_data(self): + """ + Get the migration row associated with this object, if any. + """ + query = self.database.query( + self.migration_model).filter_by(name=self.name)[0] + + if query.count(): + return query[0] + def latest_migration(self): """ Return a migration number for the latest migration, or 0 if @@ -83,32 +94,31 @@ class MigrationManager(object): """ Return the current migration in the database. """ - # TODO + return self.migration_data.version def set_current_migration(self, migration_number): """ Set the migration in the database to migration_number """ - # TODO - pass + self.migration_data = migration_number + self.database.commit() def migrations_to_run(self): """ Get a list of migrations to run still, if any. - Note that calling this will set your migration version to the - latest version if it isn't installed to anything yet! + Note that this will fail if there's no migration record for + this class! """ - ## TODO - # self._ensure_current_migration_record() - # - # db_current_migration = self.database_current_migration() - # - # return [ - # (migration_number, migration_func) - # for migration_number, migration_func in self.sorted_migrations - # if migration_number > db_current_migration] - pass + assert self.database_current_migration is None + + db_current_migration = self.database_current_migration() + + return [ + (migration_number, migration_func) + for migration_number, migration_func in self.sorted_migrations + if migration_number > db_current_migration] + def init_tables(self): ## TODO From 8bf3f63af14152099d653e426aa22c1c4e487943 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 08:55:25 -0600 Subject: [PATCH 07/67] Added init_tables method to MigrationManager --- mediagoblin/db/sql/util.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index db66776d..59e8eb8b 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -121,8 +121,16 @@ class MigrationManager(object): def init_tables(self): - ## TODO - pass + """ + Create all tables relative to this package + """ + # sanity check before we proceed, none of these should be created + for model in self.models: + assert not model.__table__.exists(self.database) + + self.migration_model.metadata.create_all( + self.database, + tables=[model.__table__ for model in self.models]) def create_new_migration_record(self): ## TODO @@ -163,6 +171,8 @@ class MigrationManager(object): Returns information about whether or not we initialized ('inited'), migrated ('migrated'), or did nothing (None) """ + assure_migrations_table_setup(self.database) + # Find out what migration number, if any, this database data is at, # and what the latest is. migration_number = self.database_current_migration() From b0ec21bff3fd01dc5e1d217560ca6479aadaae8b Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 09:12:11 -0600 Subject: [PATCH 08/67] Add create_new_migration_record method to MigrationManager --- mediagoblin/db/sql/util.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 59e8eb8b..a42d992f 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -126,6 +126,7 @@ class MigrationManager(object): """ # sanity check before we proceed, none of these should be created for model in self.models: + # Maybe in the future just print out a "Yikes!" or something? assert not model.__table__.exists(self.database) self.migration_model.metadata.create_all( @@ -133,8 +134,12 @@ class MigrationManager(object): tables=[model.__table__ for model in self.models]) def create_new_migration_record(self): - ## TODO - pass + """ + Create a new migration record for this migration set + """ + self.migration_model( + name=self.name, + version=self.latest_migration()) def dry_run(self): """ From 09dcc34c95a361c76a1aeed59b41cb14f91c17c6 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 13:36:19 -0600 Subject: [PATCH 09/67] Commit that new migration record ;) --- mediagoblin/db/sql/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index a42d992f..61ba1b45 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -140,6 +140,7 @@ class MigrationManager(object): self.migration_model( name=self.name, version=self.latest_migration()) + self.database.commit() def dry_run(self): """ From 23f4c6b2fd2440433bcb2443633d847ddad17238 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 13:38:08 -0600 Subject: [PATCH 10/67] We should probably add that object to the DB also :P --- mediagoblin/db/sql/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 61ba1b45..4938bcad 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -137,9 +137,10 @@ class MigrationManager(object): """ Create a new migration record for this migration set """ - self.migration_model( + migration_record = self.migration_model( name=self.name, version=self.latest_migration()) + self.database.add(migration_record) self.database.commit() def dry_run(self): From 851df6214ee0332eb45282702524934cdb9ebcf7 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 13:59:36 -0600 Subject: [PATCH 11/67] Use .first() instead of [0]... thanks elrond :) --- mediagoblin/db/sql/util.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 4938bcad..dfc36a61 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -73,11 +73,8 @@ class MigrationManager(object): """ Get the migration row associated with this object, if any. """ - query = self.database.query( - self.migration_model).filter_by(name=self.name)[0] - - if query.count(): - return query[0] + return self.database.query( + self.migration_model).filter_by(name=self.name).first() def latest_migration(self): """ From cbf29f2d5897c90794e6a6f855a4371296e56794 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 14:04:16 -0600 Subject: [PATCH 12/67] assert was positive when it should be negative, fixed --- mediagoblin/db/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index dfc36a61..604f040c 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -107,7 +107,7 @@ class MigrationManager(object): Note that this will fail if there's no migration record for this class! """ - assert self.database_current_migration is None + assert self.database_current_migration is not None db_current_migration = self.database_current_migration() From bf81382896e98e5a7c3dcd426ee8c73fe9ef84a6 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 14:40:32 -0600 Subject: [PATCH 13/67] Make name the primary key in migration records --- mediagoblin/db/sql/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py index 3573bc3f..ed733aff 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/sql/models.py @@ -230,8 +230,7 @@ MODELS = [ class MigrationData(Base): __tablename__ = "migrations" - id = Column(Integer, primary_key=True) - name = Column(Unicode, nullable=False, unique=True) + name = Column(Unicode, primary_key=True) version = Column(Integer, nullable=False, default=0) ###################################################### From dc5da0f891895c79f29be05df1e14035b080dcdc Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 14:56:05 -0600 Subject: [PATCH 14/67] Another MigrationManager fix. self.database -> self.database.engine (thanks again Elrond for the catch) --- mediagoblin/db/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 604f040c..0911884e 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -127,7 +127,7 @@ class MigrationManager(object): assert not model.__table__.exists(self.database) self.migration_model.metadata.create_all( - self.database, + self.database.engine, tables=[model.__table__ for model in self.models]) def create_new_migration_record(self): From 3f2c6f96c191eb89f1c6ec7c903219b445f410a0 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 16:08:32 -0600 Subject: [PATCH 15/67] No need for self here (thanks again Elrond ;)) --- mediagoblin/gmg_commands/dbupdate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py index 52819c6d..1fc5121a 100644 --- a/mediagoblin/gmg_commands/dbupdate.py +++ b/mediagoblin/gmg_commands/dbupdate.py @@ -32,7 +32,7 @@ class DatabaseData(object): self.name, self.models, self.migrations, db) -def gather_database_data(self, media_types): +def gather_database_data(media_types): """ Gather all database data relevant to the extensions we have installed so we can do migrations and table initialization. From cfbbdcc5ad08f2c6a0614cae2256880d6cbff8f6 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 2 Jan 2012 16:14:55 -0600 Subject: [PATCH 16/67] Another db->db.engine because I'm bad at things ;) Thanks again Elrond. --- mediagoblin/db/sql/util.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 0911884e..65976538 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -50,10 +50,10 @@ class MigrationManager(object): self.printer = printer # For convenience - from mediagoblin.db.sql.models import MigrationData + from mediagoblin.db.sql.models import MigrationRecord - self.migration_model = MigrationData - self.migration_table = MigrationData.__table__ + self.migration_model = MigrationRecord + self.migration_table = MigrationRecord.__table__ @property def sorted_migrations(self): @@ -251,8 +251,8 @@ def assure_migrations_table_setup(db): """ Make sure the migrations table is set up in the database. """ - from mediagoblin.db.sql.models import MigrationData + from mediagoblin.db.sql.models import MigrationRecord - if not MigrationData.__table__.exists(db): - MigrationData.metadata.create_all( - db, tables=[MigrationData.__table__]) + if not MigrationRecord.__table__.exists(db.engine): + MigrationRecord.metadata.create_all( + db, tables=[MigrationRecord.__table__]) From 0f10058fd38f1b97068320fe62eb2494803a263a Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 15 Jan 2012 11:35:26 -0600 Subject: [PATCH 17/67] A theoretical set of models to migrate about with, plus one migration ;) --- mediagoblin/tests/test_sql_migrations.py | 177 +++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 mediagoblin/tests/test_sql_migrations.py diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py new file mode 100644 index 00000000..32950d38 --- /dev/null +++ b/mediagoblin/tests/test_sql_migrations.py @@ -0,0 +1,177 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import copy + +from sqlalchemy import ( + Table, Column, MetaData, + Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, + UniqueConstraint, PickleType) +from sqlalchemy.ext.declarative import declarative_base +from migrate import changeset + +from mediagoblin.db.sql.base import GMGTableBase + + +# This one will get filled with local migrations +FULL_MIGRATIONS = {} + + +####################################################### +# Migration set 1: Define initial models, no migrations +####################################################### + +Base1 = declarative_base(cls=GMGTableBase) + +class Creature1(Base1): + __tablename__ = "creature" + + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True, nullable=False, index=True) + num_legs = Column(Integer, nullable=False) + is_demon = Column(Boolean) + +class Level1(Base1): + __tablename__ = "level" + + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True, nullable=False, index=True) + description = Column(UnicodeText) + exits = Column(PickleType) + +SET1_MODELS = [Creature1, Level1] + +SET1_MIGRATIONS = [] + +####################################################### +# Migration set 2: A few migrations and new model +####################################################### + +Base2 = declarative_base(cls=GMGTableBase) + +class Creature2(Base2): + __tablename__ = "creature" + + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True, nullable=False, index=True) + num_legs = Column(Integer, nullable=False) + +class CreaturePower2(Base2): + __tablename__ = "creature_power" + + id = Column(Integer, primary_key=True) + creature = Column( + Integer, ForeignKey('creature.id'), nullable=False) + name = Column(Unicode) + description = Column(Unicode) + +class Level2(Base2): + __tablename__ = "level" + + id = Column(Integer, primary_key=True) + name = Column(Unicode) + description = Column(UnicodeText) + +class LevelExit2(Base2): + __tablename__ = "level_exit" + + id = Column(Integer, primary_key=True) + name = Column(Unicode) + from_level = Column( + Integer, ForeignKey('level.id'), nullable=False) + to_level = Column( + Integer, ForeignKey('level.id'), nullable=False) + +SET2_MODELS = [Creature2, CreaturePower2, Level2, LevelExit2] + + +@RegisterMigration(1, FULL_MIGRATIONS) +def creature_remove_is_demon(db_conn): + creature_table = Table( + 'creature', MetaData(), + autoload=True, autoload_with=db_conn.engine) + db_conn.execute( + creature_table.drop_column('is_demon')) + + +@RegisterMigration(2, FULL_MIGRATIONS) +def creature_powers_new_table(db_conn): + pass + +@RegisterMigration(3, FULL_MIGRATIONS) +def level_exits_new_table(db_conn): + pass + + +# A hack! At this point we freeze-fame and get just a partial list of +# migrations + +PARTIAL_MIGRATIONS = copy.copy(FULL_MIGRATIONS) + +####################################################### +# Migration set 3: Final migrations +####################################################### + +Base3 = declarative_base(cls=GMGTableBase) + +class Creature3(Base3): + __tablename__ = "creature" + + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True, nullable=False, index=True) + num_limbs= Column(Integer, nullable=False) + +class CreaturePower3(Base3): + __tablename__ = "creature_power" + + id = Column(Integer, primary_key=True) + creature = Column( + Integer, ForeignKey('creature.id'), nullable=False, index=True) + name = Column(Unicode) + description = Column(Unicode) + +class Level3(Base3): + __tablename__ = "level" + + id = Column(Integer, primary_key=True) + name = Column(Unicode) + description = Column(UnicodeText) + +class LevelExit3(Base3): + __tablename__ = "level_exit" + + id = Column(Integer, primary_key=True) + name = Column(Unicode) + from_level = Column( + Integer, ForeignKey('level.id'), nullable=False, index=True) + to_level = Column( + Integer, ForeignKey('level.id'), nullable=False, index=True) + + +SET3_MODELS = [Creature3, CreaturePower3, Level3, LevelExit3] + + +@RegisterMigration(4, FULL_MIGRATIONS) +def creature_num_legs_to_num_limbs(db_conn): + pass + +@RegisterMigration(5, FULL_MIGRATIONS) +def level_exit_index_from_and_to_level(db_conn): + pass + +@RegisterMigration(6, FULL_MIGRATIONS) +def creature_power_index_creature(db_conn): + pass From 129c36be6f29977ca18ae719c1d3a5c2aad25d89 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 15 Jan 2012 11:36:00 -0600 Subject: [PATCH 18/67] Might as well call it "set2 migrations" --- mediagoblin/tests/test_sql_migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 32950d38..8682e69d 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -119,7 +119,7 @@ def level_exits_new_table(db_conn): # A hack! At this point we freeze-fame and get just a partial list of # migrations -PARTIAL_MIGRATIONS = copy.copy(FULL_MIGRATIONS) +SET2_MIGRATIONS = copy.copy(FULL_MIGRATIONS) ####################################################### # Migration set 3: Final migrations From 89694d6d69c855f1dbaaa725867ab070cc7b2490 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 15 Jan 2012 14:55:07 -0600 Subject: [PATCH 19/67] More test migration work. Closing to working migrations for set 2... Also, this file is written in 2012, correct that ;) --- mediagoblin/tests/test_sql_migrations.py | 67 ++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 8682e69d..0f1f02bd 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -1,5 +1,5 @@ # GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# Copyright (C) 2012, 2012 MediaGoblin contributors. See AUTHORS. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -18,9 +18,10 @@ import copy from sqlalchemy import ( Table, Column, MetaData, - Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, - UniqueConstraint, PickleType) + Integer, Float, Unicode, UnicodeText, DateTime, Boolean, + ForeignKey, UniqueConstraint, PickleType) from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.sql import select, insert from migrate import changeset from mediagoblin.db.sql.base import GMGTableBase @@ -77,6 +78,7 @@ class CreaturePower2(Base2): Integer, ForeignKey('creature.id'), nullable=False) name = Column(Unicode) description = Column(Unicode) + hitpower = Column(Integer, nullable=False) class Level2(Base2): __tablename__ = "level" @@ -109,11 +111,61 @@ def creature_remove_is_demon(db_conn): @RegisterMigration(2, FULL_MIGRATIONS) def creature_powers_new_table(db_conn): - pass + metadata = MetaData() + creature_powers = Table( + 'creature_power', metadata, + Column('id', Integer, primary_key=True), + Column('creature', + Integer, ForeignKey('creature.id'), nullable=False), + Column('name', Unicode), + Column('description', Unicode), + Column('hitpower', Integer, nullable=False)) + metadata.create_all(db_conn.engine) + @RegisterMigration(3, FULL_MIGRATIONS) def level_exits_new_table(db_conn): - pass + # First, create the table + # ----------------------- + metadata = MetaData() + level_exits = Table( + 'level_exit', metadata, + Column('id', Integer, primary_key=True), + Column('name', Unicode), + Column('from_level', + Integer, ForeignKey('level.id'), nullable=False), + Column('to_level', + Integer, ForeignKey('level.id'), nullable=False)) + metadata.create_all(db_conn.engine) + + # And now, convert all the old exit pickles to new level exits + # ------------------------------------------------------------ + + # Minimal representation of level table. + # Not auto-introspecting here because of pickle table. I'm not + # sure sqlalchemy can auto-introspect pickle columns. + levels = Table( + 'level', metadata, + Column('id', Integer, primary_key=True), + Column('exits', PickleType)) + + # query over and insert + result = db_conn.execute( + select([levels], levels.c.exits!=None)) + + for level in result: + this_exit = level['exits'] + + # Insert the level exit + db_conn.execute( + level_exits.insert().values( + name=this_exit['name'], + from_level=this_exit['from_level'], + to_level=this_exit['to_level'])) + + # Finally, drop the old level exits pickle table + # ---------------------------------------------- + # A hack! At this point we freeze-fame and get just a partial list of @@ -142,6 +194,7 @@ class CreaturePower3(Base3): Integer, ForeignKey('creature.id'), nullable=False, index=True) name = Column(Unicode) description = Column(Unicode) + hitpower = Column(Float, nullable=False) class Level3(Base3): __tablename__ = "level" @@ -175,3 +228,7 @@ def level_exit_index_from_and_to_level(db_conn): @RegisterMigration(6, FULL_MIGRATIONS) def creature_power_index_creature(db_conn): pass + +@RegisterMigration(7, FULL_MIGRATIONS) +def creature_power_hitpower_to_float(db_conn): + pass From 473e06053c45de534d1e9e5d94f48cf0188856af Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 15 Jan 2012 15:43:59 -0600 Subject: [PATCH 20/67] binding migration metadata to engine, and level_exits_new_table should now work --- mediagoblin/tests/test_sql_migrations.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 0f1f02bd..ba9e967a 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -102,16 +102,16 @@ SET2_MODELS = [Creature2, CreaturePower2, Level2, LevelExit2] @RegisterMigration(1, FULL_MIGRATIONS) def creature_remove_is_demon(db_conn): + metadata = MetaData(bind=db_conn.engine) creature_table = Table( - 'creature', MetaData(), + 'creature', metadata, autoload=True, autoload_with=db_conn.engine) - db_conn.execute( - creature_table.drop_column('is_demon')) + creature_table.drop_column('is_demon') @RegisterMigration(2, FULL_MIGRATIONS) def creature_powers_new_table(db_conn): - metadata = MetaData() + metadata = MetaData(bind=db_conn.engine) creature_powers = Table( 'creature_power', metadata, Column('id', Integer, primary_key=True), @@ -127,7 +127,7 @@ def creature_powers_new_table(db_conn): def level_exits_new_table(db_conn): # First, create the table # ----------------------- - metadata = MetaData() + metadata = MetaData(bind=db_conn.engine) level_exits = Table( 'level_exit', metadata, Column('id', Integer, primary_key=True), @@ -165,7 +165,7 @@ def level_exits_new_table(db_conn): # Finally, drop the old level exits pickle table # ---------------------------------------------- - + levels.drop_column('exits') # A hack! At this point we freeze-fame and get just a partial list of From 248b5061ec5671e5dd371c2ea1573e5b10219656 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 15 Jan 2012 16:43:14 -0600 Subject: [PATCH 21/67] All theoretical migrations written! --- mediagoblin/tests/test_sql_migrations.py | 30 ++++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index ba9e967a..d5b9b30e 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -17,7 +17,7 @@ import copy from sqlalchemy import ( - Table, Column, MetaData, + Table, Column, MetaData, Index Integer, Float, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, UniqueConstraint, PickleType) from sqlalchemy.ext.declarative import declarative_base @@ -219,16 +219,36 @@ SET3_MODELS = [Creature3, CreaturePower3, Level3, LevelExit3] @RegisterMigration(4, FULL_MIGRATIONS) def creature_num_legs_to_num_limbs(db_conn): - pass + metadata = MetaData(bind=db_conn.engine) + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=db_conn.engine) + creature_table.c.num_legs.alter(name="num_limbs") + @RegisterMigration(5, FULL_MIGRATIONS) def level_exit_index_from_and_to_level(db_conn): - pass + metadata = MetaData(bind=db_conn.engine) + level_exit = Table( + 'level_exit', metadata, + autoload=True, autoload_with=db_conn.engine) + Index('ix_from_level', level_exit.c.from_level).create(engine) + Index('ix_to_exit', level_exit.c.to_exit).create(engine) + @RegisterMigration(6, FULL_MIGRATIONS) def creature_power_index_creature(db_conn): - pass + metadata = MetaData(bind=db_conn.engine) + creature_power = Table( + 'creature_power', metadata, + autoload=True, autoload_with=db_conn.engine) + Index('ix_creature', creature_power.c.creature).create(engine) + @RegisterMigration(7, FULL_MIGRATIONS) def creature_power_hitpower_to_float(db_conn): - pass + metadata = MetaData(bind=db_conn.engine) + creature_power = Table( + 'creature_power', metadata, + autoload=True, autoload_with=db_conn.engine) + creature_power.c.hitpower.alter(type=Float) From 64d280647c1b6c1166b1e096914a590710eb0f4e Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 16 Jan 2012 14:39:24 -0600 Subject: [PATCH 22/67] Insert migration1 objects. Also, Level1 id from Integer->Unicode --- mediagoblin/tests/test_sql_migrations.py | 50 ++++++++++++++++++++---- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index d5b9b30e..50bdbd9d 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -20,6 +20,7 @@ from sqlalchemy import ( Table, Column, MetaData, Index Integer, Float, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, UniqueConstraint, PickleType) +from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql import select, insert from migrate import changeset @@ -48,7 +49,7 @@ class Creature1(Base1): class Level1(Base1): __tablename__ = "level" - id = Column(Integer, primary_key=True) + id = Column(Unicode, primary_key=True) name = Column(Unicode, unique=True, nullable=False, index=True) description = Column(UnicodeText) exits = Column(PickleType) @@ -83,7 +84,7 @@ class CreaturePower2(Base2): class Level2(Base2): __tablename__ = "level" - id = Column(Integer, primary_key=True) + id = Column(Unicode, primary_key=True) name = Column(Unicode) description = Column(UnicodeText) @@ -93,9 +94,9 @@ class LevelExit2(Base2): id = Column(Integer, primary_key=True) name = Column(Unicode) from_level = Column( - Integer, ForeignKey('level.id'), nullable=False) + Unicode, ForeignKey('level.id'), nullable=False) to_level = Column( - Integer, ForeignKey('level.id'), nullable=False) + Unicode, ForeignKey('level.id'), nullable=False) SET2_MODELS = [Creature2, CreaturePower2, Level2, LevelExit2] @@ -199,7 +200,7 @@ class CreaturePower3(Base3): class Level3(Base3): __tablename__ = "level" - id = Column(Integer, primary_key=True) + id = Column(Unicode, primary_key=True) name = Column(Unicode) description = Column(UnicodeText) @@ -209,9 +210,9 @@ class LevelExit3(Base3): id = Column(Integer, primary_key=True) name = Column(Unicode) from_level = Column( - Integer, ForeignKey('level.id'), nullable=False, index=True) + Unicode, ForeignKey('level.id'), nullable=False, index=True) to_level = Column( - Integer, ForeignKey('level.id'), nullable=False, index=True) + Unicode, ForeignKey('level.id'), nullable=False, index=True) SET3_MODELS = [Creature3, CreaturePower3, Level3, LevelExit3] @@ -252,3 +253,38 @@ def creature_power_hitpower_to_float(db_conn): 'creature_power', metadata, autoload=True, autoload_with=db_conn.engine) creature_power.c.hitpower.alter(type=Float) + + +def _insert_migration1_objects(session): + # Insert creatures + session.add_all( + [Creature1(name='centipede', + num_legs=100, + is_demon=False), + Creature1(name='wolf', + num_legs=4, + is_demon=False), + # don't ask me what a wizardsnake is. + Creature1(name='wizardsnake', + num_legs=0, + is_demon=True)]) + + session.add_all( + [Level1(id='necroplex', + name='The Necroplex', + description='A complex full of pure deathzone.', + exits={ + 'deathwell': 'evilstorm', + 'portal': 'central_park'}), + Level1(id='evilstorm', + name='Evil Storm', + description='A storm full of pure evil.', + exits={}), # you can't escape the evilstorm + Level1(id='central_park' + name='Central Park, NY, NY', + description="New York's friendly Central Park.", + exits={ + 'portal': 'necroplex'})]) + + session.commit() + From d74a9483ded26c575686b751e47c1e2a6f395fef Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 16 Jan 2012 16:22:25 -0600 Subject: [PATCH 23/67] Theoretical full set of migration2 objects to insert for testing --- mediagoblin/tests/test_sql_migrations.py | 77 +++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 50bdbd9d..4d58cfbf 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -20,7 +20,7 @@ from sqlalchemy import ( Table, Column, MetaData, Index Integer, Float, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, UniqueConstraint, PickleType) -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql import select, insert from migrate import changeset @@ -70,6 +70,7 @@ class Creature2(Base2): id = Column(Integer, primary_key=True) name = Column(Unicode, unique=True, nullable=False, index=True) num_legs = Column(Integer, nullable=False) + magical_powers = relationship("CreaturePower2") class CreaturePower2(Base2): __tablename__ = "creature_power" @@ -196,6 +197,7 @@ class CreaturePower3(Base3): name = Column(Unicode) description = Column(Unicode) hitpower = Column(Float, nullable=False) + magical_powers = relationship("CreaturePower3") class Level3(Base3): __tablename__ = "level" @@ -269,6 +271,7 @@ def _insert_migration1_objects(session): num_legs=0, is_demon=True)]) + # Insert levels session.add_all( [Level1(id='necroplex', name='The Necroplex', @@ -288,3 +291,75 @@ def _insert_migration1_objects(session): session.commit() + +def _insert_migration2_objects(session): + # Insert creatures + session.add_all( + [Creature2( + name='centipede', + num_legs=100), + Creature2( + name='wolf', + num_legs=4, + magical_powers = [ + CreaturePower2( + name="ice breath", + description="A blast of icy breath!", + hitpower=20), + CreaturePower2( + name="death stare", + description="A frightening stare, for sure!", + hitpower=45)]), + Creature2( + name='wizardsnake', + num_legs=0, + magical_powers=[ + CreaturePower2( + name='death_rattle', + description='A rattle... of DEATH!', + hitpower=1000), + CreaturePower2( + name='sneaky_stare', + description="The sneakiest stare you've ever seen!" + hitpower=300), + CreaturePower2( + name='slithery_smoke', + description="A blast of slithery, slithery smoke.", + hitpower=10), + CreaturePower2( + name='treacherous_tremors', + description="The ground shakes beneath footed animals!", + hitpower=0)])]) + + # Insert levels + session.add_all( + [Level2(id='necroplex', + name='The Necroplex', + description='A complex full of pure deathzone.'), + Level2(id='evilstorm', + name='Evil Storm', + description='A storm full of pure evil.', + exits=[]), # you can't escape the evilstorm + Level2(id='central_park' + name='Central Park, NY, NY', + description="New York's friendly Central Park.")]) + + # necroplex exits + session.add_all( + [LevelExit2(name='deathwell', + from_level='necroplex', + to_level='evilstorm'), + LevelExit2(name='portal', + from_level='necroplex', + to_level='central_park')]) + + # there are no evilstorm exits because there is no exit from the + # evilstorm + + # central park exits + session.add_all( + [LevelExit2(name='portal', + from_level='central_park', + to_level='necroplex')] + + session.commit() From 356654deb878d667806c347e47500a2dd0dadb09 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 16 Jan 2012 16:40:51 -0600 Subject: [PATCH 24/67] Docstrings for stage 2 migrations --- mediagoblin/tests/test_sql_migrations.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 4d58cfbf..ccaf60ba 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -104,6 +104,10 @@ SET2_MODELS = [Creature2, CreaturePower2, Level2, LevelExit2] @RegisterMigration(1, FULL_MIGRATIONS) def creature_remove_is_demon(db_conn): + """ + Remove the is_demon field from the creature model. We don't need + it! + """ metadata = MetaData(bind=db_conn.engine) creature_table = Table( 'creature', metadata, @@ -113,6 +117,11 @@ def creature_remove_is_demon(db_conn): @RegisterMigration(2, FULL_MIGRATIONS) def creature_powers_new_table(db_conn): + """ + Add a new table for creature powers. Nothing needs to go in it + yet though as there wasn't anything that previously held this + information + """ metadata = MetaData(bind=db_conn.engine) creature_powers = Table( 'creature_power', metadata, @@ -127,6 +136,10 @@ def creature_powers_new_table(db_conn): @RegisterMigration(3, FULL_MIGRATIONS) def level_exits_new_table(db_conn): + """ + Make a new table for level exits and move the previously pickled + stuff over to here (then drop the old unneeded table) + """ # First, create the table # ----------------------- metadata = MetaData(bind=db_conn.engine) @@ -258,6 +271,9 @@ def creature_power_hitpower_to_float(db_conn): def _insert_migration1_objects(session): + """ + Test objects to insert for the first set of things + """ # Insert creatures session.add_all( [Creature1(name='centipede', @@ -293,6 +309,9 @@ def _insert_migration1_objects(session): def _insert_migration2_objects(session): + """ + Test objects to insert for the second set of things + """ # Insert creatures session.add_all( [Creature2( From d6cdf64b4f498fabfcf8262c3f444b1d4c2147df Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 16 Jan 2012 16:59:14 -0600 Subject: [PATCH 25/67] Wrote some (semi-silly) descriptions of each migration --- mediagoblin/tests/test_sql_migrations.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index ccaf60ba..4117ce0c 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -235,6 +235,11 @@ SET3_MODELS = [Creature3, CreaturePower3, Level3, LevelExit3] @RegisterMigration(4, FULL_MIGRATIONS) def creature_num_legs_to_num_limbs(db_conn): + """ + Turns out we're tracking all sorts of limbs, not "legs" + specifically. Humans would be 4 here, for instance. So we + renamed the column. + """ metadata = MetaData(bind=db_conn.engine) creature_table = Table( 'creature', metadata, @@ -244,6 +249,9 @@ def creature_num_legs_to_num_limbs(db_conn): @RegisterMigration(5, FULL_MIGRATIONS) def level_exit_index_from_and_to_level(db_conn): + """ + Index the from and to levels of the level exit table. + """ metadata = MetaData(bind=db_conn.engine) level_exit = Table( 'level_exit', metadata, @@ -254,6 +262,9 @@ def level_exit_index_from_and_to_level(db_conn): @RegisterMigration(6, FULL_MIGRATIONS) def creature_power_index_creature(db_conn): + """ + Index our foreign key relationship to the creatures + """ metadata = MetaData(bind=db_conn.engine) creature_power = Table( 'creature_power', metadata, @@ -263,6 +274,13 @@ def creature_power_index_creature(db_conn): @RegisterMigration(7, FULL_MIGRATIONS) def creature_power_hitpower_to_float(db_conn): + """ + Convert hitpower column on creature power table from integer to + float. + + Turns out we want super precise values of how much hitpower there + really is. + """ metadata = MetaData(bind=db_conn.engine) creature_power = Table( 'creature_power', metadata, From 780fdd7bd6c88b55ee7058d8156a0d233d997236 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Thu, 19 Jan 2012 21:30:47 -0600 Subject: [PATCH 26/67] import changeset into sql models --- mediagoblin/db/sql/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py index ed733aff..41b8b490 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/sql/models.py @@ -29,6 +29,13 @@ from mediagoblin.db.sql.extratypes import PathTupleWithSlashes from mediagoblin.db.sql.base import Base, DictReadAttrProxy from mediagoblin.db.mixin import UserMixin, MediaEntryMixin +# It's actually kind of annoying how sqlalchemy-migrate does this, if +# I understand it right, but whatever. Anyway, don't remove this :P +# +# We could do migration calls more manually instead of relying on +# this import-based meddling... +from migrate import changeset + class SimpleFieldAlias(object): """An alias for any field""" From dc3db4681f0e3c99914dac99945a399a045f895f Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Thu, 19 Jan 2012 21:32:37 -0600 Subject: [PATCH 27/67] Insert migration objects round 3 --- mediagoblin/tests/test_sql_migrations.py | 87 ++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 4117ce0c..96324d77 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -400,3 +400,90 @@ def _insert_migration2_objects(session): to_level='necroplex')] session.commit() + + +def _insert_migration3_objects(session): + """ + Test objects to insert for the third set of things + """ + # Insert creatures + session.add_all( + [Creature3( + name='centipede', + num_limbs=100), + Creature3( + name='wolf', + num_limbs=4, + magical_powers = [ + CreaturePower3( + name="ice breath", + description="A blast of icy breath!", + hitpower=20.0), + CreaturePower3( + name="death stare", + description="A frightening stare, for sure!", + hitpower=45.0)]), + Creature3( + name='wizardsnake', + num_limbs=0, + magical_powers=[ + CreaturePower3( + name='death_rattle', + description='A rattle... of DEATH!', + hitpower=1000.0), + CreaturePower3( + name='sneaky_stare', + description="The sneakiest stare you've ever seen!" + hitpower=300.0), + CreaturePower3( + name='slithery_smoke', + description="A blast of slithery, slithery smoke.", + hitpower=10.0), + CreaturePower3( + name='treacherous_tremors', + description="The ground shakes beneath footed animals!", + hitpower=0.0)])], + # annnnnd one more to test a floating point hitpower + Creature3( + name='deity', + numb_limbs=30, + magical_powers[ + CreaturePower3( + name='smite', + description='Smitten by holy wrath!', + hitpower=9999.9)) + + # Insert levels + session.add_all( + [Level3(id='necroplex', + name='The Necroplex', + description='A complex full of pure deathzone.'), + Level3(id='evilstorm', + name='Evil Storm', + description='A storm full of pure evil.', + exits=[]), # you can't escape the evilstorm + Level3(id='central_park' + name='Central Park, NY, NY', + description="New York's friendly Central Park.")]) + + # necroplex exits + session.add_all( + [LevelExit3(name='deathwell', + from_level='necroplex', + to_level='evilstorm'), + LevelExit3(name='portal', + from_level='necroplex', + to_level='central_park')]) + + # there are no evilstorm exits because there is no exit from the + # evilstorm + + # central park exits + session.add_all( + [LevelExit3(name='portal', + from_level='central_park', + to_level='necroplex')] + + session.commit() + + From 09e2c48701f8a3fb00da68a61935e75c46a3ec3b Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sat, 21 Jan 2012 16:14:32 -0600 Subject: [PATCH 28/67] Wrote up some scaffolding for the actual tests --- mediagoblin/tests/test_sql_migrations.py | 56 ++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 96324d77..d94888e9 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -397,7 +397,7 @@ def _insert_migration2_objects(session): session.add_all( [LevelExit2(name='portal', from_level='central_park', - to_level='necroplex')] + to_level='necroplex')]) session.commit() @@ -451,7 +451,7 @@ def _insert_migration3_objects(session): CreaturePower3( name='smite', description='Smitten by holy wrath!', - hitpower=9999.9)) + hitpower=9999.9)))) # Insert levels session.add_all( @@ -482,8 +482,58 @@ def _insert_migration3_objects(session): session.add_all( [LevelExit3(name='portal', from_level='central_park', - to_level='necroplex')] + to_level='necroplex')]) session.commit() +def create_test_engine(): + from sqlalchemy import create_engine + engine = create_engine('sqlite:///:memory:', echo=False) + return engine + + +def test_set1_to_set3(): + # Create / connect to database + # Create tables by migrating on empty initial set + + # Install the initial set + # Check version in database + # Sanity check a few things in the database + + # Migrate + # Make sure version matches expected + # Check all things in database match expected + pass + + +def test_set2_to_set3(): + # Create / connect to database + # Create tables by migrating on empty initial set + + # Install the initial set + # Check version in database + # Sanity check a few things in the database + + # Migrate + # Make sure version matches expected + # Check all things in database match expected + pass + + +def test_set1_to_set2_to_set3(): + # Create / connect to database + # Create tables by migrating on empty initial set + + # Install the initial set + # Check version in database + # Sanity check a few things in the database + + # Migrate + # Make sure version matches expected + # Check all things in database match expected + + # Migrate again + # Make sure version matches expected again + # Check all things in database match expected again + pass From 40f0996ab9160fe586e3d8f3fd22412ce6a34d27 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Fri, 27 Jan 2012 18:19:34 -0600 Subject: [PATCH 29/67] A ton more work on the SQL migration unit tests... --- mediagoblin/tests/test_sql_migrations.py | 90 ++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index d94888e9..4e0a89d9 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -51,7 +51,7 @@ class Level1(Base1): id = Column(Unicode, primary_key=True) name = Column(Unicode, unique=True, nullable=False, index=True) - description = Column(UnicodeText) + description = Column(Unicode) exits = Column(PickleType) SET1_MODELS = [Creature1, Level1] @@ -87,7 +87,7 @@ class Level2(Base2): id = Column(Unicode, primary_key=True) name = Column(Unicode) - description = Column(UnicodeText) + description = Column(Unicode) class LevelExit2(Base2): __tablename__ = "level_exit" @@ -217,7 +217,7 @@ class Level3(Base3): id = Column(Unicode, primary_key=True) name = Column(Unicode) - description = Column(UnicodeText) + description = Column(Unicode) class LevelExit3(Base3): __tablename__ = "level_exit" @@ -487,21 +487,101 @@ def _insert_migration3_objects(session): session.commit() +def CollectingPrinter(object): + def __init__(self): + self.collection = [] + + def __call__(self, string): + self.collection.append(string) + + @property + def combined_string(self): + return u''.join(self.collection) + + def create_test_engine(): from sqlalchemy import create_engine engine = create_engine('sqlite:///:memory:', echo=False) - return engine + sqlalchemy.orm import sessionmaker + Session = sessionmaker(bind=engine) + return engine, Session + + +def assert_col_type(column, class): + assert isinstance(column.type, class) def test_set1_to_set3(): # Create / connect to database + engine, Session = create_test_engine() # Create tables by migrating on empty initial set + printer = CollectingPrinter + migration_manager = MigrationManager( + '__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), + printer) + # Check latest migration and database current migration + assert migration_manager.latest_migration == 0 + assert migration_manager.database_current_migration == None + + result = migration_manager.init_or_migrate() + # Make sure output was "inited" + assert result == u'inited' + # Check output + assert printer.combined_string == "-> Initializing __main__... done.\n" + # Check version in database + assert migration_manager.latest_migration == 0 + assert migration_manager.database_current_migration == 0 # Install the initial set + _insert_migration1_objects(Session()) + # Try to "re-migrate" with same manager settings... nothing should happen + migration_manager = MigrationManager( + '__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), + printer) + assert migration_manager.init_or_migrate() == None + # Check version in database - # Sanity check a few things in the database + assert migration_manager.latest_migration == 0 + assert migration_manager.database_current_migration == 0 + + # Sanity check a few things in the database... + metadata = MetaData(bind=db_conn.engine) + + # Check the structure of the creature table + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=db_conn.engine) + assert set(creature_table.c.keys()) == set( + ['id', 'name', 'num_legs', 'is_demon']) + assert_col_type(creature_table.c.id, Integer) + assert_col_type(creature_table.c.name, Unicode) + assert creature_table.c.name.nullable is False + assert creature_table.c.name.index is True + assert creature_table.c.name.unique is True + assert_col_type(creature_table.c.num_legs, Integer) + assert creature_table.c.num_legs.nullable is False + assert_col_type(creature_table.c.is_demon, Boolean) + + # Check the structure of the level table + level_table = Table( + 'level', metadata, + autoload=True, autoload_with=db_conn.engine) + assert set(level_table.c.keys()) == set( + ['id', 'name', 'description', 'exits']) + assert_col_type(level_table.c.id, Integer) + assert_col_type(level_table.c.name, Unicode) + assert level_table.c.name.nullable is False + assert level_table.c.name.index is True + assert level_table.c.name.unique is True + assert_col_type(level_table.c.description, Unicode) + # Skipping exits... Not sure if we can detect pickletype, not a + # big deal regardless. + + # Now check to see if stuff seems to be in there. # Migrate + # Make sure result was "migrated" + # Check output to user # Make sure version matches expected # Check all things in database match expected pass From 94eff10deb62f6f52c270d0926e0fa74e44001a7 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 08:58:34 -0600 Subject: [PATCH 30/67] unicode stuff and more bits on the actual migration method --- mediagoblin/tests/test_sql_migrations.py | 303 ++++++++++++++++------- 1 file changed, 214 insertions(+), 89 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 4e0a89d9..57a83ec5 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -50,7 +50,7 @@ class Level1(Base1): __tablename__ = "level" id = Column(Unicode, primary_key=True) - name = Column(Unicode, unique=True, nullable=False, index=True) + name = Column(Unicode)x description = Column(Unicode) exits = Column(PickleType) @@ -244,7 +244,7 @@ def creature_num_legs_to_num_limbs(db_conn): creature_table = Table( 'creature', metadata, autoload=True, autoload_with=db_conn.engine) - creature_table.c.num_legs.alter(name="num_limbs") + creature_table.c.num_legs.alter(name=u"num_limbs") @RegisterMigration(5, FULL_MIGRATIONS) @@ -294,32 +294,32 @@ def _insert_migration1_objects(session): """ # Insert creatures session.add_all( - [Creature1(name='centipede', + [Creature1(name=u'centipede', num_legs=100, is_demon=False), - Creature1(name='wolf', + Creature1(name=u'wolf', num_legs=4, is_demon=False), # don't ask me what a wizardsnake is. - Creature1(name='wizardsnake', + Creature1(name=u'wizardsnake', num_legs=0, is_demon=True)]) # Insert levels session.add_all( - [Level1(id='necroplex', - name='The Necroplex', - description='A complex full of pure deathzone.', + [Level1(id=u'necroplex', + name=u'The Necroplex', + description=u'A complex full of pure deathzone.', exits={ 'deathwell': 'evilstorm', 'portal': 'central_park'}), - Level1(id='evilstorm', - name='Evil Storm', - description='A storm full of pure evil.', + Level1(id=u'evilstorm', + name=u'Evil Storm', + description=u'A storm full of pure evil.', exits={}), # you can't escape the evilstorm - Level1(id='central_park' - name='Central Park, NY, NY', - description="New York's friendly Central Park.", + Level1(id=u'central_park' + name=u'Central Park, NY, NY', + description=u"New York's friendly Central Park.", exits={ 'portal': 'necroplex'})]) @@ -333,71 +333,71 @@ def _insert_migration2_objects(session): # Insert creatures session.add_all( [Creature2( - name='centipede', + name=u'centipede', num_legs=100), Creature2( - name='wolf', + name=u'wolf', num_legs=4, magical_powers = [ CreaturePower2( - name="ice breath", - description="A blast of icy breath!", + name=u"ice breath", + description=u"A blast of icy breath!", hitpower=20), CreaturePower2( - name="death stare", - description="A frightening stare, for sure!", + name=u"death stare", + description=u"A frightening stare, for sure!", hitpower=45)]), Creature2( - name='wizardsnake', + name=u'wizardsnake', num_legs=0, magical_powers=[ CreaturePower2( - name='death_rattle', - description='A rattle... of DEATH!', + name=u'death_rattle', + description=u'A rattle... of DEATH!', hitpower=1000), CreaturePower2( - name='sneaky_stare', - description="The sneakiest stare you've ever seen!" + name=u'sneaky_stare', + description=u"The sneakiest stare you've ever seen!" hitpower=300), CreaturePower2( - name='slithery_smoke', - description="A blast of slithery, slithery smoke.", + name=u'slithery_smoke', + description=u"A blast of slithery, slithery smoke.", hitpower=10), CreaturePower2( - name='treacherous_tremors', - description="The ground shakes beneath footed animals!", + name=u'treacherous_tremors', + description=u"The ground shakes beneath footed animals!", hitpower=0)])]) # Insert levels session.add_all( - [Level2(id='necroplex', - name='The Necroplex', - description='A complex full of pure deathzone.'), - Level2(id='evilstorm', - name='Evil Storm', - description='A storm full of pure evil.', + [Level2(id=u'necroplex', + name=u'The Necroplex', + description=u'A complex full of pure deathzone.'), + Level2(id=u'evilstorm', + name=u'Evil Storm', + description=u'A storm full of pure evil.', exits=[]), # you can't escape the evilstorm - Level2(id='central_park' - name='Central Park, NY, NY', - description="New York's friendly Central Park.")]) + Level2(id=u'central_park' + name=u'Central Park, NY, NY', + description=u"New York's friendly Central Park.")]) # necroplex exits session.add_all( - [LevelExit2(name='deathwell', - from_level='necroplex', - to_level='evilstorm'), - LevelExit2(name='portal', - from_level='necroplex', - to_level='central_park')]) + [LevelExit2(name=u'deathwell', + from_level=u'necroplex', + to_level=u'evilstorm'), + LevelExit2(name=u'portal', + from_level=u'necroplex', + to_level=u'central_park')]) # there are no evilstorm exits because there is no exit from the # evilstorm # central park exits session.add_all( - [LevelExit2(name='portal', - from_level='central_park', - to_level='necroplex')]) + [LevelExit2(name=u'portal', + from_level=u'central_park', + to_level=u'necroplex')]) session.commit() @@ -409,80 +409,80 @@ def _insert_migration3_objects(session): # Insert creatures session.add_all( [Creature3( - name='centipede', + name=u'centipede', num_limbs=100), Creature3( - name='wolf', + name=u'wolf', num_limbs=4, magical_powers = [ CreaturePower3( - name="ice breath", - description="A blast of icy breath!", + name=u"ice breath", + description=u"A blast of icy breath!", hitpower=20.0), CreaturePower3( - name="death stare", - description="A frightening stare, for sure!", + name=u"death stare", + description=u"A frightening stare, for sure!", hitpower=45.0)]), Creature3( - name='wizardsnake', + name=u'wizardsnake', num_limbs=0, magical_powers=[ CreaturePower3( - name='death_rattle', - description='A rattle... of DEATH!', + name=u'death_rattle', + description=u'A rattle... of DEATH!', hitpower=1000.0), CreaturePower3( - name='sneaky_stare', - description="The sneakiest stare you've ever seen!" + name=u'sneaky_stare', + description=u"The sneakiest stare you've ever seen!" hitpower=300.0), CreaturePower3( - name='slithery_smoke', - description="A blast of slithery, slithery smoke.", + name=u'slithery_smoke', + description=u"A blast of slithery, slithery smoke.", hitpower=10.0), CreaturePower3( - name='treacherous_tremors', - description="The ground shakes beneath footed animals!", + name=u'treacherous_tremors', + description=u"The ground shakes beneath footed animals!", hitpower=0.0)])], # annnnnd one more to test a floating point hitpower Creature3( - name='deity', + name=u'deity', numb_limbs=30, magical_powers[ CreaturePower3( - name='smite', - description='Smitten by holy wrath!', + name=u'smite', + description=u'Smitten by holy wrath!', hitpower=9999.9)))) # Insert levels session.add_all( - [Level3(id='necroplex', - name='The Necroplex', - description='A complex full of pure deathzone.'), - Level3(id='evilstorm', - name='Evil Storm', - description='A storm full of pure evil.', + [Level3(id=u'necroplex', + name=u'The Necroplex', + description=u'A complex full of pure deathzone.'), + Level3(id=u'evilstorm', + name=u'Evil Storm', + description=u'A storm full of pure evil.', exits=[]), # you can't escape the evilstorm - Level3(id='central_park' - name='Central Park, NY, NY', - description="New York's friendly Central Park.")]) + Level3(id=u'central_park' + name=u'Central Park, NY, NY', + description=u"New York's friendly Central Park.")]) # necroplex exits session.add_all( - [LevelExit3(name='deathwell', - from_level='necroplex', - to_level='evilstorm'), - LevelExit3(name='portal', - from_level='necroplex', - to_level='central_park')]) + [LevelExit3(name=u'deathwell', + from_level=u'necroplex', + to_level=u'evilstorm'), + LevelExit3(name=u'portal', + from_level=u'necroplex', + to_level=u'central_park')]) # there are no evilstorm exits because there is no exit from the # evilstorm # central park exits session.add_all( - [LevelExit3(name='portal', - from_level='central_park', - to_level='necroplex')]) + [LevelExit3(name=u'portal', + from_level=u'central_park', + to_level=u'necroplex')]) session.commit() @@ -513,27 +513,38 @@ def assert_col_type(column, class): def test_set1_to_set3(): # Create / connect to database + # ---------------------------- + engine, Session = create_test_engine() + # Create tables by migrating on empty initial set + # ----------------------------------------------- + printer = CollectingPrinter migration_manager = MigrationManager( '__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), printer) + # Check latest migration and database current migration assert migration_manager.latest_migration == 0 assert migration_manager.database_current_migration == None result = migration_manager.init_or_migrate() + # Make sure output was "inited" assert result == u'inited' # Check output - assert printer.combined_string == "-> Initializing __main__... done.\n" + assert printer.combined_string == ( + "-> Initializing main mediagoblin tables... done.\n") # Check version in database assert migration_manager.latest_migration == 0 assert migration_manager.database_current_migration == 0 # Install the initial set + # ----------------------- + _insert_migration1_objects(Session()) + # Try to "re-migrate" with same manager settings... nothing should happen migration_manager = MigrationManager( '__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), @@ -568,22 +579,136 @@ def test_set1_to_set3(): autoload=True, autoload_with=db_conn.engine) assert set(level_table.c.keys()) == set( ['id', 'name', 'description', 'exits']) - assert_col_type(level_table.c.id, Integer) + assert_col_type(level_table.c.id, Unicode) + assert level_table.c.id.primary_key is True assert_col_type(level_table.c.name, Unicode) - assert level_table.c.name.nullable is False - assert level_table.c.name.index is True - assert level_table.c.name.unique is True assert_col_type(level_table.c.description, Unicode) # Skipping exits... Not sure if we can detect pickletype, not a # big deal regardless. # Now check to see if stuff seems to be in there. + creature = session.query(Creature1).filter_by( + name=u'centipede').one() + assert creature.num_legs == 100 + assert creature.is_demon == False + + creature = session.query(Creature1).filter_by( + name=u'wolf').one() + assert creature.num_legs == 4 + assert creature.is_demon == False + + creature = session.query(Creature1).filter_by( + name=u'wizardsnake').one() + assert creature.num_legs == 0 + assert creature.is_demon == True + + level = session.query(Level1).filter_by( + id=u'necroplex') + assert level.name == u'The Necroplex' + assert level.description == u'A complex of pure deathzone.' + assert level.exits == { + 'deathwell': 'evilstorm', + 'portal': 'central_park'} + + level = session.query(Level1).filter_by( + id=u'evilstorm') + assert level.name == u'Evil Storm' + assert level.description == u'A storm full of pure evil.' + assert level.exits == {} # You still can't escape the evilstorm! + + level = session.query(Level1).filter_by( + id=u'central_park') + assert level.name == u'Central Park, NY, NY' + assert level.description == u"New York's friendly Central Park." + assert level.exits == { + 'portal': 'necroplex'} + + # Create new migration manager, but make sure the db migration + # isn't said to be updated yet + printer = CollectingPrinter + migration_manager = MigrationManager( + '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), + printer) + + assert migration_manager.latest_migration == 3 + assert migration_manager.database_current_migration == 0 # Migrate + result = migration_manager.init_or_migrate() + # Make sure result was "migrated" - # Check output to user + assert result == u'migrated' + + # TODO: Check output to user + assert printer.combined_string == """\ +-> Updating main mediagoblin tables... + + Running migration 1, "creature_remove_is_demon"... done. + + Running migration 2, "creature_powers_new_table"... done. + + Running migration 3, "level_exits_new_table"... done.""" + # Make sure version matches expected + migration_manager = MigrationManager( + '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), + printer) + assert migration_manager.latest_migration == 3 + assert migration_manager.database_current_migration == 3 + # Check all things in database match expected + + # Check the creature table + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=db_conn.engine) + assert set(creature_table.c.keys()) == set( + ['id', 'name', 'num_legs']) + assert_col_type(creature_table.c.id, Integer) + assert_col_type(creature_table.c.name, Unicode) + assert creature_table.c.name.nullable is False + assert creature_table.c.name.index is True + assert creature_table.c.name.unique is True + assert_col_type(creature_table.c.num_legs, Integer) + assert creature_table.c.num_legs.nullable is False + + # Check the CreaturePower table + creature_power_table = Table( + 'creature_power', metadata, + autoload=True, autoload_with=db_conn.engine) + assert set(creature_power_table.c.keys()) == set( + ['id', 'creature', 'name', 'description', 'hitpower']) + assert_col_type(creature_power_table.c.id, Integer) + assert_col_type(creature_power_table.c.creature, Integer) + assert creature_power_table.c.creature.nullable is False + assert_col_type(creature_power_table.c.name, Unicode) + assert_col_type(creature_power_table.c.description, Unicode) + assert_col_type(creature_power_table.c.hitpower, Integer) + assert creature_power_table.c.hitpower.nullable is False + + # Check the structure of the level table + level_table = Table( + 'level', metadata, + autoload=True, autoload_with=db_conn.engine) + assert set(level_table.c.keys()) == set( + ['id', 'name', 'description']) + assert_col_type(level_table.c.id, Unicode) + assert level_table.c.id.primary_key is True + assert_col_type(level_table.c.name, Unicode) + assert_col_type(level_table.c.description, Unicode) + + # Check the structure of the level_exits table + level_exit_table = Table( + 'level_exit', metadata, + autoload=True, autoload_with=db_conn.engine) + assert set(level_exit_table.c.keys()) == set( + ['id', 'name', 'from_level', 'to_level']) + assert_col_type(level_exit_table.c.id, Integer) + assert_col_type(level_exit_table.c.name, Unicode) + assert_col_type(level_exit_table.c.from_level, Unicode) + assert level_exit_table.c.from_level.nullable is False + assert_col_type(level_exit_table.c.to_level, Unicode) + assert level_exit_table.c.to_level.nullable is False + + # Now check to see if stuff seems to be in there. + pass From 690b51faa7aa8723bb08639f21b60dacd5696fcd Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 13:14:56 -0600 Subject: [PATCH 31/67] Closer to the end of this migration test... --- mediagoblin/tests/test_sql_migrations.py | 72 +++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 57a83ec5..33580432 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -660,7 +660,7 @@ def test_set1_to_set3(): 'creature', metadata, autoload=True, autoload_with=db_conn.engine) assert set(creature_table.c.keys()) == set( - ['id', 'name', 'num_legs']) + ['id', 'name', 'num_limbs']) assert_col_type(creature_table.c.id, Integer) assert_col_type(creature_table.c.name, Unicode) assert creature_table.c.name.nullable is False @@ -680,7 +680,7 @@ def test_set1_to_set3(): assert creature_power_table.c.creature.nullable is False assert_col_type(creature_power_table.c.name, Unicode) assert_col_type(creature_power_table.c.description, Unicode) - assert_col_type(creature_power_table.c.hitpower, Integer) + assert_col_type(creature_power_table.c.hitpower, Float) assert creature_power_table.c.hitpower.nullable is False # Check the structure of the level table @@ -704,10 +704,26 @@ def test_set1_to_set3(): assert_col_type(level_exit_table.c.name, Unicode) assert_col_type(level_exit_table.c.from_level, Unicode) assert level_exit_table.c.from_level.nullable is False + assert level_exit_table.c.from_level.indexed is True assert_col_type(level_exit_table.c.to_level, Unicode) assert level_exit_table.c.to_level.nullable is False + assert level_exit_table.c.to_level.indexed is True # Now check to see if stuff seems to be in there. + creature = session.query(Creature1).filter_by( + name=u'centipede').one() + assert creature.num_legs == 100.0 + assert creature.creature_powers == [] + + creature = session.query(Creature1).filter_by( + name=u'wolf').one() + assert creature.num_legs == 4.0 + assert creature.creature_powers == [] + + creature = session.query(Creature1).filter_by( + name=u'wizardsnake').one() + assert creature.num_legs == 0.0 + assert creature.creature_powers == [] pass @@ -741,4 +757,56 @@ def test_set1_to_set2_to_set3(): # Migrate again # Make sure version matches expected again # Check all things in database match expected again + + ##### Set2 + # creature_table = Table( + # 'creature', metadata, + # autoload=True, autoload_with=db_conn.engine) + # assert set(creature_table.c.keys()) == set( + # ['id', 'name', 'num_legs']) + # assert_col_type(creature_table.c.id, Integer) + # assert_col_type(creature_table.c.name, Unicode) + # assert creature_table.c.name.nullable is False + # assert creature_table.c.name.index is True + # assert creature_table.c.name.unique is True + # assert_col_type(creature_table.c.num_legs, Integer) + # assert creature_table.c.num_legs.nullable is False + + # # Check the CreaturePower table + # creature_power_table = Table( + # 'creature_power', metadata, + # autoload=True, autoload_with=db_conn.engine) + # assert set(creature_power_table.c.keys()) == set( + # ['id', 'creature', 'name', 'description', 'hitpower']) + # assert_col_type(creature_power_table.c.id, Integer) + # assert_col_type(creature_power_table.c.creature, Integer) + # assert creature_power_table.c.creature.nullable is False + # assert_col_type(creature_power_table.c.name, Unicode) + # assert_col_type(creature_power_table.c.description, Unicode) + # assert_col_type(creature_power_table.c.hitpower, Integer) + # assert creature_power_table.c.hitpower.nullable is False + + # # Check the structure of the level table + # level_table = Table( + # 'level', metadata, + # autoload=True, autoload_with=db_conn.engine) + # assert set(level_table.c.keys()) == set( + # ['id', 'name', 'description']) + # assert_col_type(level_table.c.id, Unicode) + # assert level_table.c.id.primary_key is True + # assert_col_type(level_table.c.name, Unicode) + # assert_col_type(level_table.c.description, Unicode) + + # # Check the structure of the level_exits table + # level_exit_table = Table( + # 'level_exit', metadata, + # autoload=True, autoload_with=db_conn.engine) + # assert set(level_exit_table.c.keys()) == set( + # ['id', 'name', 'from_level', 'to_level']) + # assert_col_type(level_exit_table.c.id, Integer) + # assert_col_type(level_exit_table.c.name, Unicode) + # assert_col_type(level_exit_table.c.from_level, Unicode) + # assert level_exit_table.c.from_level.nullable is False + # assert_col_type(level_exit_table.c.to_level, Unicode) + pass From 5de0f4daf5cb38606485e84253706b97f78e97ee Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 14:57:42 -0600 Subject: [PATCH 32/67] More stuff even yet per sql migration stuff! And still not ready! --- mediagoblin/tests/test_sql_migrations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 33580432..4975945c 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -17,7 +17,7 @@ import copy from sqlalchemy import ( - Table, Column, MetaData, Index + Table, Column, MetaData, Index, Integer, Float, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, UniqueConstraint, PickleType) from sqlalchemy.orm import sessionmaker, relationship @@ -712,17 +712,17 @@ def test_set1_to_set3(): # Now check to see if stuff seems to be in there. creature = session.query(Creature1).filter_by( name=u'centipede').one() - assert creature.num_legs == 100.0 + assert creature.num_limbs == 100.0 assert creature.creature_powers == [] creature = session.query(Creature1).filter_by( name=u'wolf').one() - assert creature.num_legs == 4.0 + assert creature.num_limbs == 4.0 assert creature.creature_powers == [] creature = session.query(Creature1).filter_by( name=u'wizardsnake').one() - assert creature.num_legs == 0.0 + assert creature.num_limbs == 0.0 assert creature.creature_powers == [] pass From caed154af020eb0fadefa955d0a15b836433e3a4 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 15:27:45 -0600 Subject: [PATCH 33/67] Fixing some obvious errors caught by pyflakes --- mediagoblin/tests/test_sql_migrations.py | 46 +++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 4975945c..92e99ab1 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -23,6 +23,7 @@ from sqlalchemy import ( from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql import select, insert +from sqlalchemy.sql.util import MigrationManager from migrate import changeset from mediagoblin.db.sql.base import GMGTableBase @@ -50,7 +51,7 @@ class Level1(Base1): __tablename__ = "level" id = Column(Unicode, primary_key=True) - name = Column(Unicode)x + name = Column(Unicode) description = Column(Unicode) exits = Column(PickleType) @@ -231,6 +232,7 @@ class LevelExit3(Base3): SET3_MODELS = [Creature3, CreaturePower3, Level3, LevelExit3] +SET3_MIGRATIONS = FULL_MIGRATIONS @RegisterMigration(4, FULL_MIGRATIONS) @@ -256,8 +258,8 @@ def level_exit_index_from_and_to_level(db_conn): level_exit = Table( 'level_exit', metadata, autoload=True, autoload_with=db_conn.engine) - Index('ix_from_level', level_exit.c.from_level).create(engine) - Index('ix_to_exit', level_exit.c.to_exit).create(engine) + Index('ix_from_level', level_exit.c.from_level).create(db_conn.engine) + Index('ix_to_exit', level_exit.c.to_exit).create(db_conn.engine) @RegisterMigration(6, FULL_MIGRATIONS) @@ -269,7 +271,7 @@ def creature_power_index_creature(db_conn): creature_power = Table( 'creature_power', metadata, autoload=True, autoload_with=db_conn.engine) - Index('ix_creature', creature_power.c.creature).create(engine) + Index('ix_creature', creature_power.c.creature).create(db_conn.engine) @RegisterMigration(7, FULL_MIGRATIONS) @@ -317,7 +319,7 @@ def _insert_migration1_objects(session): name=u'Evil Storm', description=u'A storm full of pure evil.', exits={}), # you can't escape the evilstorm - Level1(id=u'central_park' + Level1(id=u'central_park', name=u'Central Park, NY, NY', description=u"New York's friendly Central Park.", exits={ @@ -357,7 +359,7 @@ def _insert_migration2_objects(session): hitpower=1000), CreaturePower2( name=u'sneaky_stare', - description=u"The sneakiest stare you've ever seen!" + description=u"The sneakiest stare you've ever seen!", hitpower=300), CreaturePower2( name=u'slithery_smoke', @@ -377,7 +379,7 @@ def _insert_migration2_objects(session): name=u'Evil Storm', description=u'A storm full of pure evil.', exits=[]), # you can't escape the evilstorm - Level2(id=u'central_park' + Level2(id=u'central_park', name=u'Central Park, NY, NY', description=u"New York's friendly Central Park.")]) @@ -433,7 +435,7 @@ def _insert_migration3_objects(session): hitpower=1000.0), CreaturePower3( name=u'sneaky_stare', - description=u"The sneakiest stare you've ever seen!" + description=u"The sneakiest stare you've ever seen!", hitpower=300.0), CreaturePower3( name=u'slithery_smoke', @@ -447,11 +449,11 @@ def _insert_migration3_objects(session): Creature3( name=u'deity', numb_limbs=30, - magical_powers[ + magical_powers=[ CreaturePower3( name=u'smite', description=u'Smitten by holy wrath!', - hitpower=9999.9)))) + hitpower=9999.9)])) # Insert levels session.add_all( @@ -462,7 +464,7 @@ def _insert_migration3_objects(session): name=u'Evil Storm', description=u'A storm full of pure evil.', exits=[]), # you can't escape the evilstorm - Level3(id=u'central_park' + Level3(id=u'central_park', name=u'Central Park, NY, NY', description=u"New York's friendly Central Park.")]) @@ -502,13 +504,12 @@ def CollectingPrinter(object): def create_test_engine(): from sqlalchemy import create_engine engine = create_engine('sqlite:///:memory:', echo=False) - sqlalchemy.orm import sessionmaker Session = sessionmaker(bind=engine) return engine, Session -def assert_col_type(column, class): - assert isinstance(column.type, class) +def assert_col_type(column, this_class): + assert isinstance(column.type, this_class) def test_set1_to_set3(): @@ -556,12 +557,12 @@ def test_set1_to_set3(): assert migration_manager.database_current_migration == 0 # Sanity check a few things in the database... - metadata = MetaData(bind=db_conn.engine) + metadata = MetaData(bind=engine) # Check the structure of the creature table creature_table = Table( 'creature', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=engine) assert set(creature_table.c.keys()) == set( ['id', 'name', 'num_legs', 'is_demon']) assert_col_type(creature_table.c.id, Integer) @@ -576,7 +577,7 @@ def test_set1_to_set3(): # Check the structure of the level table level_table = Table( 'level', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=engine) assert set(level_table.c.keys()) == set( ['id', 'name', 'description', 'exits']) assert_col_type(level_table.c.id, Unicode) @@ -587,6 +588,8 @@ def test_set1_to_set3(): # big deal regardless. # Now check to see if stuff seems to be in there. + session = Session() + creature = session.query(Creature1).filter_by( name=u'centipede').one() assert creature.num_legs == 100 @@ -658,7 +661,7 @@ def test_set1_to_set3(): # Check the creature table creature_table = Table( 'creature', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=engine) assert set(creature_table.c.keys()) == set( ['id', 'name', 'num_limbs']) assert_col_type(creature_table.c.id, Integer) @@ -672,7 +675,7 @@ def test_set1_to_set3(): # Check the CreaturePower table creature_power_table = Table( 'creature_power', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=engine) assert set(creature_power_table.c.keys()) == set( ['id', 'creature', 'name', 'description', 'hitpower']) assert_col_type(creature_power_table.c.id, Integer) @@ -686,7 +689,7 @@ def test_set1_to_set3(): # Check the structure of the level table level_table = Table( 'level', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=engine) assert set(level_table.c.keys()) == set( ['id', 'name', 'description']) assert_col_type(level_table.c.id, Unicode) @@ -697,7 +700,7 @@ def test_set1_to_set3(): # Check the structure of the level_exits table level_exit_table = Table( 'level_exit', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=engine) assert set(level_exit_table.c.keys()) == set( ['id', 'name', 'from_level', 'to_level']) assert_col_type(level_exit_table.c.id, Integer) @@ -710,6 +713,7 @@ def test_set1_to_set3(): assert level_exit_table.c.to_level.indexed is True # Now check to see if stuff seems to be in there. + session = Session() creature = session.query(Creature1).filter_by( name=u'centipede').one() assert creature.num_limbs == 100.0 From e8e52b3a0b353849cbdbfa70bb44270ec8211e17 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 16:07:16 -0600 Subject: [PATCH 34/67] test_set1_to_set3() now has appropriate amount of code, even if it doesn't run :) --- mediagoblin/tests/test_sql_migrations.py | 45 +++++++++++++++++++----- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 92e99ab1..1f97b6ce 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -23,10 +23,10 @@ from sqlalchemy import ( from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql import select, insert -from sqlalchemy.sql.util import MigrationManager from migrate import changeset from mediagoblin.db.sql.base import GMGTableBase +from mediagoblin.db.sql.util import MigrationManager, RegisterMigration # This one will get filled with local migrations @@ -512,6 +512,13 @@ def assert_col_type(column, this_class): assert isinstance(column.type, this_class) +def _get_level3_exits(session, level): + return dict( + [(level_exit.name, level_exit.to_level) + for level_exit in + session.query(LevelExit3).filter_by(from_level=level.id)]) + + def test_set1_to_set3(): # Create / connect to database # ---------------------------- @@ -606,7 +613,7 @@ def test_set1_to_set3(): assert creature.is_demon == True level = session.query(Level1).filter_by( - id=u'necroplex') + id=u'necroplex').one() assert level.name == u'The Necroplex' assert level.description == u'A complex of pure deathzone.' assert level.exits == { @@ -614,13 +621,13 @@ def test_set1_to_set3(): 'portal': 'central_park'} level = session.query(Level1).filter_by( - id=u'evilstorm') + id=u'evilstorm').one() assert level.name == u'Evil Storm' assert level.description == u'A storm full of pure evil.' assert level.exits == {} # You still can't escape the evilstorm! level = session.query(Level1).filter_by( - id=u'central_park') + id=u'central_park').one() assert level.name == u'Central Park, NY, NY' assert level.description == u"New York's friendly Central Park." assert level.exits == { @@ -714,22 +721,44 @@ def test_set1_to_set3(): # Now check to see if stuff seems to be in there. session = Session() - creature = session.query(Creature1).filter_by( + creature = session.query(Creature3).filter_by( name=u'centipede').one() assert creature.num_limbs == 100.0 assert creature.creature_powers == [] - creature = session.query(Creature1).filter_by( + creature = session.query(Creature3).filter_by( name=u'wolf').one() assert creature.num_limbs == 4.0 assert creature.creature_powers == [] - creature = session.query(Creature1).filter_by( + creature = session.query(Creature3).filter_by( name=u'wizardsnake').one() assert creature.num_limbs == 0.0 assert creature.creature_powers == [] - pass + level = session.query(Level3).filter_by( + id=u'necroplex').one() + assert level.name == u'The Necroplex' + assert level.description == u'A complex of pure deathzone.' + level_exits = _get_level3_exits(session, level) + assert level_exits == { + u'deathwell': u'evilstorm', + u'portal': u'central_park'} + + level = session.query(Level3).filter_by( + id=u'evilstorm').one() + assert level.name == u'Evil Storm' + assert level.description == u'A storm full of pure evil.' + level_exits = _get_level3_exits(session, level) + assert level_exits == {} # You still can't escape the evilstorm! + + level = session.query(Level3).filter_by( + id=u'central_park').one() + assert level.name == u'Central Park, NY, NY' + assert level.description == u"New York's friendly Central Park." + level_exits = _get_level3_exits(session, level) + assert level_exits == { + 'portal': 'necroplex'} def test_set2_to_set3(): From 9a185731903e750c041061139cc3738859b6acf7 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 16:32:10 -0600 Subject: [PATCH 35/67] Import MigrationData, not MigrationRecord --- mediagoblin/db/sql/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 65976538..e81cf845 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -50,10 +50,10 @@ class MigrationManager(object): self.printer = printer # For convenience - from mediagoblin.db.sql.models import MigrationRecord + from mediagoblin.db.sql.models import MigrationData - self.migration_model = MigrationRecord - self.migration_table = MigrationRecord.__table__ + self.migration_model = MigrationData + self.migration_table = MigrationData.__table__ @property def sorted_migrations(self): From 47616ece50b34ea15c7449345f3528947822adc5 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 16:36:33 -0600 Subject: [PATCH 36/67] Make latest_migration a property --- mediagoblin/db/sql/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index e81cf845..33d2c59b 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -76,6 +76,7 @@ class MigrationManager(object): return self.database.query( self.migration_model).filter_by(name=self.name).first() + @property def latest_migration(self): """ Return a migration number for the latest migration, or 0 if From a5e03db6c2d16581251802bcf1eeae1a7c1f2d9f Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 16:39:08 -0600 Subject: [PATCH 37/67] Migration records are dicts, not lists. Fix SET1_MIGATIONS! --- mediagoblin/tests/test_sql_migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 1f97b6ce..cae29549 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -57,7 +57,7 @@ class Level1(Base1): SET1_MODELS = [Creature1, Level1] -SET1_MIGRATIONS = [] +SET1_MIGRATIONS = {} ####################################################### # Migration set 2: A few migrations and new model From e8ba2223fa2bf79e8998dfbb54794b6a1cb93e63 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 16:40:46 -0600 Subject: [PATCH 38/67] Also switch database_current_migration to a property --- mediagoblin/db/sql/util.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 33d2c59b..d9ce7d2b 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -88,6 +88,7 @@ class MigrationManager(object): # If no migrations have been set, we start at 0. return 0 + @property def database_current_migration(self): """ Return the current migration in the database. @@ -110,7 +111,7 @@ class MigrationManager(object): """ assert self.database_current_migration is not None - db_current_migration = self.database_current_migration() + db_current_migration = self.database_current_migration return [ (migration_number, migration_func) @@ -145,7 +146,7 @@ class MigrationManager(object): """ Print out a dry run of what we would have upgraded. """ - if self.database_current_migration() is None: + if self.database_current_migration is None: self.printer( u'~> Woulda initialized: %s\n' % self.name_for_printing()) return u'inited' @@ -180,7 +181,7 @@ class MigrationManager(object): # Find out what migration number, if any, this database data is at, # and what the latest is. - migration_number = self.database_current_migration() + migration_number = self.database_current_migration # Is this our first time? Is there even a table entry for # this identifier? From 0f3526c601b7848a54808ec0baef832d3baaa8b7 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 16:48:44 -0600 Subject: [PATCH 39/67] magical_powers relationship set on wrong table, fixed --- mediagoblin/tests/test_sql_migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index cae29549..7a49a4a5 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -201,6 +201,7 @@ class Creature3(Base3): id = Column(Integer, primary_key=True) name = Column(Unicode, unique=True, nullable=False, index=True) num_limbs= Column(Integer, nullable=False) + magical_powers = relationship("CreaturePower3") class CreaturePower3(Base3): __tablename__ = "creature_power" @@ -211,7 +212,6 @@ class CreaturePower3(Base3): name = Column(Unicode) description = Column(Unicode) hitpower = Column(Float, nullable=False) - magical_powers = relationship("CreaturePower3") class Level3(Base3): __tablename__ = "level" From f3791a9490f7dca7eaadc8229e31fe7285823d12 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 16:58:58 -0600 Subject: [PATCH 40/67] A few basic fixes to sql/util.py - MigrationRecord to MigrationData, again - If the table doesn't exist, return None for database_current_migration - database.engine -> database.bind --- mediagoblin/db/sql/util.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index d9ce7d2b..604ea19c 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -93,6 +93,10 @@ class MigrationManager(object): """ Return the current migration in the database. """ + # If the table doesn't even exist, return None. + if not self.migration_table.exists(self.database.bind): + return None + return self.migration_data.version def set_current_migration(self, migration_number): @@ -129,7 +133,7 @@ class MigrationManager(object): assert not model.__table__.exists(self.database) self.migration_model.metadata.create_all( - self.database.engine, + self.database.bind, tables=[model.__table__ for model in self.models]) def create_new_migration_record(self): @@ -253,8 +257,8 @@ def assure_migrations_table_setup(db): """ Make sure the migrations table is set up in the database. """ - from mediagoblin.db.sql.models import MigrationRecord + from mediagoblin.db.sql.models import MigrationData - if not MigrationRecord.__table__.exists(db.engine): - MigrationRecord.metadata.create_all( - db, tables=[MigrationRecord.__table__]) + if not MigrationData.__table__.exists(db.bind): + MigrationData.metadata.create_all( + db, tables=[MigrationData.__table__]) From 16d4dce9e94644ba783d7ca8d4f87e75420ce3d9 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:00:39 -0600 Subject: [PATCH 41/67] another db -> db.bind fix. --- mediagoblin/db/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 604ea19c..87234019 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -261,4 +261,4 @@ def assure_migrations_table_setup(db): if not MigrationData.__table__.exists(db.bind): MigrationData.metadata.create_all( - db, tables=[MigrationData.__table__]) + db.bind, tables=[MigrationData.__table__]) From 396f39c3e9e5346b4b2e86f6bcce9cdb0c6ee683 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:05:16 -0600 Subject: [PATCH 42/67] Fix database_current_version for when self.migration_data is None. --- mediagoblin/db/sql/util.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 87234019..cb8fbf0d 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -97,6 +97,10 @@ class MigrationManager(object): if not self.migration_table.exists(self.database.bind): return None + # Also return None if self.migration_data is None. + if self.migration_data is None: + return None + return self.migration_data.version def set_current_migration(self, migration_number): From ef8591fdd09a4910a590b145d781cdd1e1eff0f4 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:06:19 -0600 Subject: [PATCH 43/67] Yet *another* self.database -> self.database.bind fix! --- mediagoblin/db/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index cb8fbf0d..52c57543 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -134,7 +134,7 @@ class MigrationManager(object): # sanity check before we proceed, none of these should be created for model in self.models: # Maybe in the future just print out a "Yikes!" or something? - assert not model.__table__.exists(self.database) + assert not model.__table__.exists(self.database.bind) self.migration_model.metadata.create_all( self.database.bind, From f98be6a65ba10f230547a51ff202b577a76c1556 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:07:47 -0600 Subject: [PATCH 44/67] For clarity, self.database -> self.session. Actually, I'm not even sure *that* is ideal! But better than what we had... --- mediagoblin/db/sql/util.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 52c57543..9487d391 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -33,18 +33,18 @@ class MigrationManager(object): to the latest migrations, etc. """ - def __init__(self, name, models, migration_registry, database, + def __init__(self, name, models, migration_registry, session, printer=_simple_printer): """ Args: - name: identifier of this section of the database - - database: database we're going to migrate + - session: session we're going to migrate - migration_registry: where we should find all migrations to run """ self.name = name self.models = models - self.database = database + self.session = session self.migration_registry = migration_registry self._sorted_migrations = None self.printer = printer @@ -73,7 +73,7 @@ class MigrationManager(object): """ Get the migration row associated with this object, if any. """ - return self.database.query( + return self.session.query( self.migration_model).filter_by(name=self.name).first() @property @@ -94,7 +94,7 @@ class MigrationManager(object): Return the current migration in the database. """ # If the table doesn't even exist, return None. - if not self.migration_table.exists(self.database.bind): + if not self.migration_table.exists(self.session.bind): return None # Also return None if self.migration_data is None. @@ -108,7 +108,7 @@ class MigrationManager(object): Set the migration in the database to migration_number """ self.migration_data = migration_number - self.database.commit() + self.session.commit() def migrations_to_run(self): """ @@ -134,10 +134,10 @@ class MigrationManager(object): # sanity check before we proceed, none of these should be created for model in self.models: # Maybe in the future just print out a "Yikes!" or something? - assert not model.__table__.exists(self.database.bind) + assert not model.__table__.exists(self.session.bind) self.migration_model.metadata.create_all( - self.database.bind, + self.session.bind, tables=[model.__table__ for model in self.models]) def create_new_migration_record(self): @@ -147,8 +147,8 @@ class MigrationManager(object): migration_record = self.migration_model( name=self.name, version=self.latest_migration()) - self.database.add(migration_record) - self.database.commit() + self.session.add(migration_record) + self.session.commit() def dry_run(self): """ @@ -185,7 +185,7 @@ class MigrationManager(object): Returns information about whether or not we initialized ('inited'), migrated ('migrated'), or did nothing (None) """ - assure_migrations_table_setup(self.database) + assure_migrations_table_setup(self.session) # Find out what migration number, if any, this database data is at, # and what the latest is. @@ -217,7 +217,7 @@ class MigrationManager(object): self.printer( u' + Running migration %s, "%s"... ' % ( migration_number, migration_func.func_name)) - migration_func(self.database) + migration_func(self.session) self.printer('done.') return u'migrated' From 9303d47df0ec4779e692529a2791aa2b98e3bc70 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:08:38 -0600 Subject: [PATCH 45/67] self.latest_migration now a property, so we shouldn't __call__ it! --- mediagoblin/db/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 9487d391..a7f5fedf 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -146,7 +146,7 @@ class MigrationManager(object): """ migration_record = self.migration_model( name=self.name, - version=self.latest_migration()) + version=self.latest_migration) self.session.add(migration_record) self.session.commit() From 245e6d83a618f524be0c71d071374ddc6da291f0 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:10:18 -0600 Subject: [PATCH 46/67] CollectingPrinter is a class, not a function! --- mediagoblin/tests/test_sql_migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 7a49a4a5..7dfc4b26 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -489,7 +489,7 @@ def _insert_migration3_objects(session): session.commit() -def CollectingPrinter(object): +class CollectingPrinter(object): def __init__(self): self.collection = [] From ecb4cc89900b897b26fe5f6a1f4c1d94b4a50b6a Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:11:41 -0600 Subject: [PATCH 47/67] printer = CollectingPrinter -> printer = CollectingPrinter() --- mediagoblin/tests/test_sql_migrations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 7dfc4b26..d9d30237 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -528,7 +528,7 @@ def test_set1_to_set3(): # Create tables by migrating on empty initial set # ----------------------------------------------- - printer = CollectingPrinter + printer = CollectingPrinter() migration_manager = MigrationManager( '__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), printer) @@ -635,7 +635,7 @@ def test_set1_to_set3(): # Create new migration manager, but make sure the db migration # isn't said to be updated yet - printer = CollectingPrinter + printer = CollectingPrinter() migration_manager = MigrationManager( '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), printer) From c7fa585bffd1f2cf55455c810ce5c30c8b16d690 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:21:44 -0600 Subject: [PATCH 48/67] assert column type from Unicode -> VARCHAR. SQLAlchemy reflection only so smart ;) --- mediagoblin/tests/test_sql_migrations.py | 58 ++++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index d9d30237..71ea321a 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -19,7 +19,7 @@ import copy from sqlalchemy import ( Table, Column, MetaData, Index, Integer, Float, Unicode, UnicodeText, DateTime, Boolean, - ForeignKey, UniqueConstraint, PickleType) + ForeignKey, UniqueConstraint, PickleType, VARCHAR) from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql import select, insert @@ -573,10 +573,10 @@ def test_set1_to_set3(): assert set(creature_table.c.keys()) == set( ['id', 'name', 'num_legs', 'is_demon']) assert_col_type(creature_table.c.id, Integer) - assert_col_type(creature_table.c.name, Unicode) + assert_col_type(creature_table.c.name, VARCHAR) assert creature_table.c.name.nullable is False - assert creature_table.c.name.index is True - assert creature_table.c.name.unique is True + #assert creature_table.c.name.index is True + #assert creature_table.c.name.unique is True assert_col_type(creature_table.c.num_legs, Integer) assert creature_table.c.num_legs.nullable is False assert_col_type(creature_table.c.is_demon, Boolean) @@ -587,10 +587,10 @@ def test_set1_to_set3(): autoload=True, autoload_with=engine) assert set(level_table.c.keys()) == set( ['id', 'name', 'description', 'exits']) - assert_col_type(level_table.c.id, Unicode) + assert_col_type(level_table.c.id, VARCHAR) assert level_table.c.id.primary_key is True - assert_col_type(level_table.c.name, Unicode) - assert_col_type(level_table.c.description, Unicode) + assert_col_type(level_table.c.name, VARCHAR) + assert_col_type(level_table.c.description, VARCHAR) # Skipping exits... Not sure if we can detect pickletype, not a # big deal regardless. @@ -672,10 +672,10 @@ def test_set1_to_set3(): assert set(creature_table.c.keys()) == set( ['id', 'name', 'num_limbs']) assert_col_type(creature_table.c.id, Integer) - assert_col_type(creature_table.c.name, Unicode) + assert_col_type(creature_table.c.name, VARCHAR) assert creature_table.c.name.nullable is False - assert creature_table.c.name.index is True - assert creature_table.c.name.unique is True + #assert creature_table.c.name.index is True + #assert creature_table.c.name.unique is True assert_col_type(creature_table.c.num_legs, Integer) assert creature_table.c.num_legs.nullable is False @@ -688,8 +688,8 @@ def test_set1_to_set3(): assert_col_type(creature_power_table.c.id, Integer) assert_col_type(creature_power_table.c.creature, Integer) assert creature_power_table.c.creature.nullable is False - assert_col_type(creature_power_table.c.name, Unicode) - assert_col_type(creature_power_table.c.description, Unicode) + assert_col_type(creature_power_table.c.name, VARCHAR) + assert_col_type(creature_power_table.c.description, VARCHAR) assert_col_type(creature_power_table.c.hitpower, Float) assert creature_power_table.c.hitpower.nullable is False @@ -699,10 +699,10 @@ def test_set1_to_set3(): autoload=True, autoload_with=engine) assert set(level_table.c.keys()) == set( ['id', 'name', 'description']) - assert_col_type(level_table.c.id, Unicode) + assert_col_type(level_table.c.id, VARCHAR) assert level_table.c.id.primary_key is True - assert_col_type(level_table.c.name, Unicode) - assert_col_type(level_table.c.description, Unicode) + assert_col_type(level_table.c.name, VARCHAR) + assert_col_type(level_table.c.description, VARCHAR) # Check the structure of the level_exits table level_exit_table = Table( @@ -711,13 +711,13 @@ def test_set1_to_set3(): assert set(level_exit_table.c.keys()) == set( ['id', 'name', 'from_level', 'to_level']) assert_col_type(level_exit_table.c.id, Integer) - assert_col_type(level_exit_table.c.name, Unicode) - assert_col_type(level_exit_table.c.from_level, Unicode) + assert_col_type(level_exit_table.c.name, VARCHAR) + assert_col_type(level_exit_table.c.from_level, VARCHAR) assert level_exit_table.c.from_level.nullable is False - assert level_exit_table.c.from_level.indexed is True - assert_col_type(level_exit_table.c.to_level, Unicode) + #assert level_exit_table.c.from_level.index is True + assert_col_type(level_exit_table.c.to_level, VARCHAR) assert level_exit_table.c.to_level.nullable is False - assert level_exit_table.c.to_level.indexed is True + #assert level_exit_table.c.to_level.index is True # Now check to see if stuff seems to be in there. session = Session() @@ -798,7 +798,7 @@ def test_set1_to_set2_to_set3(): # assert set(creature_table.c.keys()) == set( # ['id', 'name', 'num_legs']) # assert_col_type(creature_table.c.id, Integer) - # assert_col_type(creature_table.c.name, Unicode) + # assert_col_type(creature_table.c.name, VARCHAR) # assert creature_table.c.name.nullable is False # assert creature_table.c.name.index is True # assert creature_table.c.name.unique is True @@ -814,8 +814,8 @@ def test_set1_to_set2_to_set3(): # assert_col_type(creature_power_table.c.id, Integer) # assert_col_type(creature_power_table.c.creature, Integer) # assert creature_power_table.c.creature.nullable is False - # assert_col_type(creature_power_table.c.name, Unicode) - # assert_col_type(creature_power_table.c.description, Unicode) + # assert_col_type(creature_power_table.c.name, VARCHAR) + # assert_col_type(creature_power_table.c.description, VARCHAR) # assert_col_type(creature_power_table.c.hitpower, Integer) # assert creature_power_table.c.hitpower.nullable is False @@ -825,10 +825,10 @@ def test_set1_to_set2_to_set3(): # autoload=True, autoload_with=db_conn.engine) # assert set(level_table.c.keys()) == set( # ['id', 'name', 'description']) - # assert_col_type(level_table.c.id, Unicode) + # assert_col_type(level_table.c.id, VARCHAR) # assert level_table.c.id.primary_key is True - # assert_col_type(level_table.c.name, Unicode) - # assert_col_type(level_table.c.description, Unicode) + # assert_col_type(level_table.c.name, VARCHAR) + # assert_col_type(level_table.c.description, VARCHAR) # # Check the structure of the level_exits table # level_exit_table = Table( @@ -837,9 +837,9 @@ def test_set1_to_set2_to_set3(): # assert set(level_exit_table.c.keys()) == set( # ['id', 'name', 'from_level', 'to_level']) # assert_col_type(level_exit_table.c.id, Integer) - # assert_col_type(level_exit_table.c.name, Unicode) - # assert_col_type(level_exit_table.c.from_level, Unicode) + # assert_col_type(level_exit_table.c.name, VARCHAR) + # assert_col_type(level_exit_table.c.from_level, VARCHAR) # assert level_exit_table.c.from_level.nullable is False - # assert_col_type(level_exit_table.c.to_level, Unicode) + # assert_col_type(level_exit_table.c.to_level, VARCHAR) pass From 505c0db119c9a33a2ee823b4da3859054cce48fd Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:22:29 -0600 Subject: [PATCH 49/67] Fixed the descriptions for the necroplex! --- mediagoblin/tests/test_sql_migrations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 71ea321a..8bb82efd 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -615,7 +615,7 @@ def test_set1_to_set3(): level = session.query(Level1).filter_by( id=u'necroplex').one() assert level.name == u'The Necroplex' - assert level.description == u'A complex of pure deathzone.' + assert level.description == u'A complex full of pure deathzone.' assert level.exits == { 'deathwell': 'evilstorm', 'portal': 'central_park'} @@ -739,7 +739,7 @@ def test_set1_to_set3(): level = session.query(Level3).filter_by( id=u'necroplex').one() assert level.name == u'The Necroplex' - assert level.description == u'A complex of pure deathzone.' + assert level.description == u'A complex full of pure deathzone.' level_exits = _get_level3_exits(session, level) assert level_exits == { u'deathwell': u'evilstorm', From be1077ac44ac4a6ad659e202b93363af2d033f96 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:23:27 -0600 Subject: [PATCH 50/67] Migration manager's current migration should be 3, not 7, after running all migrations! --- mediagoblin/tests/test_sql_migrations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 8bb82efd..92925e64 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -640,7 +640,7 @@ def test_set1_to_set3(): '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), printer) - assert migration_manager.latest_migration == 3 + assert migration_manager.latest_migration == 7 assert migration_manager.database_current_migration == 0 # Migrate @@ -660,8 +660,8 @@ def test_set1_to_set3(): migration_manager = MigrationManager( '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), printer) - assert migration_manager.latest_migration == 3 - assert migration_manager.database_current_migration == 3 + assert migration_manager.latest_migration == 7 + assert migration_manager.database_current_migration == 7 # Check all things in database match expected From bff7098a6c8efa699f7344ee0f5d4e1326f996c2 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:26:23 -0600 Subject: [PATCH 51/67] migrations_to_run here a list, so no reason to call it --- mediagoblin/db/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index a7f5fedf..17c8bf8a 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -213,7 +213,7 @@ class MigrationManager(object): if migrations_to_run: self.printer( u'~> Updating %s:\n' % self.name_for_printing()) - for migration_number, migration_func in migrations_to_run(): + for migration_number, migration_func in migrations_to_run: self.printer( u' + Running migration %s, "%s"... ' % ( migration_number, migration_func.func_name)) From e920b96859b37e5404415bd0573f57b81f7d14d0 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 17:28:25 -0600 Subject: [PATCH 52/67] db_conn.engine -> db_conn.bind --- mediagoblin/tests/test_sql_migrations.py | 42 ++++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 92925e64..f91d449c 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -109,10 +109,10 @@ def creature_remove_is_demon(db_conn): Remove the is_demon field from the creature model. We don't need it! """ - metadata = MetaData(bind=db_conn.engine) + metadata = MetaData(bind=db_conn.bind) creature_table = Table( 'creature', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=db_conn.bind) creature_table.drop_column('is_demon') @@ -123,7 +123,7 @@ def creature_powers_new_table(db_conn): yet though as there wasn't anything that previously held this information """ - metadata = MetaData(bind=db_conn.engine) + metadata = MetaData(bind=db_conn.bind) creature_powers = Table( 'creature_power', metadata, Column('id', Integer, primary_key=True), @@ -132,7 +132,7 @@ def creature_powers_new_table(db_conn): Column('name', Unicode), Column('description', Unicode), Column('hitpower', Integer, nullable=False)) - metadata.create_all(db_conn.engine) + metadata.create_all(db_conn.bind) @RegisterMigration(3, FULL_MIGRATIONS) @@ -143,7 +143,7 @@ def level_exits_new_table(db_conn): """ # First, create the table # ----------------------- - metadata = MetaData(bind=db_conn.engine) + metadata = MetaData(bind=db_conn.bind) level_exits = Table( 'level_exit', metadata, Column('id', Integer, primary_key=True), @@ -152,7 +152,7 @@ def level_exits_new_table(db_conn): Integer, ForeignKey('level.id'), nullable=False), Column('to_level', Integer, ForeignKey('level.id'), nullable=False)) - metadata.create_all(db_conn.engine) + metadata.create_all(db_conn.bind) # And now, convert all the old exit pickles to new level exits # ------------------------------------------------------------ @@ -242,10 +242,10 @@ def creature_num_legs_to_num_limbs(db_conn): specifically. Humans would be 4 here, for instance. So we renamed the column. """ - metadata = MetaData(bind=db_conn.engine) + metadata = MetaData(bind=db_conn.bind) creature_table = Table( 'creature', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=db_conn.bind) creature_table.c.num_legs.alter(name=u"num_limbs") @@ -254,12 +254,12 @@ def level_exit_index_from_and_to_level(db_conn): """ Index the from and to levels of the level exit table. """ - metadata = MetaData(bind=db_conn.engine) + metadata = MetaData(bind=db_conn.bind) level_exit = Table( 'level_exit', metadata, - autoload=True, autoload_with=db_conn.engine) - Index('ix_from_level', level_exit.c.from_level).create(db_conn.engine) - Index('ix_to_exit', level_exit.c.to_exit).create(db_conn.engine) + autoload=True, autoload_with=db_conn.bind) + Index('ix_from_level', level_exit.c.from_level).create(db_conn.bind) + Index('ix_to_exit', level_exit.c.to_exit).create(db_conn.bind) @RegisterMigration(6, FULL_MIGRATIONS) @@ -267,11 +267,11 @@ def creature_power_index_creature(db_conn): """ Index our foreign key relationship to the creatures """ - metadata = MetaData(bind=db_conn.engine) + metadata = MetaData(bind=db_conn.bind) creature_power = Table( 'creature_power', metadata, - autoload=True, autoload_with=db_conn.engine) - Index('ix_creature', creature_power.c.creature).create(db_conn.engine) + autoload=True, autoload_with=db_conn.bind) + Index('ix_creature', creature_power.c.creature).create(db_conn.bind) @RegisterMigration(7, FULL_MIGRATIONS) @@ -283,10 +283,10 @@ def creature_power_hitpower_to_float(db_conn): Turns out we want super precise values of how much hitpower there really is. """ - metadata = MetaData(bind=db_conn.engine) + metadata = MetaData(bind=db_conn.bind) creature_power = Table( 'creature_power', metadata, - autoload=True, autoload_with=db_conn.engine) + autoload=True, autoload_with=db_conn.bind) creature_power.c.hitpower.alter(type=Float) @@ -794,7 +794,7 @@ def test_set1_to_set2_to_set3(): ##### Set2 # creature_table = Table( # 'creature', metadata, - # autoload=True, autoload_with=db_conn.engine) + # autoload=True, autoload_with=db_conn.bind) # assert set(creature_table.c.keys()) == set( # ['id', 'name', 'num_legs']) # assert_col_type(creature_table.c.id, Integer) @@ -808,7 +808,7 @@ def test_set1_to_set2_to_set3(): # # Check the CreaturePower table # creature_power_table = Table( # 'creature_power', metadata, - # autoload=True, autoload_with=db_conn.engine) + # autoload=True, autoload_with=db_conn.bind) # assert set(creature_power_table.c.keys()) == set( # ['id', 'creature', 'name', 'description', 'hitpower']) # assert_col_type(creature_power_table.c.id, Integer) @@ -822,7 +822,7 @@ def test_set1_to_set2_to_set3(): # # Check the structure of the level table # level_table = Table( # 'level', metadata, - # autoload=True, autoload_with=db_conn.engine) + # autoload=True, autoload_with=db_conn.bind) # assert set(level_table.c.keys()) == set( # ['id', 'name', 'description']) # assert_col_type(level_table.c.id, VARCHAR) @@ -833,7 +833,7 @@ def test_set1_to_set2_to_set3(): # # Check the structure of the level_exits table # level_exit_table = Table( # 'level_exit', metadata, - # autoload=True, autoload_with=db_conn.engine) + # autoload=True, autoload_with=db_conn.bind) # assert set(level_exit_table.c.keys()) == set( # ['id', 'name', 'from_level', 'to_level']) # assert_col_type(level_exit_table.c.id, Integer) From adf54363735172badc5984817cc9debd9c39ea0e Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 21:45:05 -0600 Subject: [PATCH 53/67] Update the string outputs to match our tests: newlines, ...->:, etc. --- mediagoblin/db/sql/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 17c8bf8a..731593f6 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -212,13 +212,13 @@ class MigrationManager(object): migrations_to_run = self.migrations_to_run() if migrations_to_run: self.printer( - u'~> Updating %s:\n' % self.name_for_printing()) + u'-> Updating %s:\n' % self.name_for_printing()) for migration_number, migration_func in migrations_to_run: self.printer( u' + Running migration %s, "%s"... ' % ( migration_number, migration_func.func_name)) migration_func(self.session) - self.printer('done.') + self.printer('done.\n') return u'migrated' From 78d17b8055ca19b3af52f97337ccf3c6b8e610b7 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 22:19:03 -0600 Subject: [PATCH 54/67] Excepting that migration 1 doesn't work(!), sqlalchemy migration branch working The reason migration 1 doesn't work, and is commented out, is because of sqlalchemy-migrate not handling certain constraints while dropping binary sqlite columns right. See also: http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=143&thanks=143&ts=1327882242 --- mediagoblin/db/sql/util.py | 7 +- mediagoblin/tests/test_sql_migrations.py | 114 +++++++++++++++-------- 2 files changed, 80 insertions(+), 41 deletions(-) diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py index 731593f6..08602414 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/sql/util.py @@ -103,11 +103,12 @@ class MigrationManager(object): return self.migration_data.version - def set_current_migration(self, migration_number): + def set_current_migration(self, migration_number=None): """ Set the migration in the database to migration_number + (or, the latest available) """ - self.migration_data = migration_number + self.migration_data.version = migration_number or self.latest_migration self.session.commit() def migrations_to_run(self): @@ -206,6 +207,7 @@ class MigrationManager(object): self.create_new_migration_record() self.printer(u"done.\n") + self.set_current_migration() return u'inited' # Run migrations, if appropriate. @@ -220,6 +222,7 @@ class MigrationManager(object): migration_func(self.session) self.printer('done.\n') + self.set_current_migration() return u'migrated' # Otherwise return None. Well it would do this anyway, but diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index f91d449c..e1af2586 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -109,12 +109,13 @@ def creature_remove_is_demon(db_conn): Remove the is_demon field from the creature model. We don't need it! """ - metadata = MetaData(bind=db_conn.bind) - creature_table = Table( - 'creature', metadata, - autoload=True, autoload_with=db_conn.bind) - creature_table.drop_column('is_demon') - + # metadata = MetaData(bind=db_conn.bind) + # creature_table = Table( + # 'creature', metadata, + # autoload=True, autoload_with=db_conn.bind) + # creature_table.drop_column('is_demon') + pass + @RegisterMigration(2, FULL_MIGRATIONS) def creature_powers_new_table(db_conn): @@ -124,6 +125,13 @@ def creature_powers_new_table(db_conn): information """ metadata = MetaData(bind=db_conn.bind) + + # We have to access the creature table so sqlalchemy can make the + # foreign key relationship + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=db_conn.bind) + creature_powers = Table( 'creature_power', metadata, Column('id', Integer, primary_key=True), @@ -144,40 +152,43 @@ def level_exits_new_table(db_conn): # First, create the table # ----------------------- metadata = MetaData(bind=db_conn.bind) - level_exits = Table( - 'level_exit', metadata, - Column('id', Integer, primary_key=True), - Column('name', Unicode), - Column('from_level', - Integer, ForeignKey('level.id'), nullable=False), - Column('to_level', - Integer, ForeignKey('level.id'), nullable=False)) - metadata.create_all(db_conn.bind) - - # And now, convert all the old exit pickles to new level exits - # ------------------------------------------------------------ # Minimal representation of level table. # Not auto-introspecting here because of pickle table. I'm not # sure sqlalchemy can auto-introspect pickle columns. levels = Table( 'level', metadata, - Column('id', Integer, primary_key=True), + Column('id', Unicode, primary_key=True), + Column('name', Unicode), + Column('description', Unicode), Column('exits', PickleType)) + level_exits = Table( + 'level_exit', metadata, + Column('id', Integer, primary_key=True), + Column('name', Unicode), + Column('from_level', + Unicode, ForeignKey('level.id'), nullable=False), + Column('to_level', + Unicode, ForeignKey('level.id'), nullable=False)) + metadata.create_all(db_conn.bind) + + # And now, convert all the old exit pickles to new level exits + # ------------------------------------------------------------ + # query over and insert result = db_conn.execute( select([levels], levels.c.exits!=None)) for level in result: - this_exit = level['exits'] - - # Insert the level exit - db_conn.execute( - level_exits.insert().values( - name=this_exit['name'], - from_level=this_exit['from_level'], - to_level=this_exit['to_level'])) + + for exit_name, to_level in level['exits'].iteritems(): + # Insert the level exit + db_conn.execute( + level_exits.insert().values( + name=exit_name, + from_level=level.id, + to_level=to_level)) # Finally, drop the old level exits pickle table # ---------------------------------------------- @@ -258,8 +269,10 @@ def level_exit_index_from_and_to_level(db_conn): level_exit = Table( 'level_exit', metadata, autoload=True, autoload_with=db_conn.bind) - Index('ix_from_level', level_exit.c.from_level).create(db_conn.bind) - Index('ix_to_exit', level_exit.c.to_exit).create(db_conn.bind) + Index('ix_level_exit_from_level', + level_exit.c.from_level).create(db_conn.bind) + Index('ix_level_exit_to_level', + level_exit.c.to_level).create(db_conn.bind) @RegisterMigration(6, FULL_MIGRATIONS) @@ -271,7 +284,8 @@ def creature_power_index_creature(db_conn): creature_power = Table( 'creature_power', metadata, autoload=True, autoload_with=db_conn.bind) - Index('ix_creature', creature_power.c.creature).create(db_conn.bind) + Index('ix_creature_power_creature', + creature_power.c.creature).create(db_conn.bind) @RegisterMigration(7, FULL_MIGRATIONS) @@ -284,9 +298,23 @@ def creature_power_hitpower_to_float(db_conn): really is. """ metadata = MetaData(bind=db_conn.bind) + + # We have to access the creature table so sqlalchemy can make the + # foreign key relationship + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=db_conn.bind) + creature_power = Table( 'creature_power', metadata, - autoload=True, autoload_with=db_conn.bind) + Column('id', Integer, primary_key=True), + Column('creature', Integer, + ForeignKey('creature.id'), nullable=False, + index=True), + Column('name', Unicode), + Column('description', Unicode), + Column('hitpower', Integer, nullable=False)) + creature_power.c.hitpower.alter(type=Float) @@ -651,10 +679,15 @@ def test_set1_to_set3(): # TODO: Check output to user assert printer.combined_string == """\ --> Updating main mediagoblin tables... +-> Updating main mediagoblin tables: + Running migration 1, "creature_remove_is_demon"... done. + Running migration 2, "creature_powers_new_table"... done. - + Running migration 3, "level_exits_new_table"... done.""" + + Running migration 3, "level_exits_new_table"... done. + + Running migration 4, "creature_num_legs_to_num_limbs"... done. + + Running migration 5, "level_exit_index_from_and_to_level"... done. + + Running migration 6, "creature_power_index_creature"... done. + + Running migration 7, "creature_power_hitpower_to_float"... done. +""" # Make sure version matches expected migration_manager = MigrationManager( @@ -666,18 +699,21 @@ def test_set1_to_set3(): # Check all things in database match expected # Check the creature table + metadata = MetaData(bind=engine) creature_table = Table( 'creature', metadata, autoload=True, autoload_with=engine) + # assert set(creature_table.c.keys()) == set( + # ['id', 'name', 'num_limbs']) assert set(creature_table.c.keys()) == set( - ['id', 'name', 'num_limbs']) + [u'id', 'name', u'num_limbs', u'is_demon']) assert_col_type(creature_table.c.id, Integer) assert_col_type(creature_table.c.name, VARCHAR) assert creature_table.c.name.nullable is False #assert creature_table.c.name.index is True #assert creature_table.c.name.unique is True - assert_col_type(creature_table.c.num_legs, Integer) - assert creature_table.c.num_legs.nullable is False + assert_col_type(creature_table.c.num_limbs, Integer) + assert creature_table.c.num_limbs.nullable is False # Check the CreaturePower table creature_power_table = Table( @@ -724,17 +760,17 @@ def test_set1_to_set3(): creature = session.query(Creature3).filter_by( name=u'centipede').one() assert creature.num_limbs == 100.0 - assert creature.creature_powers == [] + assert creature.magical_powers == [] creature = session.query(Creature3).filter_by( name=u'wolf').one() assert creature.num_limbs == 4.0 - assert creature.creature_powers == [] + assert creature.magical_powers == [] creature = session.query(Creature3).filter_by( name=u'wizardsnake').one() assert creature.num_limbs == 0.0 - assert creature.creature_powers == [] + assert creature.magical_powers == [] level = session.query(Level3).filter_by( id=u'necroplex').one() From 7f3ec607a34e727324397c2bf240f771b12a0455 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 29 Jan 2012 22:19:53 -0600 Subject: [PATCH 55/67] Explained why migration #1 commented out. --- mediagoblin/tests/test_sql_migrations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index e1af2586..1b7fb903 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -109,6 +109,9 @@ def creature_remove_is_demon(db_conn): Remove the is_demon field from the creature model. We don't need it! """ + # :( Commented out 'cuz of: + # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=143&thanks=143&ts=1327882242 + # metadata = MetaData(bind=db_conn.bind) # creature_table = Table( # 'creature', metadata, From e4c3077437042f5466c3f75c85c0d174c272a85a Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 12 Feb 2012 20:36:46 -0600 Subject: [PATCH 56/67] Our javascript is actually AGPLv3+, not LGPL*. Correcting. --- COPYING | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COPYING b/COPYING index f8d93cf6..23e8db70 100644 --- a/COPYING +++ b/COPYING @@ -30,7 +30,7 @@ If not, see . JavaScript files located in the ``mediagoblin/`` directory tree are free software: you can redistribute and/or modify them under the -terms of the GNU Lesser General Public License as published by the +terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. From 53280164e2ebb5856a6f25d14f27439855f99dbb Mon Sep 17 00:00:00 2001 From: Elrond Date: Mon, 13 Feb 2012 23:11:49 +0100 Subject: [PATCH 57/67] 47: Only lowercase host part of email According to most documentation it seems that the local part of an email adress is/can be case sensitive. While the host part is not. So we lowercase only the host part of the given adress. See: http://issues.mediagoblin.org/ticket/47 --- mediagoblin/auth/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py index 9af89c2a..e18469b9 100644 --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@ -60,7 +60,9 @@ def register(request): if request.method == 'POST' and register_form.validate(): # TODO: Make sure the user doesn't exist already username = unicode(request.POST['username'].lower()) - email = unicode(request.POST['email'].lower()) + em_user, em_dom = unicode(request.POST['email']).split("@", 1) + em_dom = em_dom.lower() + email = em_user + "@" + em_dom users_with_username = request.db.User.find( {'username': username}).count() users_with_email = request.db.User.find( From 38816c66078fe679dc4b51b545d15d331712bcb4 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 13 Feb 2012 21:31:11 -0600 Subject: [PATCH 58/67] Revert "Layout overhaul time!" This reverts a whole bunch of commits, fb1dc4f5 thru 92e8ca79, where an experimental new layout was played with. Unfortunately, this layout broke the look and feel of master, even though it was going in the right direction for mobile stuff. Jef said he'll do things in a branch! --- mediagoblin/static/css/base.css | 81 +++------- .../mediagoblin/user_pages/media.html | 141 +++++++++--------- .../templates/mediagoblin/utils/exif.html | 2 +- .../mediagoblin/utils/geolocation_map.html | 2 +- .../templates/mediagoblin/utils/license.html | 2 +- .../mediagoblin/utils/object_gallery.html | 36 +++-- .../mediagoblin/utils/prev_next.html | 46 +++--- .../templates/mediagoblin/utils/tags.html | 9 +- 8 files changed, 146 insertions(+), 173 deletions(-) diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 9c429404..73b07384 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -109,7 +109,7 @@ input, textarea { .container { margin: auto; width: 96%; - max-width: 820px; + max-width: 940px; } header { @@ -151,19 +151,16 @@ footer { } .media_pane { - width: 560px; + width: 640px; margin-left: 0px; margin-right: 10px; float: left; } -img.media_image { - width: 100%; -} - .media_sidebar { - width: 240px; + width: 280px; margin-left: 10px; + margin-right: 0px; float: left; } @@ -255,6 +252,15 @@ text-align: center; float: right; } +textarea#comment_content { + resize: vertical; + width: 634px; + height: 90px; + border: none; + background-color: #f1f1f1; + padding: 3px; +} + .clear { clear: both; display: block; @@ -264,15 +270,6 @@ text-align: center; height: 0; } -h3.sidedata { - border: none; - background-color: #212121; - border-radius: 4px 4px 0 0; - padding: 3px 8px; - margin: 20px 0 5px 0; - font-size: 1em; -} - /* forms */ .form_box,.form_box_xl { @@ -355,18 +352,13 @@ textarea#description, textarea#bio { } textarea#comment_content { - resize: vertical; - width: 100%; + width: 634px; height: 90px; border: none; background-color: #f1f1f1; padding: 3px; } -#form_comment .form_field_input { - padding-right: 6px; -} - /* media galleries */ .media_thumbnail { @@ -377,7 +369,6 @@ textarea#comment_content { margin: 0px 4px 10px 4px; text-align: center; font-size: 0.875em; - list-style: none; } .media_thumbnail a { @@ -389,12 +380,6 @@ textarea#comment_content { h2.media_title { margin-bottom: 0px; - display: inline-block; -} - -p.context { - display: inline-block; - padding-top: 4px; } p.media_specs { @@ -421,21 +406,19 @@ img.media_icon { /* navigation */ -.navigation { - float: right; -} - .navigation_button { width: 135px; - display: inline-block; + display: block; + float: left; text-align: center; background-color: #1d1d1d; border: 1px solid; border-color: #2c2c2c #232323 #1a1a1a; border-radius: 4px; text-decoration: none; - padding: 4px 0 8px; - margin: 0 0 6px + padding: 12px 0 16px; + font-size: 1.4em; + margin: 0 0 20px } .navigation_left { @@ -520,43 +503,23 @@ table.media_panel th { } /* Media queries and other responsivisivity */ - -@media screen and (max-width: 820px) { +@media screen and (max-width: 680px) { .media_pane { width: 100%; margin: 0px; } - - .media_sidebar { - width: 100%; - margin: 0px; - } - img.media_image { width: 100%; } +} +@media screen and (max-width: 960px) { .profile_sidebar { width: 100%; margin: 0px; } - .profile_showcase { width: 100%; margin: 0px; } - - .navigation { - float: none; - } - - .navigation_button { - width: 49%; - float: right; - } - - .navigation_left { - margin-right: 0; - float: left; - } } diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 3e711e92..d2503a4e 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -40,84 +40,67 @@ {% endblock mediagoblin_head %} {% block mediagoblin_content %} - {% trans user_url=request.urlgen( - 'mediagoblin.user_pages.user_home', - user=media.get_uploader.username), - username=media.get_uploader.username -%} -

❖ Browsing media by {{ username }}

- {%- endtrans %} - {% include "mediagoblin/utils/prev_next.html" %} -
- {% block mediagoblin_media %} - {% set display_media = request.app.public_store.file_url( - media.get_display_media(media.media_files)) %} - {# if there's a medium file size, that means the medium size - # isn't the original... so link to the original! - #} - {% if media.media_files.has_key('medium') %} - +
+ -
+ {% endif %} + {% endblock %} +

{{ media.title }}

- {% if request.user and - (media.uploader == request.user._id or - request.user.is_admin) %} - {% set edit_url = request.urlgen('mediagoblin.edit.edit_media', - user= media.get_uploader.username, - media= media._id) %} - {% trans %}Edit{% endtrans %} - {% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete', - user= media.get_uploader.username, - media= media._id) %} - {% trans %}Delete{% endtrans %} - {% endif %} {% autoescape False %}

{{ media.description_html }}

- {% endautoescape %} - {% if media.attachment_files|count %} -

Attachments

- - {% endif %} - {% if app_config['allow_attachments'] - and request.user - and (media.uploader == request.user._id - or request.user.is_admin) %} -

- Add attachment -

- {% endif %} + {% endautoescape %} +

+ {% trans date=media.created.strftime("%Y-%m-%d") -%} + Added on {{ date }}. + {%- endtrans %} + {% if request.user and + (media.uploader == request.user._id or + request.user.is_admin) %} + {% set edit_url = request.urlgen('mediagoblin.edit.edit_media', + user= media.get_uploader.username, + media= media._id) %} + {% trans %}Edit{% endtrans %} + {% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete', + user= media.get_uploader.username, + media= media._id) %} + {% trans %}Delete{% endtrans %} + {% endif %} +

{% if comments %}

+ {% if comments.count()==1 %} + {% trans comment_count=comments.count() -%}{{ comment_count }} comment{%- endtrans %} + {% elif comments.count()>1 %} + {% trans comment_count=comments.count() -%}{{ comment_count }} comments{%- endtrans %} + {% else %} + {% trans %}No comments yet.{% endtrans %} + {% endif %}

@@ -167,11 +150,35 @@ {% endif %}
- {% trans date=media.created.strftime("%Y-%m-%d") -%} -

Added on

-

{{ date }}

- {%- endtrans %} - + {% trans user_url=request.urlgen( + 'mediagoblin.user_pages.user_home', + user=media.get_uploader.username), + username=media.get_uploader.username -%} +

❖ Browsing media by {{ username }}

+ {%- endtrans %} + {% include "mediagoblin/utils/prev_next.html" %} + {% if media.attachment_files|count %} +

Attachments

+ + {% endif %} + {% if app_config['allow_attachments'] + and request.user + and (media.uploader == request.user._id + or request.user.is_admin) %} +

+ Add attachment +

+ {% endif %} {% if media.tags %} {% include "mediagoblin/utils/tags.html" %} {% endif %} diff --git a/mediagoblin/templates/mediagoblin/utils/exif.html b/mediagoblin/templates/mediagoblin/utils/exif.html index bd2e3307..0dd187f2 100644 --- a/mediagoblin/templates/mediagoblin/utils/exif.html +++ b/mediagoblin/templates/mediagoblin/utils/exif.html @@ -20,7 +20,7 @@ {% if media.media_data.has_key('exif') and app_config['exif_visible'] and media.media_data.exif.has_key('useful') %} -

EXIF

+

EXIF

{% for key, tag in media.media_data.exif.useful.items() %} diff --git a/mediagoblin/templates/mediagoblin/utils/geolocation_map.html b/mediagoblin/templates/mediagoblin/utils/geolocation_map.html index 118d0e62..c1909ae5 100644 --- a/mediagoblin/templates/mediagoblin/utils/geolocation_map.html +++ b/mediagoblin/templates/mediagoblin/utils/geolocation_map.html @@ -20,7 +20,7 @@ {% if media.media_data.has_key('gps') and app_config['geolocation_map_visible'] and media.media_data.gps %} -

Location

+

Map

{% set gps = media.media_data.gps %}
diff --git a/mediagoblin/templates/mediagoblin/utils/license.html b/mediagoblin/templates/mediagoblin/utils/license.html index ab157508..2438ed4e 100644 --- a/mediagoblin/templates/mediagoblin/utils/license.html +++ b/mediagoblin/templates/mediagoblin/utils/license.html @@ -17,8 +17,8 @@ #} {% block license_content -%} -

{% trans %}License{% endtrans %}

+ {% trans %}License:{% endtrans %} {% if media.license %} {{ media.get_license_data().abbreviation }} {% else %} diff --git a/mediagoblin/templates/mediagoblin/utils/object_gallery.html b/mediagoblin/templates/mediagoblin/utils/object_gallery.html index 6b5988fb..81506a84 100644 --- a/mediagoblin/templates/mediagoblin/utils/object_gallery.html +++ b/mediagoblin/templates/mediagoblin/utils/object_gallery.html @@ -19,23 +19,29 @@ {% from "mediagoblin/utils/pagination.html" import render_pagination %} {% macro media_grid(request, media_entries, col_number=5) %} -

{% for row in gridify_cursor(media_entries, col_number) %} - {% for entry in row %} - {% set entry_url = entry.url_for_self(request.urlgen) %} -
  • - - - - {% if entry.title %} -
    - {{ entry.title }} - {% endif %} -
  • - {% endfor %} + + {% for entry in row %} + {% set entry_url = entry.url_for_self(request.urlgen) %} + + {% endfor %} + {% endfor %} - + {%- endmacro %} {# diff --git a/mediagoblin/templates/mediagoblin/utils/prev_next.html b/mediagoblin/templates/mediagoblin/utils/prev_next.html index f1175ce4..d0cf3f8c 100644 --- a/mediagoblin/templates/mediagoblin/utils/prev_next.html +++ b/mediagoblin/templates/mediagoblin/utils/prev_next.html @@ -21,28 +21,26 @@ {% set next_entry_url = media.url_to_next(request.urlgen) %} {% if prev_entry_url or next_entry_url %} - + {# There are no previous entries for the very first media entry #} + {% if prev_entry_url %} + + ← {% trans %}newer{% endtrans %} + + {% else %} + {# This is the first entry. display greyed-out 'previous' image #} + + {% endif %} + {# Likewise, this could be the very last media entry #} + {% if next_entry_url %} + + {% trans %}older{% endtrans %} → + + {% else %} + {# This is the last entry. display greyed-out 'next' image #} + + {% endif %} {% endif %} diff --git a/mediagoblin/templates/mediagoblin/utils/tags.html b/mediagoblin/templates/mediagoblin/utils/tags.html index bcf3b5fd..6408102d 100644 --- a/mediagoblin/templates/mediagoblin/utils/tags.html +++ b/mediagoblin/templates/mediagoblin/utils/tags.html @@ -17,17 +17,16 @@ #} {% block tags_content -%} -

    Tagged with

    -

    +

    {% trans %}View more media tagged with{% endtrans %} {% for tag in media.tags %} {% if loop.last %} {# the 'and' should only appear if there is more than one tag #} {% if media.tags|length > 1 %} - · + {% trans %}or{% endtrans %} {% endif %} {{ tag['name'] }} + tag=tag['slug']) }}">{{ tag['name'] }}. {% elif loop.revindex == 2 %} {{ tag['name'] }} · + tag=tag['slug']) }}">{{ tag['name'] }}, {% endif %} {% endfor %}

    From e61ab0998b77eaf18268001fd2d70917c3cd3e37 Mon Sep 17 00:00:00 2001 From: Elrond Date: Sat, 4 Feb 2012 20:55:55 +0100 Subject: [PATCH 59/67] Drop pre-rendered html: User.bio_html After a bit of discussion, we decided to drop the pre-rendered html from the database and render it on the fly. In another step, we will use some proper caching method to cache this stuff. This commit affects the User.bio_html part. --- mediagoblin/db/mixin.py | 5 +++++ mediagoblin/db/mongo/migrations.py | 11 +++++++++++ mediagoblin/db/mongo/models.py | 2 -- mediagoblin/db/sql/convert.py | 2 +- mediagoblin/db/sql/models.py | 1 - mediagoblin/edit/views.py | 2 -- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py index beaff9b0..2ff08722 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -29,6 +29,7 @@ real objects. from mediagoblin.auth import lib as auth_lib from mediagoblin.tools import common, licenses +from mediagoblin.tools.text import cleaned_markdown_conversion class UserMixin(object): @@ -39,6 +40,10 @@ class UserMixin(object): return auth_lib.bcrypt_check_password( password, self.pw_hash) + @property + def bio_html(self): + return cleaned_markdown_conversion(self.bio) + class MediaEntryMixin(object): def get_display_media(self, media_map, diff --git a/mediagoblin/db/mongo/migrations.py b/mediagoblin/db/mongo/migrations.py index 261e21a5..74a810c1 100644 --- a/mediagoblin/db/mongo/migrations.py +++ b/mediagoblin/db/mongo/migrations.py @@ -115,3 +115,14 @@ def mediaentry_add_license(database): Add the 'license' field for entries that don't have it. """ add_table_field(database, 'media_entries', 'license', None) + + +@RegisterMigration(9) +def user_remove_bio_html(database): + """ + Drop bio_html again and calculate things on the fly (and cache) + """ + database['users'].update( + {'bio_html': {'$exists': True}}, + {'$unset': {'bio_html': 1}}, + multi=True) diff --git a/mediagoblin/db/mongo/models.py b/mediagoblin/db/mongo/models.py index 541086bc..c1282f4a 100644 --- a/mediagoblin/db/mongo/models.py +++ b/mediagoblin/db/mongo/models.py @@ -59,7 +59,6 @@ class User(Document, UserMixin): - is_admin: Whether or not this user is an administrator or not. - url: this user's personal webpage/website, if appropriate. - bio: biography of this user (plaintext, in markdown) - - bio_html: biography of the user converted to proper HTML. """ __collection__ = 'users' use_dot_notation = True @@ -76,7 +75,6 @@ class User(Document, UserMixin): 'is_admin': bool, 'url': unicode, 'bio': unicode, # May contain markdown - 'bio_html': unicode, # May contain plaintext, or HTML 'fp_verification_key': unicode, # forgotten password verification key 'fp_token_expire': datetime.datetime, } diff --git a/mediagoblin/db/sql/convert.py b/mediagoblin/db/sql/convert.py index f6575be9..a098927f 100644 --- a/mediagoblin/db/sql/convert.py +++ b/mediagoblin/db/sql/convert.py @@ -56,7 +56,7 @@ def convert_users(mk_db): copy_attrs(entry, new_entry, ('username', 'email', 'created', 'pw_hash', 'email_verified', 'status', 'verification_key', 'is_admin', 'url', - 'bio', 'bio_html', + 'bio', 'fp_verification_key', 'fp_token_expire',)) # new_entry.fp_verification_expire = entry.fp_token_expire diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py index 9d06f79c..3cf4ff40 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/sql/models.py @@ -64,7 +64,6 @@ class User(Base, UserMixin): is_admin = Column(Boolean, default=False, nullable=False) url = Column(Unicode) bio = Column(UnicodeText) # ?? - bio_html = Column(UnicodeText) # ?? fp_verification_key = Column(Unicode) fp_token_expire = Column(DateTime) diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index a7245517..47761f23 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -171,8 +171,6 @@ def edit_profile(request): user.url = unicode(request.POST['url']) user.bio = unicode(request.POST['bio']) - user.bio_html = cleaned_markdown_conversion(user.bio) - user.save() messages.add_message(request, From 1e72e075f8d542f4aa1ad0262f4fd1a5645cc731 Mon Sep 17 00:00:00 2001 From: Elrond Date: Mon, 13 Feb 2012 13:42:59 +0100 Subject: [PATCH 60/67] Drop pre-rendered html: MediaEntry.description_html After a bit of discussion, we decided to drop the pre-rendered html from the database and render it on the fly. In another step, we will use some proper caching method to cache this stuff. This commit affects the MediaEntry.description_html part. --- mediagoblin/db/mixin.py | 8 ++++++++ mediagoblin/db/mongo/migrations.py | 20 ++++++++++++++------ mediagoblin/db/mongo/models.py | 4 ---- mediagoblin/db/sql/convert.py | 2 +- mediagoblin/db/sql/models.py | 1 - mediagoblin/edit/views.py | 5 +---- mediagoblin/listings/views.py | 2 +- mediagoblin/submit/views.py | 4 +--- mediagoblin/user_pages/views.py | 2 +- 9 files changed, 27 insertions(+), 21 deletions(-) diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py index 2ff08722..4e3800ab 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -46,6 +46,14 @@ class UserMixin(object): class MediaEntryMixin(object): + @property + def description_html(self): + """ + Rendered version of the description, run through + Markdown and cleaned with our cleaning tool. + """ + return cleaned_markdown_conversion(self.description) + def get_display_media(self, media_map, fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER): """ diff --git a/mediagoblin/db/mongo/migrations.py b/mediagoblin/db/mongo/migrations.py index 74a810c1..57da7dd8 100644 --- a/mediagoblin/db/mongo/migrations.py +++ b/mediagoblin/db/mongo/migrations.py @@ -29,6 +29,16 @@ def add_table_field(db, table_name, field_name, default_value): multi=True) +def drop_table_field(db, table_name, field_name): + """ + Drop an old field from a table/collection + """ + db[table_name].update( + {field_name: {'$exists': True}}, + {'$unset': {field_name: 1}}, + multi=True) + + # Please see mediagoblin/tests/test_migrations.py for some examples of # basic migrations. @@ -118,11 +128,9 @@ def mediaentry_add_license(database): @RegisterMigration(9) -def user_remove_bio_html(database): +def remove_calculated_html(database): """ - Drop bio_html again and calculate things on the fly (and cache) + Drop bio_html, description_html again and calculate things on the fly (and cache) """ - database['users'].update( - {'bio_html': {'$exists': True}}, - {'$unset': {'bio_html': 1}}, - multi=True) + drop_table_field(database, 'users', 'bio_html') + drop_table_field(database, 'media_entries', 'description_html') diff --git a/mediagoblin/db/mongo/models.py b/mediagoblin/db/mongo/models.py index c1282f4a..db38d502 100644 --- a/mediagoblin/db/mongo/models.py +++ b/mediagoblin/db/mongo/models.py @@ -110,9 +110,6 @@ class MediaEntry(Document, MediaEntryMixin): up with MarkDown for slight fanciness (links, boldness, italics, paragraphs...) - - description_html: Rendered version of the description, run through - Markdown and cleaned with our cleaning tool. - - media_type: What type of media is this? Currently we only support 'image' ;) @@ -177,7 +174,6 @@ class MediaEntry(Document, MediaEntryMixin): 'slug': unicode, 'created': datetime.datetime, 'description': unicode, # May contain markdown/up - 'description_html': unicode, # May contain plaintext, or HTML 'media_type': unicode, 'media_data': dict, # extra data relevant to this media_type 'plugin_data': dict, # plugins can dump stuff here. diff --git a/mediagoblin/db/sql/convert.py b/mediagoblin/db/sql/convert.py index a098927f..9d276866 100644 --- a/mediagoblin/db/sql/convert.py +++ b/mediagoblin/db/sql/convert.py @@ -77,7 +77,7 @@ def convert_media_entries(mk_db): new_entry = MediaEntry() copy_attrs(entry, new_entry, ('title', 'slug', 'created', - 'description', 'description_html', + 'description', 'media_type', 'state', 'license', 'fail_error', 'queued_task_id',)) diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py index 3cf4ff40..72591f4e 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/sql/models.py @@ -85,7 +85,6 @@ class MediaEntry(Base, MediaEntryMixin): slug = Column(Unicode) created = Column(DateTime, nullable=False, default=datetime.datetime.now) description = Column(UnicodeText) # ?? - description_html = Column(UnicodeText) # ?? media_type = Column(Unicode, nullable=False) state = Column(Unicode, default=u'unprocessed', nullable=False) # or use sqlalchemy.types.Enum? diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 47761f23..3df36e8e 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -34,7 +34,7 @@ from mediagoblin.tools.response import render_to_response, redirect from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.text import ( clean_html, convert_to_tag_list_of_dicts, - media_tags_as_string, cleaned_markdown_conversion) + media_tags_as_string) from mediagoblin.tools.licenses import SUPPORTED_LICENSES @@ -72,9 +72,6 @@ def edit_media(request, media): media.tags = convert_to_tag_list_of_dicts( request.POST.get('tags')) - media.description_html = cleaned_markdown_conversion( - media.description) - media.license = unicode(request.POST.get('license', '')) or None media.slug = unicode(request.POST['slug']) diff --git a/mediagoblin/listings/views.py b/mediagoblin/listings/views.py index 48320cb2..ba23fc46 100644 --- a/mediagoblin/listings/views.py +++ b/mediagoblin/listings/views.py @@ -91,7 +91,7 @@ def tag_atom_feed(request): 'type': 'text/html'}]) for entry in cursor: feed.add(entry.get('title'), - entry.get('description_html'), + entry.description_html, id=entry.url_for_self(request.urlgen,qualified=True), content_type='html', author={'name': entry.get_uploader.username, diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py index cdd097ec..845400ca 100644 --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@ -28,7 +28,7 @@ _log = logging.getLogger(__name__) from werkzeug.utils import secure_filename from mediagoblin.db.util import ObjectId -from mediagoblin.tools.text import cleaned_markdown_conversion, convert_to_tag_list_of_dicts +from mediagoblin.tools.text import convert_to_tag_list_of_dicts from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.response import render_to_response, redirect from mediagoblin.decorators import require_active_login @@ -66,8 +66,6 @@ def submit_start(request): or unicode(splitext(filename)[0])) entry.description = unicode(request.POST.get('description')) - entry.description_html = cleaned_markdown_conversion( - entry.description) entry.license = unicode(request.POST.get('license', "")) or None diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 82791278..46435d81 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -250,7 +250,7 @@ def atom_feed(request): for entry in cursor: feed.add(entry.get('title'), - entry.get('description_html'), + entry.description_html, id=entry.url_for_self(request.urlgen,qualified=True), content_type='html', author={ From feba5c5287a7cb4c0ed8f5124ad60a8a291770ad Mon Sep 17 00:00:00 2001 From: Elrond Date: Sat, 18 Feb 2012 11:32:28 +0100 Subject: [PATCH 61/67] Drop pre-rendered html: MediaComment.content_html After a bit of discussion, we decided to drop the pre-rendered html from the database and render it on the fly. In another step, we will use some proper caching method to cache this stuff. This commit affects the MediaComment.content_html part. --- mediagoblin/db/mixin.py | 10 ++++++++++ mediagoblin/db/mongo/migrations.py | 7 ++++++- mediagoblin/db/mongo/models.py | 8 +++----- mediagoblin/db/sql/convert.py | 2 +- mediagoblin/db/sql/models.py | 5 ++--- mediagoblin/user_pages/views.py | 2 -- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py index 4e3800ab..758f7e72 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -104,3 +104,13 @@ class MediaEntryMixin(object): def get_license_data(self): """Return license dict for requested license""" return licenses.SUPPORTED_LICENSES[self.license or ""] + + +class MediaCommentMixin(object): + @property + def content_html(self): + """ + the actual html-rendered version of the comment displayed. + Run through Markdown and the HTML cleaner. + """ + return cleaned_markdown_conversion(self.content) diff --git a/mediagoblin/db/mongo/migrations.py b/mediagoblin/db/mongo/migrations.py index 57da7dd8..59035f3b 100644 --- a/mediagoblin/db/mongo/migrations.py +++ b/mediagoblin/db/mongo/migrations.py @@ -130,7 +130,12 @@ def mediaentry_add_license(database): @RegisterMigration(9) def remove_calculated_html(database): """ - Drop bio_html, description_html again and calculate things on the fly (and cache) + Drop pre-rendered html again and calculate things + on the fly (and cache): + - User.bio_html + - MediaEntry.description_html + - MediaComment.content_html """ drop_table_field(database, 'users', 'bio_html') drop_table_field(database, 'media_entries', 'description_html') + drop_table_field(database, 'media_comments', 'content_html') diff --git a/mediagoblin/db/mongo/models.py b/mediagoblin/db/mongo/models.py index db38d502..57af137d 100644 --- a/mediagoblin/db/mongo/models.py +++ b/mediagoblin/db/mongo/models.py @@ -23,7 +23,7 @@ from mediagoblin.db.mongo import migrations from mediagoblin.db.mongo.util import ASCENDING, DESCENDING, ObjectId from mediagoblin.tools.pagination import Pagination from mediagoblin.tools import url -from mediagoblin.db.mixin import UserMixin, MediaEntryMixin +from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin ################### # Custom validators @@ -251,7 +251,7 @@ class MediaEntry(Document, MediaEntryMixin): return self.db.User.find_one({'_id': self.uploader}) -class MediaComment(Document): +class MediaComment(Document, MediaCommentMixin): """ A comment on a MediaEntry. @@ -260,8 +260,6 @@ class MediaComment(Document): - author: user who posted this comment - created: when the comment was created - content: plaintext (but markdown'able) version of the comment's content. - - content_html: the actual html-rendered version of the comment displayed. - Run through Markdown and the HTML cleaner. """ __collection__ = 'media_comments' @@ -272,7 +270,7 @@ class MediaComment(Document): 'author': ObjectId, 'created': datetime.datetime, 'content': unicode, - 'content_html': unicode} + } required_fields = [ 'media_entry', 'author', 'created', 'content'] diff --git a/mediagoblin/db/sql/convert.py b/mediagoblin/db/sql/convert.py index 9d276866..a46d62ea 100644 --- a/mediagoblin/db/sql/convert.py +++ b/mediagoblin/db/sql/convert.py @@ -133,7 +133,7 @@ def convert_media_comments(mk_db): new_entry = MediaComment() copy_attrs(entry, new_entry, ('created', - 'content', 'content_html',)) + 'content',)) copy_reference_attr(entry, new_entry, "media_entry") copy_reference_attr(entry, new_entry, "author") diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py index 72591f4e..18e1dfd7 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/sql/models.py @@ -31,7 +31,7 @@ from sqlalchemy.ext.associationproxy import association_proxy from mediagoblin.db.sql.extratypes import PathTupleWithSlashes from mediagoblin.db.sql.base import Base, DictReadAttrProxy -from mediagoblin.db.mixin import UserMixin, MediaEntryMixin +from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin class SimpleFieldAlias(object): @@ -218,7 +218,7 @@ class MediaTag(Base): return DictReadAttrProxy(self) -class MediaComment(Base): +class MediaComment(Base, MediaCommentMixin): __tablename__ = "media_comments" id = Column(Integer, primary_key=True) @@ -227,7 +227,6 @@ class MediaComment(Base): author = Column(Integer, ForeignKey('users.id'), nullable=False) created = Column(DateTime, nullable=False, default=datetime.datetime.now) content = Column(UnicodeText, nullable=False) - content_html = Column(UnicodeText) get_author = relationship(User) diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 46435d81..05d07b1b 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -18,7 +18,6 @@ from webob import exc from mediagoblin import messages, mg_globals from mediagoblin.db.util import DESCENDING, ObjectId -from mediagoblin.tools.text import cleaned_markdown_conversion from mediagoblin.tools.response import render_to_response, render_404, redirect from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.pagination import Pagination @@ -146,7 +145,6 @@ def media_post_comment(request, media): comment['media_entry'] = media._id comment['author'] = request.user._id comment['content'] = unicode(request.POST['comment_content']) - comment['content_html'] = cleaned_markdown_conversion(comment['content']) if not comment['content'].strip(): messages.add_message( From cf27accc9e9f2d8eb0b697cb4cea801a388e7993 Mon Sep 17 00:00:00 2001 From: Elrond Date: Sun, 12 Feb 2012 23:49:01 +0100 Subject: [PATCH 62/67] SQL: fail_metadata as JSON encoded field fail_metadata used to be a dict in mongo. So a json encoded field should be okay too. We could use a pickled field instead, which would be more flexible. --- mediagoblin/db/sql/convert.py | 2 +- mediagoblin/db/sql/extratypes.py | 28 +++++++++++++++++++++++++++- mediagoblin/db/sql/models.py | 4 ++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/mediagoblin/db/sql/convert.py b/mediagoblin/db/sql/convert.py index a46d62ea..14a0b911 100644 --- a/mediagoblin/db/sql/convert.py +++ b/mediagoblin/db/sql/convert.py @@ -79,7 +79,7 @@ def convert_media_entries(mk_db): ('title', 'slug', 'created', 'description', 'media_type', 'state', 'license', - 'fail_error', + 'fail_error', 'fail_metadata', 'queued_task_id',)) copy_reference_attr(entry, new_entry, "uploader") diff --git a/mediagoblin/db/sql/extratypes.py b/mediagoblin/db/sql/extratypes.py index 3a594728..8e078f14 100644 --- a/mediagoblin/db/sql/extratypes.py +++ b/mediagoblin/db/sql/extratypes.py @@ -15,7 +15,8 @@ # along with this program. If not, see . -from sqlalchemy.types import TypeDecorator, Unicode +from sqlalchemy.types import TypeDecorator, Unicode, VARCHAR +import json class PathTupleWithSlashes(TypeDecorator): @@ -35,3 +36,28 @@ class PathTupleWithSlashes(TypeDecorator): if value is not None: value = tuple(value.split('/')) return value + + +# The following class and only this one class is in very +# large parts based on example code from sqlalchemy. +# +# The original copyright notice and license follows: +# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php +# +class JSONEncoded(TypeDecorator): + "Represents an immutable structure as a json-encoded string." + + impl = VARCHAR + + def process_bind_param(self, value, dialect): + if value is not None: + value = json.dumps(value) + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = json.loads(value) + return value diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py index 18e1dfd7..a34ff3bc 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/sql/models.py @@ -29,7 +29,7 @@ from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.sql.expression import desc from sqlalchemy.ext.associationproxy import association_proxy -from mediagoblin.db.sql.extratypes import PathTupleWithSlashes +from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded from mediagoblin.db.sql.base import Base, DictReadAttrProxy from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin @@ -91,7 +91,7 @@ class MediaEntry(Base, MediaEntryMixin): license = Column(Unicode) fail_error = Column(Unicode) - fail_metadata = Column(UnicodeText) + fail_metadata = Column(JSONEncoded) queued_media_file = Column(PathTupleWithSlashes) From 6456cefa0ded3d442a7bdd7e84e2ffae248ce065 Mon Sep 17 00:00:00 2001 From: Elrond Date: Sat, 18 Feb 2012 19:22:00 +0100 Subject: [PATCH 63/67] Fix MediaTag __init__ to handle no args Let the init code also handle createing a fresh clean instance without any attrs set. --- mediagoblin/db/sql/models.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py index a34ff3bc..53360f8d 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/sql/models.py @@ -207,10 +207,12 @@ class MediaTag(Base): creator=Tag.find_or_new ) - def __init__(self, name, slug): + def __init__(self, name=None, slug=None): Base.__init__(self) - self.name = name - self.tag_helper = Tag.find_or_new(slug) + if name is not None: + self.name = name + if slug is not None: + self.tag_helper = Tag.find_or_new(slug) @property def dict_view(self): From a45631e3f3791071571da3a59bf6d3ecad42033f Mon Sep 17 00:00:00 2001 From: Elrond Date: Sat, 18 Feb 2012 11:00:13 +0100 Subject: [PATCH 64/67] Start having useful defaults for SQL Mostly this means: Havintg a config_spec.ini that has a local (relative to mediagoblin.ini) sqlite db with the name "mediagoblin.db". Also: - Add to .gitignore - Add a notice to mediagoblin.ini about the db --- .gitignore | 1 + mediagoblin.ini | 4 ++++ mediagoblin/config_spec.ini | 1 + mediagoblin/db/sql/convert.py | 3 +-- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b46ec38a..e3a83822 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ /user_dev/ /paste_local.ini /mediagoblin_local.ini +/mediagoblin.db /server-log.txt # Tests diff --git a/mediagoblin.ini b/mediagoblin.ini index dbde6e51..223f0f4a 100644 --- a/mediagoblin.ini +++ b/mediagoblin.ini @@ -5,6 +5,10 @@ direct_remote_path = /mgoblin_static/ email_sender_address = "notice@mediagoblin.example.org" +## Uncomment and change to your DB's appropiate setting. +## Default is a local sqlite db "mediagoblin.db". +# sql_engine = postgresql:///gmg + # set to false to enable sending notices email_debug_mode = true diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini index 2d410899..2b4ba2f9 100644 --- a/mediagoblin/config_spec.ini +++ b/mediagoblin/config_spec.ini @@ -9,6 +9,7 @@ media_types = string_list(default=list("mediagoblin.media_types.image")) db_host = string() db_name = string(default="mediagoblin") db_port = integer() +sql_engine = string(default="sqlite:///%(here)s/mediagoblin.db") # Where temporary files used in processing and etc are kept workbench_path = string(default="%(here)s/user_dev/media/workbench") diff --git a/mediagoblin/db/sql/convert.py b/mediagoblin/db/sql/convert.py index 14a0b911..36d6fc7f 100644 --- a/mediagoblin/db/sql/convert.py +++ b/mediagoblin/db/sql/convert.py @@ -148,8 +148,7 @@ def convert_media_comments(mk_db): def main(): global_config, app_config = setup_global_and_app_config("mediagoblin.ini") - sql_conn, sql_db = sql_connect({'sql_engine': 'sqlite:///mediagoblin.db'}) - + sql_conn, sql_db = sql_connect(app_config) mk_conn, mk_db = mongo_connect(app_config) Base.metadata.create_all(sql_db.engine) From 3ea1cf36fcfec9a381f2c425e626c4107e7dca43 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sat, 18 Feb 2012 23:19:09 -0600 Subject: [PATCH 65/67] Updates so that dbupdate command works - Various fixes to dbupdate itself - Switching db/sql/migrations.py to use a dict instead of a list - Registering the function --- mediagoblin/db/sql/migrations.py | 2 +- mediagoblin/gmg_commands/__init__.py | 4 +++ mediagoblin/gmg_commands/dbupdate.py | 17 +++++++---- mediagoblin/media_types/ascii/migrations.py | 17 +++++++++++ mediagoblin/media_types/ascii/models.py | 34 +++++++++++++++++++++ mediagoblin/media_types/image/migrations.py | 17 +++++++++++ mediagoblin/media_types/image/models.py | 4 ++- mediagoblin/media_types/video/migrations.py | 17 +++++++++++ mediagoblin/media_types/video/models.py | 22 +++++++++++-- 9 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 mediagoblin/media_types/ascii/migrations.py create mode 100644 mediagoblin/media_types/ascii/models.py create mode 100644 mediagoblin/media_types/image/migrations.py create mode 100644 mediagoblin/media_types/video/migrations.py diff --git a/mediagoblin/db/sql/migrations.py b/mediagoblin/db/sql/migrations.py index 67a02c96..98d0d0aa 100644 --- a/mediagoblin/db/sql/migrations.py +++ b/mediagoblin/db/sql/migrations.py @@ -14,4 +14,4 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -MIGRATIONS = [] +MIGRATIONS = {} diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py index db944b3c..d804376b 100644 --- a/mediagoblin/gmg_commands/__init__.py +++ b/mediagoblin/gmg_commands/__init__.py @@ -53,6 +53,10 @@ SUBCOMMAND_MAP = { 'setup': 'mediagoblin.gmg_commands.import_export:import_export_parse_setup', 'func': 'mediagoblin.gmg_commands.import_export:env_import', 'help': 'Exports the data for this MediaGoblin instance'}, + 'dbupdate': { + 'setup': 'mediagoblin.gmg_commands.dbupdate:dbupdate_parse_setup', + 'func': 'mediagoblin.gmg_commands.dbupdate:dbupdate', + 'help': 'Set up or update the SQL database'}, } diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py index 1fc5121a..27698170 100644 --- a/mediagoblin/gmg_commands/dbupdate.py +++ b/mediagoblin/gmg_commands/dbupdate.py @@ -1,5 +1,5 @@ # GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -14,6 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from sqlalchemy.orm import sessionmaker + from mediagoblin.db.sql.open import setup_connection_and_db_from_config from mediagoblin.db.sql.util import ( MigrationManager, assure_migrations_table_setup) @@ -21,15 +23,19 @@ from mediagoblin.init import setup_global_and_app_config from mediagoblin.tools.common import import_component +def dbupdate_parse_setup(subparser): + pass + + class DatabaseData(object): def __init__(self, name, models, migrations): self.name = name self.models = models self.migrations = migrations - def make_migration_manager(self, db): + def make_migration_manager(self, session): return MigrationManager( - self.name, self.models, self.migrations, db) + self.name, self.models, self.migrations, session) def gather_database_data(media_types): @@ -74,11 +80,10 @@ def dbupdate(args): # Set up the database connection, db = setup_connection_and_db_from_config(app_config) - # If migrations table not made, make it! - assure_migrations_table_setup(db) + Session = sessionmaker(bind=db.engine) # Setup media managers for all dbdata, run init/migrate and print info # For each component, create/migrate tables for dbdata in dbdatas: - migration_manager = dbdata.make_migration_manager(db) + migration_manager = dbdata.make_migration_manager(Session()) migration_manager.init_or_migrate() diff --git a/mediagoblin/media_types/ascii/migrations.py b/mediagoblin/media_types/ascii/migrations.py new file mode 100644 index 00000000..f54c23ea --- /dev/null +++ b/mediagoblin/media_types/ascii/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +MIGRATIONS = {} diff --git a/mediagoblin/media_types/ascii/models.py b/mediagoblin/media_types/ascii/models.py new file mode 100644 index 00000000..324794b9 --- /dev/null +++ b/mediagoblin/media_types/ascii/models.py @@ -0,0 +1,34 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from mediagoblin.db.sql.models import Base + +from sqlalchemy import ( + Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, + UniqueConstraint) + + +class AsciiData(Base): + __tablename__ = "ascii_data" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey('media_entries.id'), nullable=False) + + +DATA_MODEL = AsciiData +MODELS = [AsciiData] diff --git a/mediagoblin/media_types/image/migrations.py b/mediagoblin/media_types/image/migrations.py new file mode 100644 index 00000000..f54c23ea --- /dev/null +++ b/mediagoblin/media_types/image/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +MIGRATIONS = {} diff --git a/mediagoblin/media_types/image/models.py b/mediagoblin/media_types/image/models.py index 96b5cdf2..296eca0a 100644 --- a/mediagoblin/media_types/image/models.py +++ b/mediagoblin/media_types/image/models.py @@ -6,11 +6,13 @@ from sqlalchemy import ( class ImageData(Base): - __tablename__ = "image__data" + __tablename__ = "image_data" id = Column(Integer, primary_key=True) width = Column(Integer) height = Column(Integer) + media_entry = Column( + Integer, ForeignKey('media_entries.id'), nullable=False) DATA_MODEL = ImageData diff --git a/mediagoblin/media_types/video/migrations.py b/mediagoblin/media_types/video/migrations.py new file mode 100644 index 00000000..f54c23ea --- /dev/null +++ b/mediagoblin/media_types/video/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +MIGRATIONS = {} diff --git a/mediagoblin/media_types/video/models.py b/mediagoblin/media_types/video/models.py index c44f1919..741c329b 100644 --- a/mediagoblin/media_types/video/models.py +++ b/mediagoblin/media_types/video/models.py @@ -1,3 +1,20 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + from mediagoblin.db.sql.models import Base from sqlalchemy import ( @@ -6,10 +23,11 @@ from sqlalchemy import ( class VideoData(Base): - __tablename__ = "video__data" + __tablename__ = "video_data" id = Column(Integer, primary_key=True) - integer + media_entry = Column( + Integer, ForeignKey('media_entries.id'), nullable=False) DATA_MODEL = VideoData From d2506eebb45e13616e51230b39f68db9ae91a0c2 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sat, 18 Feb 2012 23:19:41 -0600 Subject: [PATCH 66/67] Commenting out the migrations that don't exist yet --- mediagoblin/tests/test_sql_migrations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py index 1b7fb903..507a7725 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -800,7 +800,7 @@ def test_set1_to_set3(): 'portal': 'necroplex'} -def test_set2_to_set3(): +#def test_set2_to_set3(): # Create / connect to database # Create tables by migrating on empty initial set @@ -811,10 +811,10 @@ def test_set2_to_set3(): # Migrate # Make sure version matches expected # Check all things in database match expected - pass + # pass -def test_set1_to_set2_to_set3(): +#def test_set1_to_set2_to_set3(): # Create / connect to database # Create tables by migrating on empty initial set @@ -881,4 +881,4 @@ def test_set1_to_set2_to_set3(): # assert level_exit_table.c.from_level.nullable is False # assert_col_type(level_exit_table.c.to_level, VARCHAR) - pass + # pass From 99812bbc4a76735824708b341ea283f09a1b423c Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 19 Feb 2012 15:30:38 -0600 Subject: [PATCH 67/67] We now require sqlalchemy and sqlalchemy-migrate --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 9dd8964a..1c7caf96 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,8 @@ setup( 'webtest', 'ConfigObj', 'Markdown', + 'sqlalchemy', + 'sqlalchemy-migrate', ## For now we're expecting that users will install this from ## their package managers. # 'lxml',