diff options
Diffstat (limited to 'mediagoblin/db/base.py')
-rw-r--r-- | mediagoblin/db/base.py | 84 |
1 files changed, 82 insertions, 2 deletions
diff --git a/mediagoblin/db/base.py b/mediagoblin/db/base.py index 6acb0b79..11afbcec 100644 --- a/mediagoblin/db/base.py +++ b/mediagoblin/db/base.py @@ -13,7 +13,8 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - +import six +import copy from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import inspect @@ -24,8 +25,38 @@ if not DISABLE_GLOBALS: from sqlalchemy.orm import scoped_session, sessionmaker Session = scoped_session(sessionmaker()) +class FakeCursor(object): + + def __init__ (self, cursor, mapper, filter=None): + self.cursor = cursor + self.mapper = mapper + self.filter = filter + + def count(self): + return self.cursor.count() + + def __copy__(self): + # Or whatever the function is named to make + # copy.copy happy? + return FakeCursor(copy.copy(self.cursor), self.mapper, self.filter) + + def __iter__(self): + return six.moves.filter(self.filter, six.moves.map(self.mapper, self.cursor)) + + def __getitem__(self, key): + return self.mapper(self.cursor[key]) + + def slice(self, *args, **kwargs): + r = self.cursor.slice(*args, **kwargs) + return list(six.moves.filter(self.filter, six.moves.map(self.mapper, r))) class GMGTableBase(object): + # Deletion types + HARD_DELETE = "hard-deletion" + SOFT_DELETE = "soft-deletion" + + deletion_mode = HARD_DELETE + @property def _session(self): return inspect(self).session @@ -55,7 +86,56 @@ class GMGTableBase(object): else: sess.flush() - def delete(self, commit=True): + def delete(self, commit=True, deletion=None): + """ Delete the object either using soft or hard deletion """ + # Get the setting in the model args if none has been specified. + if deletion is None: + deletion = self.deletion_mode + + # Hand off to the correct deletion function. + if deletion == self.HARD_DELETE: + return self.hard_delete(commit=commit) + elif deletion == self.SOFT_DELETE: + return self.soft_delete(commit=commit) + else: + raise ValueError( + "Invalid deletion mode {mode!r}".format( + mode=deletion + ) + ) + + def soft_delete(self, commit): + # Create the graveyard version of this model + # Importing this here due to cyclic imports + from mediagoblin.db.models import User, Graveyard, GenericModelReference + tombstone = Graveyard() + if getattr(self, "public_id", None) is not None: + tombstone.public_id = self.public_id + + # This is a special case, we don't want to save any actor if the thing + # being soft deleted is a User model as this would create circular + # ForeignKeys + if not isinstance(self, User): + tombstone.actor = User.query.filter_by( + id=self.actor + ).first() + tombstone.object_type = self.object_type + tombstone.save(commit=False) + + # There will be a lot of places where the GenericForeignKey will point + # to the model, we want to remap those to our tombstone. + gmrs = GenericModelReference.query.filter_by( + obj_pk=self.id, + model_type=self.__tablename__ + ).update({ + "obj_pk": tombstone.id, + "model_type": tombstone.__tablename__, + }) + + # Now we can go ahead and actually delete the model. + return self.hard_delete(commit=commit) + + def hard_delete(self, commit): """Delete the object and commit the change immediately by default""" sess = self._session assert sess is not None, "Not going to delete detached %r" % self |