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 (
|
from mediagoblin.db.migration_tools import (
|
||||||
RegisterMigration, inspect_table, replace_table_hack)
|
RegisterMigration, inspect_table, replace_table_hack)
|
||||||
from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User,
|
from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User,
|
||||||
Privilege, Generator)
|
Privilege, Generator, GenericForeignKey)
|
||||||
from mediagoblin.db.extratypes import JSONEncoded, MutationDict
|
from mediagoblin.db.extratypes import JSONEncoded, MutationDict
|
||||||
|
|
||||||
|
|
||||||
@ -910,6 +910,14 @@ class ActivityIntermediator_R0(declarative_base()):
|
|||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
type = Column(Unicode, nullable=False)
|
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()):
|
class Activity_R0(declarative_base()):
|
||||||
__tablename__ = "core__activities"
|
__tablename__ = "core__activities"
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
@ -927,6 +935,7 @@ class Activity_R0(declarative_base()):
|
|||||||
ForeignKey(ActivityIntermediator_R0.id),
|
ForeignKey(ActivityIntermediator_R0.id),
|
||||||
nullable=True)
|
nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@RegisterMigration(24, MIGRATIONS)
|
@RegisterMigration(24, MIGRATIONS)
|
||||||
def activity_migration(db):
|
def activity_migration(db):
|
||||||
"""
|
"""
|
||||||
@ -1250,6 +1259,12 @@ def datetime_to_utc(db):
|
|||||||
# Commit this to the database
|
# Commit this to the database
|
||||||
db.commit()
|
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()):
|
class GenericModelReference_V0(declarative_base()):
|
||||||
__tablename__ = "core__generic_model_reference"
|
__tablename__ = "core__generic_model_reference"
|
||||||
|
|
||||||
@ -1263,4 +1278,128 @@ def create_generic_model_reference(db):
|
|||||||
GenericModelReference_V0.__table__.create(db.bind)
|
GenericModelReference_V0.__table__.create(db.bind)
|
||||||
db.commit()
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,8 +63,7 @@ class GenericModelReference(Base):
|
|||||||
# This will be the tablename of the model
|
# This will be the tablename of the model
|
||||||
model_type = Column(Unicode, nullable=False)
|
model_type = Column(Unicode, nullable=False)
|
||||||
|
|
||||||
@property
|
def get_object(self):
|
||||||
def get(self):
|
|
||||||
# This can happen if it's yet to be saved
|
# This can happen if it's yet to be saved
|
||||||
if self.model_type is None or self.obj_pk is None:
|
if self.model_type is None or self.obj_pk is None:
|
||||||
return None
|
return None
|
||||||
@ -72,8 +71,7 @@ class GenericModelReference(Base):
|
|||||||
model = self._get_model_from_type(self.model_type)
|
model = self._get_model_from_type(self.model_type)
|
||||||
return model.query.filter_by(id=self.obj_pk)
|
return model.query.filter_by(id=self.obj_pk)
|
||||||
|
|
||||||
@property
|
def set_object(self, obj):
|
||||||
def set(self, obj):
|
|
||||||
model = obj.__class__
|
model = obj.__class__
|
||||||
|
|
||||||
# Check we've been given a object
|
# Check we've been given a object
|
||||||
@ -118,11 +116,31 @@ class GenericForeignKey(ForeignKey):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(GenericForeignKey, self).__init__(
|
super(GenericForeignKey, self).__init__(
|
||||||
"core__generic_model_reference.id",
|
GenericModelReference.id,
|
||||||
*args,
|
*args,
|
||||||
**kwargs
|
**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):
|
class Location(Base):
|
||||||
""" Represents a physical location """
|
""" Represents a physical location """
|
||||||
__tablename__ = "core__locations"
|
__tablename__ = "core__locations"
|
||||||
@ -1414,10 +1432,10 @@ class Activity(Base, ActivityMixin):
|
|||||||
ForeignKey("core__generators.id"),
|
ForeignKey("core__generators.id"),
|
||||||
nullable=True)
|
nullable=True)
|
||||||
object = Column(Integer,
|
object = Column(Integer,
|
||||||
ForeignKey("core__activity_intermediators.id"),
|
GenericForeignKey(),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
target = Column(Integer,
|
target = Column(Integer,
|
||||||
ForeignKey("core__activity_intermediators.id"),
|
GenericForeignKey(),
|
||||||
nullable=True)
|
nullable=True)
|
||||||
|
|
||||||
get_actor = relationship(User,
|
get_actor = relationship(User,
|
||||||
@ -1437,44 +1455,6 @@ class Activity(Base, ActivityMixin):
|
|||||||
content=self.content
|
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):
|
def save(self, set_updated=True, *args, **kwargs):
|
||||||
if set_updated:
|
if set_updated:
|
||||||
self.updated = datetime.datetime.now()
|
self.updated = datetime.datetime.now()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user