Fix #5382 - Add migration and fix so tombstones are removed from collections
authorJessica Tallon <tsyesika@tsyesika.se>
Tue, 12 Jan 2016 11:41:21 +0000 (11:41 +0000)
committerJessica Tallon <tsyesika@tsyesika.se>
Fri, 15 Jan 2016 09:20:15 +0000 (09:20 +0000)
When an item is deleted it should be removed from all collections, this commit
makes that happen. It's got two changes:

1. Adds the code so when an object is soft deleted, it's automatically removed from
   all collection items
2. Add a migration to fix this issue for those who have tombstones (Graveyard objects)
   in their collections because of this bug.

This commit requires you to run a migration

mediagoblin/db/base.py
mediagoblin/db/migrations/versions/101510e3a713_removes_graveyard_items_from_.py [new file with mode: 0644]

index 11afbcec5726210c5289e9ef59c41471d71ef819..af2337df040dfbdc55dde3dcf90688d95482593c 100644 (file)
@@ -92,6 +92,31 @@ class GMGTableBase(object):
         if deletion is None:
             deletion = self.deletion_mode
 
+        # If the item is in any collection then it should be removed, this will
+        # cause issues if it isn't. See #5382.
+        # Import here to prevent cyclic imports.
+        from mediagoblin.db.models import CollectionItem, GenericModelReference
+        
+        # Some of the models don't have an "id" field which means they can't be
+        # used with GMR, these models won't be in collections because they
+        # can't be. We can skip all of this.
+        if hasattr(self, "id"):
+            # First find the GenericModelReference for this object
+            gmr = GenericModelReference.query.filter_by(
+                obj_pk=self.id,
+                model_type=self.__tablename__
+            ).first()
+
+            # If there is no gmr then we've got lucky, a GMR is a requirement of
+            # being in a collection.
+            if gmr is not None:
+                items = CollectionItem.query.filter_by(
+                    object_id=gmr.id
+                )
+
+                # Delete any CollectionItems found.
+                items.delete()
+
         # Hand off to the correct deletion function.
         if deletion == self.HARD_DELETE:
             return self.hard_delete(commit=commit)
@@ -132,6 +157,7 @@ class GMGTableBase(object):
             "model_type": tombstone.__tablename__,
         })
 
+        
         # Now we can go ahead and actually delete the model.
         return self.hard_delete(commit=commit)
 
diff --git a/mediagoblin/db/migrations/versions/101510e3a713_removes_graveyard_items_from_.py b/mediagoblin/db/migrations/versions/101510e3a713_removes_graveyard_items_from_.py
new file mode 100644 (file)
index 0000000..d730e80
--- /dev/null
@@ -0,0 +1,84 @@
+"""#5382 Removes graveyard items from collections
+
+Revision ID: 101510e3a713
+Revises: 52bf0ccbedc1
+Create Date: 2016-01-12 10:46:26.486610
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '101510e3a713'
+down_revision = '52bf0ccbedc1'
+
+from alembic import op
+
+import sqlalchemy as sa
+from sqlalchemy.sql import and_
+
+# Create the tables to query
+gmr_table = sa.Table(
+    "core__generic_model_reference",
+    sa.MetaData(),
+    sa.Column("id", sa.Integer, primary_key=True),
+    sa.Column("obj_pk", sa.Integer),
+    sa.Column("model_type", sa.Unicode)
+)
+
+graveyard_table = sa.Table(
+    "core__graveyard",
+    sa.MetaData(),
+    sa.Column("id", sa.Integer, primary_key=True),
+    sa.Column("public_id", sa.Unicode, unique=True),
+    sa.Column("deleted", sa.DateTime, nullable=False),
+    sa.Column("object_type", sa.Unicode, nullable=False),
+    sa.Column("actor_id", sa.Integer)
+)
+
+collection_items_table = sa.Table(
+    "core__collection_items",
+    sa.MetaData(),
+    sa.Column("id", sa.Integer, primary_key=True),
+    sa.Column("collection", sa.Integer, nullable=False),
+    sa.Column("note", sa.UnicodeText),
+    sa.Column("added", sa.DateTime, nullable=False),
+    sa.Column("position", sa.Integer),
+    sa.Column("object_id", sa.Integer, index=True)
+) 
+
+def upgrade():
+    """
+    The problem is deletions are occuring and as we expect the
+    GenericModelReference objects are being updated to point to the tombstone
+    object. The issue is that collections now contain deleted items, this
+    causes problems when it comes to rendering them for example.
+
+    This migration is to remove any Graveyard objects (tombstones) from any
+    Collection.
+    """
+    connection = op.get_bind()
+
+    for tombstone in connection.execute(graveyard_table.select()):
+        # Get GMR for tombstone
+        gmr = connection.execute(gmr_table.select().where(and_(
+            gmr_table.c.obj_pk == tombstone.id,
+            gmr_table.c.model_type == "core__graveyard"
+        ))).first()
+
+        # If there is no GMR, we're all good as it's required to be in a
+        # collection
+        if gmr is None:
+            continue
+
+        # Delete all the CollectionItem objects for this GMR
+        connection.execute(collection_items_table.delete().where(
+            collection_items_table.c.object_id == gmr.id
+        ))
+
+
+def downgrade():
+    """
+    Nothing to do here, the migration just deletes objects from collections.
+    There are no schema changes that have occured. This can be reverted without
+    any problems.
+    """
+    pass