Migrate Activity to using the new GenericForeignKey
This commit is contained in:
parent
641ae2f1e1
commit
bfe1e8ce88
@ -36,7 +36,7 @@ from mediagoblin.db.extratypes import JSONEncoded, MutationDict
|
||||
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
|
||||
|
||||
|
||||
@ -910,6 +910,14 @@ class ActivityIntermediator_R0(declarative_base()):
|
||||
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)
|
||||
@ -927,6 +935,7 @@ class Activity_R0(declarative_base()):
|
||||
ForeignKey(ActivityIntermediator_R0.id),
|
||||
nullable=True)
|
||||
|
||||
|
||||
@RegisterMigration(24, MIGRATIONS)
|
||||
def activity_migration(db):
|
||||
"""
|
||||
@ -1250,6 +1259,12 @@ def datetime_to_utc(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"
|
||||
|
||||
@ -1263,4 +1278,128 @@ def create_generic_model_reference(db):
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -53,7 +53,7 @@ class GenericModelReference(Base):
|
||||
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"
|
||||
|
||||
@ -63,8 +63,7 @@ class GenericModelReference(Base):
|
||||
# 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
|
||||
@ -72,8 +71,7 @@ class GenericModelReference(Base):
|
||||
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
|
||||
@ -118,11 +116,31 @@ class GenericForeignKey(ForeignKey):
|
||||
|
||||
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"
|
||||
@ -1414,10 +1432,10 @@ class Activity(Base, ActivityMixin):
|
||||
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,
|
||||
@ -1437,44 +1455,6 @@ class Activity(Base, ActivityMixin):
|
||||
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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user