from mediagoblin.db.migration_tools import (
RegisterMigration, inspect_table, replace_table_hack)
from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User,
- Privilege, Generator)
+ Privilege, Generator, GenericForeignKey)
from mediagoblin.db.extratypes import JSONEncoded, MutationDict
id = Column(Integer, primary_key=True)
type = Column(Unicode, nullable=False)
+ # These are needed for migration 29
+ TYPES = {
+ "user": User,
+ "media": MediaEntry,
+ "comment": MediaComment,
+ "collection": Collection,
+ }
+
class Activity_R0(declarative_base()):
__tablename__ = "core__activities"
id = Column(Integer, primary_key=True)
ForeignKey(ActivityIntermediator_R0.id),
nullable=True)
+
@RegisterMigration(24, MIGRATIONS)
def activity_migration(db):
"""
# Commit this to the database
db.commit()
+##
+# Migrations to handle migrating from activity specific foreign key to the
+# new GenericForeignKey implementations. They have been split up to improve
+# readability and minimise errors
+##
+
class GenericModelReference_V0(declarative_base()):
__tablename__ = "core__generic_model_reference"
GenericModelReference_V0.__table__.create(db.bind)
db.commit()
+@RegisterMigration(28, MIGRATIONS)
+def add_foreign_key_fields(db):
+ """
+ Add the fields for GenericForeignKey to the model under temporary name,
+ this is so that later a data migration can occur. They will be renamed to
+ the origional names.
+ """
+ metadata = MetaData(bind=db.bind)
+ activity_table = inspect_table(metadata, "core__activities")
+
+ # Create column and add to model.
+ object_column = Column("temp_object", Integer, GenericForeignKey())
+ object_column.create(activity_table)
+
+ target_column = Column("temp_target", Integer, GenericForeignKey())
+ target_column.create(activity_table)
+
+ # Commit this to the database
+ db.commit()
+
+@RegisterMigration(29, MIGRATIONS)
+def migrate_data_foreign_keys(db):
+ """
+ This will migrate the data from the old object and target attributes which
+ use the old ActivityIntermediator to the new temparay fields which use the
+ new GenericForeignKey.
+ """
+ metadata = MetaData(bind=db.bind)
+ activity_table = inspect_table(metadata, "core__activities")
+ ai_table = inspect_table(metadata, "core__activity_intermediators")
+ gmr_table = inspect_table(metadata, "core__generic_model_reference")
+
+
+ # Iterate through all activities doing the migration per activity.
+ for activity in db.execute(activity_table.select()):
+ # First do the "Activity.object" migration to "Activity.temp_object"
+ # I need to get the object from the Activity, I can't use the old
+ # Activity.get_object as we're in a migration.
+ object_ai = db.execute(ai_table.select(
+ ai_table.c.id==activity.object
+ )).first()
+
+ object_ai_type = ActivityIntermediator_R0.TYPES[object_ai.type]
+ object_ai_table = inspect_table(metadata, object_ai_type.__tablename__)
+
+ activity_object = db.execute(object_ai_table.select(
+ object_ai_table.c.activity==object_ai.id
+ )).first()
+
+ # now we need to create the GenericModelReference
+ object_gmr = db.execute(gmr_table.insert().values(
+ obj_pk=activity_object.id,
+ model_type=object_ai_type.__tablename__
+ ))
+
+ # Now set the ID of the GenericModelReference in the GenericForignKey
+ db.execute(activity_table.update().values(
+ temp_object=object_gmr.inserted_primary_key[0]
+ ))
+
+ # Now do same process for "Activity.target" to "Activity.temp_target"
+ # not all Activities have a target so if it doesn't just skip the rest
+ # of this.
+ if activity.target is None:
+ continue
+
+ # Now get the target for the activity.
+ target_ai = db.execute(ai_table.select(
+ ai_table.c.id==activity.target
+ )).first()
+
+ target_ai_type = ActivityIntermediator_R0.TYPES[target_ai.type]
+ target_ai_table = inspect_table(metadata, target_ai_type.__tablename__)
+
+ activity_target = db.execute(target_ai_table.select(
+ target_ai_table.c.activity==target_ai.id
+ )).first()
+
+ # We now want to create the new target GenericModelReference
+ target_gmr = db.execute(gmr_table.insert().values(
+ obj_pk=activity_target.id,
+ model_type=target_ai_type.__tablename__
+ ))
+
+ # Now set the ID of the GenericModelReference in the GenericForignKey
+ db.execute(activity_table.update().values(
+ temp_object=target_gmr.inserted_primary_key[0]
+ ))
+
+ # Commit to the database.
+ db.commit()
+
+@RegisterMigration(30, MIGRATIONS)
+def rename_and_remove_object_and_target(db):
+ """
+ Renames the new Activity.object and Activity.target fields and removes the
+ old ones.
+ """
+ metadata = MetaData(bind=db.bind)
+ activity_table = inspect_table(metadata, "core__activities")
+
+ # Firstly lets remove the old fields.
+ old_object_column = activity_table.columns["object"]
+ old_target_column = activity_table.columns["target"]
+
+ # Drop the tables.
+ old_object_column.drop()
+ old_target_column.drop()
+
+ # Now get the new columns.
+ new_object_column = activity_table.columns["temp_object"]
+ new_target_column = activity_table.columns["temp_target"]
+
+ # rename them to the old names.
+ new_object_column.alter(name="object")
+ new_target_column.alter(name="target")
+
+ # Commit the changes to the database.
+ db.commit()
+
+
+
+
+
Represents a relationship to any model that is defined with a integer pk
NB: This model should not be used directly but through the GenericForeignKey
- field provided.
+ field provided.
"""
__tablename__ = "core__generic_model_reference"
# This will be the tablename of the model
model_type = Column(Unicode, nullable=False)
- @property
- def get(self):
+ def get_object(self):
# This can happen if it's yet to be saved
if self.model_type is None or self.obj_pk is None:
return None
model = self._get_model_from_type(self.model_type)
return model.query.filter_by(id=self.obj_pk)
- @property
- def set(self, obj):
+ def set_object(self, obj):
model = obj.__class__
# Check we've been given a object
def __init__(self, *args, **kwargs):
super(GenericForeignKey, self).__init__(
- "core__generic_model_reference.id",
+ GenericModelReference.id,
*args,
**kwargs
)
+ def __get__(self, *args, **kwargs):
+ """ Looks up GenericModelReference and model for field """
+ # Find the value of the foreign key.
+ ref = super(self, GenericForeignKey).__get__(*args, **kwargs)
+
+ # If this hasn't been set yet return None
+ if ref is None:
+ return None
+
+ # Look up the GenericModelReference for this.
+ gmr = GenericModelReference.query.filter_by(id=ref).first()
+
+ # If it's set to something invalid (i.e. no GMR exists return None)
+ if gmr is None:
+ return None
+
+ # Ask the GMR for the corresponding model
+ return gmr.get_object()
+
+
class Location(Base):
""" Represents a physical location """
__tablename__ = "core__locations"
ForeignKey("core__generators.id"),
nullable=True)
object = Column(Integer,
- ForeignKey("core__activity_intermediators.id"),
+ GenericForeignKey(),
nullable=False)
target = Column(Integer,
- ForeignKey("core__activity_intermediators.id"),
+ GenericForeignKey(),
nullable=True)
get_actor = relationship(User,
content=self.content
)
- @property
- def get_object(self):
- if self.object is None:
- return None
-
- ai = ActivityIntermediator.query.filter_by(id=self.object).first()
- return ai.get()
-
- def set_object(self, obj):
- self.object = self._set_model(obj)
-
- @property
- def get_target(self):
- if self.target is None:
- return None
-
- ai = ActivityIntermediator.query.filter_by(id=self.target).first()
- return ai.get()
-
- def set_target(self, obj):
- self.target = self._set_model(obj)
-
- def _set_model(self, obj):
- # Firstly can we set obj
- if not hasattr(obj, "activity"):
- raise ValueError(
- "{0!r} is unable to be set on activity".format(obj))
-
- if obj.activity is None:
- # We need to create a new AI
- ai = ActivityIntermediator()
- ai.set(obj)
- ai.save()
- return ai.id
-
- # Okay we should have an existing AI
- return ActivityIntermediator.query.filter_by(id=obj.activity).first().id
-
def save(self, set_updated=True, *args, **kwargs):
if set_updated:
self.updated = datetime.datetime.now()