Add the __model_args__ deletion code
This adds the "deleted" fields to the models as well as a new __model_args__ section whcih supports the option for changing the deletion type. Deletion is now handled by choosing a deletion method based on the __model_args__["deletion"] setting, for example if it's soft deletion it will call Model.soft_delete()
This commit is contained in:
parent
0f3bf8d4b1
commit
30852fda1c
@ -13,7 +13,7 @@
|
||||
#
|
||||
# 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 datetime
|
||||
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import inspect
|
||||
@ -26,6 +26,16 @@ if not DISABLE_GLOBALS:
|
||||
|
||||
|
||||
class GMGTableBase(object):
|
||||
# Deletion types
|
||||
HARD_DELETE = "hard-deletion"
|
||||
SOFT_DELETE = "soft-deletion"
|
||||
|
||||
__default_model_args__ = {
|
||||
"deletion": HARD_DELETE,
|
||||
"soft_deletion_field": "deleted",
|
||||
"soft_deletion_retain": ("id",)
|
||||
}
|
||||
|
||||
@property
|
||||
def _session(self):
|
||||
return inspect(self).session
|
||||
@ -37,6 +47,11 @@ class GMGTableBase(object):
|
||||
if not DISABLE_GLOBALS:
|
||||
query = Session.query_property()
|
||||
|
||||
def get_model_arg(self, argument):
|
||||
model_args = self.__default_model_args__.copy()
|
||||
model_args.update(getattr(self, "__model_args__", {}))
|
||||
return model_args.get(argument)
|
||||
|
||||
def get(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
@ -56,6 +71,43 @@ class GMGTableBase(object):
|
||||
sess.flush()
|
||||
|
||||
def delete(self, commit=True):
|
||||
""" Delete the object either using soft or hard deletion """
|
||||
if self.get_model_arg("deletion") == self.HARD_DELETE:
|
||||
return self.hard_delete(commit)
|
||||
elif self.get_model_arg("deletion") == self.SOFT_DELETE:
|
||||
return self.soft_delete(commit)
|
||||
else:
|
||||
raise ValueError(
|
||||
"__model_args__['deletion'] is an invalid value %s" % (
|
||||
self.get_model_arg("deletion")
|
||||
))
|
||||
|
||||
def soft_delete(self, commit):
|
||||
# Find the deletion field
|
||||
field_name = self.get_model_arg("soft_deletion_field")
|
||||
|
||||
# We can't use self.__table__.columns as it only shows it of the
|
||||
# current model and no parent if polymorphism is being used. This
|
||||
# will cause problems for example for the User model.
|
||||
if field_name not in dir(type(self)):
|
||||
raise ValueError("Cannot find soft_deletion_field")
|
||||
|
||||
# Store a value in the deletion field
|
||||
setattr(self, field_name, datetime.datetime.utcnow())
|
||||
|
||||
# Iterate through the fields and remove data
|
||||
retain_fields = self.get_model_arg("soft_deletion_retain")
|
||||
for field_name in self.__table__.columns.keys():
|
||||
# should we skip this field?
|
||||
if field_name in retain_fields:
|
||||
continue
|
||||
|
||||
setattr(self, field_name, None)
|
||||
|
||||
# Save the changes
|
||||
self.save(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
|
||||
|
@ -1820,3 +1820,70 @@ def federation_actor(db):
|
||||
|
||||
# commit changes to db.
|
||||
db.commit()
|
||||
|
||||
@RegisterMigration(39, MIGRATIONS)
|
||||
def federation_soft_deletion(db):
|
||||
""" Introduces soft deletion to models
|
||||
|
||||
This adds a deleted DateTime column which represents if the model is
|
||||
deleted and if so, when. With this change comes changes on the models
|
||||
that soft delete the models rather than the previous hard deletion.
|
||||
"""
|
||||
metadata = MetaData(bind=db.bind)
|
||||
|
||||
# User Model
|
||||
user_table = inspect_table(metadata, "core__users")
|
||||
user_deleted_column = Column(
|
||||
"deleted",
|
||||
DateTime,
|
||||
nullable=True
|
||||
)
|
||||
user_deleted_column.create(user_table)
|
||||
|
||||
# MediaEntry
|
||||
media_entry_table = inspect_table(metadata, "core__media_entries")
|
||||
me_deleted_column = Column(
|
||||
"deleted",
|
||||
DateTime,
|
||||
nullable=True
|
||||
)
|
||||
me_deleted_column.create(media_entry_table)
|
||||
|
||||
# MediaComment
|
||||
media_comment_table = inspect_table(metadata, "core__media_comments")
|
||||
mc_deleted_column = Column(
|
||||
"deleted",
|
||||
DateTime,
|
||||
nullable=True
|
||||
)
|
||||
mc_deleted_column.create(media_comment_table)
|
||||
|
||||
# Collection
|
||||
collection_table = inspect_table(metadata, "core__collections")
|
||||
collection_deleted_column = Column(
|
||||
"deleted",
|
||||
DateTime,
|
||||
nullable=True
|
||||
)
|
||||
collection_deleted_column.create(collection_table)
|
||||
|
||||
# Generator
|
||||
generator_table = inspect_table(metadata, "core__generators")
|
||||
generator_deleted_column = Column(
|
||||
"deleted",
|
||||
DateTime,
|
||||
nullable=True
|
||||
)
|
||||
generator_deleted_column.create(generator_table)
|
||||
|
||||
# Activity
|
||||
activity_table = inspect_table(metadata, "core__activities")
|
||||
activity_deleted_column = Column(
|
||||
"deleted",
|
||||
DateTime,
|
||||
nullable=True
|
||||
)
|
||||
activity_deleted_column.create(activity_table)
|
||||
|
||||
# Commit changes to the db
|
||||
db.commit()
|
||||
|
@ -243,6 +243,7 @@ class User(Base, UserMixin):
|
||||
|
||||
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
deleted = Column(DateTime, nullable=True)
|
||||
|
||||
location = Column(Integer, ForeignKey("core__locations.id"))
|
||||
|
||||
@ -254,6 +255,10 @@ class User(Base, UserMixin):
|
||||
'polymorphic_on': type,
|
||||
}
|
||||
|
||||
__model_args__ = {
|
||||
'deletion': Base.SOFT_DELETE,
|
||||
}
|
||||
|
||||
def delete(self, **kwargs):
|
||||
"""Deletes a User and all related entries/comments/files/..."""
|
||||
# Collections get deleted by relationships.
|
||||
@ -516,6 +521,7 @@ class MediaEntry(Base, MediaEntryMixin):
|
||||
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow,
|
||||
index=True)
|
||||
updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
deleted = Column(DateTime, nullable=True)
|
||||
|
||||
fail_error = Column(Unicode)
|
||||
fail_metadata = Column(JSONEncoded)
|
||||
@ -909,6 +915,7 @@ class MediaComment(Base, MediaCommentMixin):
|
||||
Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
|
||||
actor = Column(Integer, ForeignKey(User.id), nullable=False)
|
||||
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
deleted = Column(DateTime, nullable=True)
|
||||
content = Column(UnicodeText, nullable=False)
|
||||
location = Column(Integer, ForeignKey("core__locations.id"))
|
||||
get_location = relationship("Location", lazy="joined")
|
||||
@ -934,6 +941,10 @@ class MediaComment(Base, MediaCommentMixin):
|
||||
lazy="dynamic",
|
||||
cascade="all, delete-orphan"))
|
||||
|
||||
__model_args__ = {
|
||||
"deletion": Base.SOFT_DELETE,
|
||||
}
|
||||
|
||||
def serialize(self, request):
|
||||
""" Unserialize to python dictionary for API """
|
||||
href = request.urlgen(
|
||||
@ -1010,6 +1021,7 @@ class Collection(Base, CollectionMixin):
|
||||
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow,
|
||||
index=True)
|
||||
updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
deleted = Column(DateTime, nullable=True)
|
||||
description = Column(UnicodeText)
|
||||
actor = Column(Integer, ForeignKey(User.id), nullable=False)
|
||||
num_items = Column(Integer, default=0)
|
||||
@ -1028,9 +1040,13 @@ class Collection(Base, CollectionMixin):
|
||||
backref=backref("collections",
|
||||
cascade="all, delete-orphan"))
|
||||
__table_args__ = (
|
||||
UniqueConstraint('actor', 'slug'),
|
||||
UniqueConstraint("actor", "slug"),
|
||||
{})
|
||||
|
||||
__model_args__ = {
|
||||
"delete": Base.SOFT_DELETE,
|
||||
}
|
||||
|
||||
# These are the types, It's strongly suggested if new ones are invented they
|
||||
# are prefixed to ensure they're unique from other types. Any types used in
|
||||
# the main mediagoblin should be prefixed "core-"
|
||||
@ -1422,8 +1438,13 @@ class Generator(Base):
|
||||
name = Column(Unicode, nullable=False)
|
||||
published = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
deleted = Column(DateTime, nullable=True)
|
||||
object_type = Column(Unicode, nullable=False)
|
||||
|
||||
__model_args__ = {
|
||||
"deletion": Base.SOFT_DELETE,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return "<{klass} {name}>".format(
|
||||
klass=self.__class__.__name__,
|
||||
@ -1464,6 +1485,8 @@ class Activity(Base, ActivityMixin):
|
||||
nullable=False)
|
||||
published = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
deleted = Column(DateTime, nullable=True)
|
||||
|
||||
verb = Column(Unicode, nullable=False)
|
||||
content = Column(Unicode, nullable=True)
|
||||
title = Column(Unicode, nullable=True)
|
||||
@ -1488,6 +1511,10 @@ class Activity(Base, ActivityMixin):
|
||||
cascade="all, delete-orphan"))
|
||||
get_generator = relationship(Generator)
|
||||
|
||||
__model_args__ = {
|
||||
"deletion": Base.SOFT_DELETE,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
if self.content is None:
|
||||
return "<{klass} verb:{verb}>".format(
|
||||
|
Loading…
x
Reference in New Issue
Block a user