1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from sqlalchemy
.ext
.declarative
import declarative_base
20 from sqlalchemy
import inspect
22 from mediagoblin
.tools
.transition
import DISABLE_GLOBALS
24 if not DISABLE_GLOBALS
:
25 from sqlalchemy
.orm
import scoped_session
, sessionmaker
26 Session
= scoped_session(sessionmaker())
28 class FakeCursor(object):
30 def __init__ (self
, cursor
, mapper
, filter=None):
36 return self
.cursor
.count()
39 # Or whatever the function is named to make
41 return FakeCursor(copy
.copy(self
.cursor
), self
.mapper
, self
.filter)
44 return six
.moves
.filter(self
.filter, six
.moves
.map(self
.mapper
, self
.cursor
))
46 def __getitem__(self
, key
):
47 return self
.mapper(self
.cursor
[key
])
49 def slice(self
, *args
, **kwargs
):
50 r
= self
.cursor
.slice(*args
, **kwargs
)
51 return list(six
.moves
.filter(self
.filter, six
.moves
.map(self
.mapper
, r
)))
53 class GMGTableBase(object):
55 HARD_DELETE
= "hard-deletion"
56 SOFT_DELETE
= "soft-deletion"
58 deletion_mode
= HARD_DELETE
62 return inspect(self
).session
66 return self
._session
.bind
.app
68 if not DISABLE_GLOBALS
:
69 query
= Session
.query_property()
72 return getattr(self
, key
)
74 def setdefault(self
, key
, defaultvalue
):
75 # The key *has* to exist on sql.
76 return getattr(self
, key
)
78 def save(self
, commit
=True):
80 if sess
is None and not DISABLE_GLOBALS
:
82 assert sess
is not None, "Can't save, %r has a detached session" % self
89 def delete(self
, commit
=True, deletion
=None):
90 """ Delete the object either using soft or hard deletion """
91 # Get the setting in the model args if none has been specified.
93 deletion
= self
.deletion_mode
95 # If the item is in any collection then it should be removed, this will
96 # cause issues if it isn't. See #5382.
97 # Import here to prevent cyclic imports.
98 from mediagoblin
.db
.models
import CollectionItem
, GenericModelReference
100 # Some of the models don't have an "id" field which means they can't be
101 # used with GMR, these models won't be in collections because they
102 # can't be. We can skip all of this.
103 if hasattr(self
, "id"):
104 # First find the GenericModelReference for this object
105 gmr
= GenericModelReference
.query
.filter_by(
107 model_type
=self
.__tablename
__
110 # If there is no gmr then we've got lucky, a GMR is a requirement of
111 # being in a collection.
113 items
= CollectionItem
.query
.filter_by(
117 # Delete any CollectionItems found.
120 # Hand off to the correct deletion function.
121 if deletion
== self
.HARD_DELETE
:
122 return self
.hard_delete(commit
=commit
)
123 elif deletion
== self
.SOFT_DELETE
:
124 return self
.soft_delete(commit
=commit
)
127 "Invalid deletion mode {mode!r}".format(
132 def soft_delete(self
, commit
):
133 # Create the graveyard version of this model
134 # Importing this here due to cyclic imports
135 from mediagoblin
.db
.models
import User
, Graveyard
, GenericModelReference
136 tombstone
= Graveyard()
137 if getattr(self
, "public_id", None) is not None:
138 tombstone
.public_id
= self
.public_id
140 # This is a special case, we don't want to save any actor if the thing
141 # being soft deleted is a User model as this would create circular
143 if not isinstance(self
, User
):
144 tombstone
.actor
= User
.query
.filter_by(
147 tombstone
.object_type
= self
.object_type
148 tombstone
.save(commit
=False)
150 # There will be a lot of places where the GenericForeignKey will point
151 # to the model, we want to remap those to our tombstone.
152 gmrs
= GenericModelReference
.query
.filter_by(
154 model_type
=self
.__tablename
__
156 "obj_pk": tombstone
.id,
157 "model_type": tombstone
.__tablename
__,
161 # Now we can go ahead and actually delete the model.
162 return self
.hard_delete(commit
=commit
)
164 def hard_delete(self
, commit
):
165 """Delete the object and commit the change immediately by default"""
167 assert sess
is not None, "Not going to delete detached %r" % self
173 Base
= declarative_base(cls
=GMGTableBase
)
176 class DictReadAttrProxy(object):
178 Maps read accesses to obj['key'] to obj.key
179 and hides all the rest of the obj
181 def __init__(self
, proxied_obj
):
182 self
.proxied_obj
= proxied_obj
184 def __getitem__(self
, key
):
186 return getattr(self
.proxied_obj
, key
)
187 except AttributeError:
188 raise KeyError("%r is not an attribute on %r"
189 % (key
, self
.proxied_obj
))