Ticket #1028: new_models.org

File new_models.org, 11.7 KB (added by Jessica Tallon, 9 years ago)

new models org file

Line 
1#+TITLE: New Models (GNU MediaGoblin)
2#+AUTHOR: Jessica Tallon <jessica@megworld.co.uk>
3
4* Generic Foreign Key
5 There is a few solutions to this including one example from SQLAlchemy
6 themselves. I personlly think that the SA's [[http://docs.sqlalchemy.org/en/latest/_modules/examples/generic_associations/generic_fk.html][example]] isn't flexible enough
7 to work as a truely generic way of easily having 1 or more foreign keys on
8 a model.
9
10 We don't need referential integrity, I'm thinking something like:
11 #+BEGIN_SRC python
12 from sqlalchemy.orm import class_mapper
13
14 class GenericModelReference(Base):
15 """
16 This represents a generic relationship to any model
17 that is defined with a integer primry key.
18 """
19 __tablename__ = "core__generic_model_reference"
20
21 id = Column(Integer, primary_key=True)
22
23 obj_pk = Column(Integer, nullable=False)
24
25 # This will be the tablename of the model
26 model_type = Column(Unicode, nullable=False)
27
28 @property
29 def get(self):
30 # This can happen if it's yet to be saved
31 if self.model_type is None or self.obj_pk is None:
32 return None
33
34 model = self._get_model_from_type(self.model_type)
35 return model.query.filter_by(id=self.obj_pk)
36
37 @property
38 def set(self, obj):
39 model = obj.__class__
40
41 # Check we've been given a object
42 if not issubclass(model, Base):
43 raise ValueError("Only models can be set as GenericForeignKeys")
44
45 # Check that the model has an explicit __tablename__ declaration
46 if getattr(model, "__tablename__", None) is None:
47 raise ValueError("Models must have __tablename__ attribute")
48
49 # Check that it's not a composite primary key
50 primary_keys = [key.name for key in class_mapper(model).primary_key]
51 if len(primary_keys) > 1:
52 raise ValueError("Models can't have composite primary keys")
53
54 # Check that the field on the model is a an integer field
55 pk_column = getattr(model, primary_keys[0])
56 if issubclass(Integer, pk_column):
57 raise ValueError("Only models with integer pks can be set")
58
59 # Ensure that everything has it's ID set
60 obj.save(commit=False)
61
62 self.obj_pk = obj.id
63 self.model_type = obj.__tablename__
64
65
66 def _get_model_from_type(self, model_type):
67 """ Gets a model from a tablename (model type) """
68 if getattr(self, "_TYPE_MAP", None) is None:
69 # We want to build on the class (not the instance) a map of all the
70 # models by the table name (type) for easy lookup, this is done on
71 # the class so it can be shared between all instances
72
73 # to prevent circular imports do import here
74 from mediagoblin.db.models import MODELS
75 self._TYPE_MAP = dict(((m.__tablename__, m) for m in MODELS))
76 setattr(self.__class__._TYPE_MAP, self._TYPE_MAP)
77
78 return self._TYPE_MAP[model_type]
79
80
81 class GenericForeignKey(ForeignKey):
82
83 def __init__(self, *args, **kwargs):
84 super(GenericForeignKey, self).__init__(
85 "core__generic_model_reference.id",
86 ,*args,
87 ,**kwargs
88 )
89
90 #+END_SRC
91* User model
92 Use the [[http://docs.sqlalchemy.org/en/rel_0_8/orm/extensions/declarative.html#inheritance-configuration][inheritance configuration]] that will allow us to have a base User model
93 which a Local and Remote user can extend from. This allows us to have the local
94 specific attributes on the LocalUser and the remote specific on the Remote user
95 but retain a single User model to point to and retain referential integrity.
96
97 #+BEGIN_SRC python
98 class User(Base):
99 """
100 The base User class that LocalUser and RemoteUser
101 can inherit from. This will hold all the fields which
102 are shared between all User models.
103
104 NB: In ForeignKey fields to User you should point to this
105 User model not the LocalUser or RemoteUser models.
106 """
107 __tablename__ = "core__users"
108
109 id = Column(Integer, primary_key=True)
110 public_id = Column(Unicode, unique=True)
111
112 name = Column(Unicode)
113 bio = Column(UnicodeText)
114 url = Column(Unicode)
115 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
116 updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
117 location = Column(Integer, ForeignKey("core__locations.id"))
118
119 # Activity backreference field
120 activity = Column(Integer, ForeignKey("core__activity_intermediators.id"))
121
122 # Lazy getters
123 get_location = relationship("Location", lazy="joined")
124
125 class LocalUser(User):
126 """
127 These are Users which exist on this instance.
128 """
129 __tablename__ = "core__local_users"
130
131 id = Column(Integer, ForeignKey("core__users.id"), primary_key=True)
132
133 username = Column(Unicode, nullable=False, unique=True)
134 email = Column(Unicode, nullable=False)
135 pw_hash = Column(Unicode)
136
137 wants_comment_notification = Column(Boolean, default=True)
138 wants_notifications = Column(Boolean, default=True)
139 license_preference = Column(Unicode)
140 uploaded = Column(Integer, default=0)
141 upload_limit = Column(Integer)
142
143 def __repr__(self):
144 return "<{0} #{1} {2} {3} {4}>".format(
145 self.__class__.__name__,
146 self.id,
147 "verified" if self.has_privilege(u"active") else "non-verified",
148 "admin" if self.has_privilege(u"admin") else "user",
149 self.username
150 )
151
152 class RemoteUser(User):
153 """
154 These are Users which are registered on a remote site
155 """
156 __tablename__ = "core__remote_users"
157
158 id = Column(Integer, ForeignKey("core__users.id"), primary_key=True)
159
160 webfinger = Column(Unicode, nullable=False)
161
162 def __repr__(self):
163 return "<{0} #{1} {2}>".format(
164 self.__class__.__name__,
165 self.id,
166 self.webfinger
167 )
168 #+END_SRC
169
170* MediaEntry
171 #+BEGIN_SRC python
172 class MediaEntry(Base, MediaEntryMixin):
173 __tablename__ = "core__media_entries"
174
175 id = Column(Integer, primary_key=True)
176 public_id = Column(Unicode, unique=True)
177 deleted = Column(Boolean, default=False)
178 remote = Column(Boolean, default=False)
179
180 title = Column(Unicode)
181 slug = Column(Unicode)
182 description = Column(UnicodeText)
183 media_type = Column(Unicode, nullable=False)
184 state = Column(Unicode, default=u"unprocessed", nullable=False)
185 license = Column(Unicode)
186 file_size = Column(Integer, default=0)
187 created = Column(DateTime, default=datetime.datetime.now, nullable=False)
188 updated = Column(DateTime, default=datetime.datetime.now, nullable=False)
189
190 activity = Column(Integer, ForeignKey("core__activity_intermediators.id"))
191 uploader = Column(Integer, ForeignKey("core__users.id"), nullable=False, index=True)
192 #+END_SRC
193* Activity
194 The activity needs to/cc/bto/bcc needs to be able to reference
195 Users, Lists of users and a special list users such as:
196
197 - Public
198 - Following
199 - Followers
200
201 This should be a Generic ManyToMany field. The activity should be:
202 #+BEGIN_SRC python
203 # Create the link tables for the ManyToMany fields
204 to_link_table = Table("link__to", Base.metadata,
205 Column("generic_id", Integer, GenericForeignKey()),
206 Column("activity_id", Integer, ForeignKey("core__activities.id"))
207 )
208
209 cc_link_table = Table("link_cc", Base.metadata,
210 Column("generic_id", Integer, GenericForeignKey()),
211 Column("activity_id", Integer, ForeignKey("core__activities.id"))
212 )
213
214 bto_link_table = Table("link_bto", Base.metadata,
215 Column("generic_id", Integer, GenericForeignKey()),
216 Column("activity_id", Integer, ForeignKey("core__activities.id"))
217 )
218
219 bcc_link_table = Table("link_bcc", Base.metadata,
220 Column("generic_id", Integer, GenericForeignKey()),
221 Column("activity_id", Integer, ForeignKey("core__activities.id"))
222 )
223
224 class Activity(Base):
225 __tablename__ = "core__activities"
226
227 id = Column(Integer, primary_key=True)
228 public_id = Column(Integer, unique=True)
229 remote = Column(Boolean, default=False)
230
231 to = relationship("GenericModelReference",
232 secondary=to_link_table)
233 cc = relationship("GenericModelReference",
234 secondary=cc_link_table)
235 bto = relationship("GenericModelReference",
236 secondary=bto_link_table)
237 bcc = relationship("GenericModelReference",
238 secondary=bcc_link_table)
239
240 actor = Column(Integer, ForeignKey("core__users.id"), nullable=False)
241 generator = Column(Integer,
242 ForeignKey("core__generators.id"),
243 nullable=False)
244 published = Column(DateTimeField,
245 nullable=False,
246 default=datetime.datetime.now)
247 updated = Column(DateTimeField,
248 nullable=False,
249 default=datetime.datetime.now)
250
251 title = Column(Unicode)
252 content = Column(UnicodeText)
253 verb = Column(Unicode)
254
255 object = Column(Integer, GenericForeignKey(), nullable=False)
256 target = Column(Integer, GenericForeignKey(), nullable=True)
257
258 #+END_SRC python
259
260* Collections
261 Collections in pump.io can contain any kind of data including different
262 kinds of data ([[https://github.com/activitystreams/activity-schema/blob/master/activity-schema.md#object-types][see spec]]). This will require a migration away from the
263 current kind of Collections which can currently can only be MediaEntries.
264
265 #+BEGIN_SRC python
266 collection_link_table = Table("link__user_collection", Base.metadata,
267 Column(Integer, ForeignKey("core__user_collections.id")),
268 Column(Integer, GenericForeignKey())
269 )
270
271 class Collection(Base):
272 """ This represents a Collection of objects """
273 __tablename__ = "core__collections"
274
275 id = Column(Integer, primary_key=True)
276 public_id = Column(Unicode, unique=True)
277 remote = Column(Boolean, default=False)
278 deleted = Column(Boolean, default=False)
279
280 name = Column(Unicode)
281 description = Column(UnicodeText)
282 objects = relationship("GenericModelReference",
283 secondary=collection_link_table)
284 #+END_SRC
285
286* Likes
287 This is to record who likes a comment or a piece of media.
288
289 #+BEGIN_SRC python
290 class Like(Base):
291 """ This represents a like/favorite on an object """
292 __tablename__ = "core__likes"
293
294 id = Column(Integer, primary_key=True)
295
296 object = Column(Integer, GenericForeignKey(), nullable=False)
297 author = Column(Integer, FoeignKey("core__users.id"), nullable=False)
298 created = Column(DateTimeField, default=datetime.datetime.now, nullable=False)
299 #+END_SRC
300
301