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/>.
18 TODO: indexes on foreignkeys, where useful.
21 from __future__
import print_function
26 from sqlalchemy
import Column
, Integer
, Unicode
, UnicodeText
, DateTime
, \
27 Boolean
, ForeignKey
, UniqueConstraint
, PrimaryKeyConstraint
, \
28 SmallInteger
, Date
, types
, Float
29 from sqlalchemy
.orm
import relationship
, backref
, with_polymorphic
, validates
, \
31 from sqlalchemy
.orm
.collections
import attribute_mapped_collection
32 from sqlalchemy
.sql
import and_
33 from sqlalchemy
.sql
.expression
import desc
34 from sqlalchemy
.ext
.associationproxy
import association_proxy
35 from sqlalchemy
.util
import memoized_property
37 from mediagoblin
.db
.extratypes
import (PathTupleWithSlashes
, JSONEncoded
,
39 from mediagoblin
.db
.base
import Base
, DictReadAttrProxy
, FakeCursor
40 from mediagoblin
.db
.mixin
import UserMixin
, MediaEntryMixin
, \
41 CollectionMixin
, CollectionItemMixin
, ActivityMixin
, TextCommentMixin
, \
43 from mediagoblin
.tools
.files
import delete_media_files
44 from mediagoblin
.tools
.common
import import_component
45 from mediagoblin
.tools
.routing
import extract_url_arguments
48 from six
.moves
.urllib
.parse
import urljoin
51 _log
= logging
.getLogger(__name__
)
53 class GenericModelReference(Base
):
55 Represents a relationship to any model that is defined with a integer pk
57 __tablename__
= "core__generic_model_reference"
59 id = Column(Integer
, primary_key
=True)
60 obj_pk
= Column(Integer
, nullable
=False)
62 # This will be the tablename of the model
63 model_type
= Column(Unicode
, nullable
=False)
65 # Constrain it so obj_pk and model_type have to be unique
66 # They should be this order as the index is generated, "model_type" will be
67 # the major order as it's put first.
69 UniqueConstraint("model_type", "obj_pk"),
73 # This can happen if it's yet to be saved
74 if self
.model_type
is None or self
.obj_pk
is None:
77 model
= self
._get
_model
_from
_type
(self
.model_type
)
78 return model
.query
.filter_by(id=self
.obj_pk
).first()
80 def set_object(self
, obj
):
83 # Check we've been given a object
84 if not issubclass(model
, Base
):
85 raise ValueError("Only models can be set as using the GMR")
87 # Check that the model has an explicit __tablename__ declaration
88 if getattr(model
, "__tablename__", None) is None:
89 raise ValueError("Models must have __tablename__ attribute")
91 # Check that it's not a composite primary key
92 primary_keys
= [key
.name
for key
in class_mapper(model
).primary_key
]
93 if len(primary_keys
) > 1:
94 raise ValueError("Models can not have composite primary keys")
96 # Check that the field on the model is a an integer field
97 pk_column
= getattr(model
, primary_keys
[0])
98 if not isinstance(pk_column
.type, Integer
):
99 raise ValueError("Only models with integer pks can be set")
101 if getattr(obj
, pk_column
.key
) is None:
102 obj
.save(commit
=False)
104 self
.obj_pk
= getattr(obj
, pk_column
.key
)
105 self
.model_type
= obj
.__tablename
__
107 def _get_model_from_type(self
, model_type
):
108 """ Gets a model from a tablename (model type) """
109 if getattr(type(self
), "_TYPE_MAP", None) is None:
110 # We want to build on the class (not the instance) a map of all the
111 # models by the table name (type) for easy lookup, this is done on
112 # the class so it can be shared between all instances
114 # to prevent circular imports do import here
115 registry
= dict(Base
._decl
_class
_registry
).values()
116 self
._TYPE
_MAP
= dict(
117 ((m
.__tablename
__, m
) for m
in registry
if hasattr(m
, "__tablename__"))
119 setattr(type(self
), "_TYPE_MAP", self
._TYPE
_MAP
)
121 return self
.__class
__._TYPE
_MAP
[model_type
]
124 def find_for_obj(cls
, obj
):
125 """ Finds a GMR for an object or returns None """
126 # Is there one for this already.
128 pk
= getattr(obj
, "id")
130 gmr
= cls
.query
.filter_by(
132 model_type
=model
.__tablename
__
138 def find_or_new(cls
, obj
):
139 """ Finds an existing GMR or creates a new one for the object """
140 gmr
= cls
.find_for_obj(obj
)
142 # If there isn't one already create one
146 model_type
=type(obj
).__tablename
__
151 class Location(Base
):
152 """ Represents a physical location """
153 __tablename__
= "core__locations"
155 id = Column(Integer
, primary_key
=True)
156 name
= Column(Unicode
)
159 position
= Column(MutationDict
.as_mutable(JSONEncoded
))
160 address
= Column(MutationDict
.as_mutable(JSONEncoded
))
163 def create(cls
, data
, obj
):
165 location
.unserialize(data
)
167 obj
.location
= location
.id
170 def serialize(self
, request
):
171 location
= {"objectType": "place"}
173 if self
.name
is not None:
174 location
["displayName"] = self
.name
177 location
["position"] = self
.position
180 location
["address"] = self
.address
184 def unserialize(self
, data
):
185 if "displayName" in data
:
186 self
.name
= data
["displayName"]
191 # nicer way to do this?
192 if "position" in data
:
193 # TODO: deal with ISO 9709 formatted string as position
194 if "altitude" in data
["position"]:
195 self
.position
["altitude"] = data
["position"]["altitude"]
197 if "direction" in data
["position"]:
198 self
.position
["direction"] = data
["position"]["direction"]
200 if "longitude" in data
["position"]:
201 self
.position
["longitude"] = data
["position"]["longitude"]
203 if "latitude" in data
["position"]:
204 self
.position
["latitude"] = data
["position"]["latitude"]
206 if "address" in data
:
207 if "formatted" in data
["address"]:
208 self
.address
["formatted"] = data
["address"]["formatted"]
210 if "streetAddress" in data
["address"]:
211 self
.address
["streetAddress"] = data
["address"]["streetAddress"]
213 if "locality" in data
["address"]:
214 self
.address
["locality"] = data
["address"]["locality"]
216 if "region" in data
["address"]:
217 self
.address
["region"] = data
["address"]["region"]
219 if "postalCode" in data
["address"]:
220 self
.address
["postalCode"] = data
["addresss"]["postalCode"]
222 if "country" in data
["address"]:
223 self
.address
["country"] = data
["address"]["country"]
225 class User(Base
, UserMixin
):
227 Base user that is common amongst LocalUser and RemoteUser.
229 This holds all the fields which are common between both the Local and Remote
232 NB: ForeignKeys should reference this User model and NOT the LocalUser or
235 __tablename__
= "core__users"
237 id = Column(Integer
, primary_key
=True)
238 url
= Column(Unicode
)
239 bio
= Column(UnicodeText
)
240 name
= Column(Unicode
)
242 # This is required for the polymorphic inheritance
243 type = Column(Unicode
)
245 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
246 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
248 location
= Column(Integer
, ForeignKey("core__locations.id"))
251 get_location
= relationship("Location", lazy
="joined")
254 'polymorphic_identity': 'user',
255 'polymorphic_on': type,
258 deletion_mode
= Base
.SOFT_DELETE
260 def soft_delete(self
, *args
, **kwargs
):
261 # Find all the Collections and delete those
262 for collection
in Collection
.query
.filter_by(actor
=self
.id):
263 collection
.delete(**kwargs
)
265 # Find all the comments and delete those too
266 for comment
in TextComment
.query
.filter_by(actor
=self
.id):
267 comment
.delete(**kwargs
)
269 # Find all the activities and delete those too
270 for activity
in Activity
.query
.filter_by(actor
=self
.id):
271 activity
.delete(**kwargs
)
273 super(User
, self
).soft_delete(*args
, **kwargs
)
276 def delete(self
, *args
, **kwargs
):
277 """Deletes a User and all related entries/comments/files/..."""
278 # Collections get deleted by relationships.
280 media_entries
= MediaEntry
.query
.filter(MediaEntry
.actor
== self
.id)
281 for media
in media_entries
:
282 # TODO: Make sure that "MediaEntry.delete()" also deletes
283 # all related files/Comments
284 media
.delete(del_orphan_tags
=False, commit
=False)
286 # Delete now unused tags
287 # TODO: import here due to cyclic imports!!! This cries for refactoring
288 from mediagoblin
.db
.util
import clean_orphan_tags
289 clean_orphan_tags(commit
=False)
291 # Delete user, pass through commit=False/True in kwargs
292 username
= self
.username
293 super(User
, self
).delete(*args
, **kwargs
)
294 _log
.info('Deleted user "{0}" account'.format(username
))
296 def has_privilege(self
, privilege
, allow_admin
=True):
298 This method checks to make sure a user has all the correct privileges
299 to access a piece of content.
301 :param privilege A unicode object which represent the different
302 privileges which may give the user access to
305 :param allow_admin If this is set to True the then if the user is
306 an admin, then this will always return True
307 even if the user hasn't been given the
308 privilege. (defaults to True)
310 priv
= Privilege
.query
.filter_by(privilege_name
=privilege
).one()
311 if priv
in self
.all_privileges
:
313 elif allow_admin
and self
.has_privilege(u
'admin', allow_admin
=False):
320 Checks if this user is banned.
322 :returns True if self is banned
323 :returns False if self is not
325 return UserBan
.query
.get(self
.id) is not None
327 def serialize(self
, request
):
328 published
= UTC
.localize(self
.created
)
329 updated
= UTC
.localize(self
.updated
)
331 "published": published
.isoformat(),
332 "updated": updated
.isoformat(),
333 "objectType": self
.object_type
,
341 user
.update({"summary": self
.bio
})
343 user
.update({"url": self
.url
})
345 user
.update({"location": self
.get_location
.serialize(request
)})
349 def unserialize(self
, data
):
350 if "summary" in data
:
351 self
.bio
= data
["summary"]
353 if "location" in data
:
354 Location
.create(data
, self
)
356 class LocalUser(User
):
357 """ This represents a user registered on this instance """
358 __tablename__
= "core__local_users"
360 id = Column(Integer
, ForeignKey("core__users.id"), primary_key
=True)
361 username
= Column(Unicode
, nullable
=False, unique
=True)
362 # Note: no db uniqueness constraint on email because it's not
363 # reliable (many email systems case insensitive despite against
364 # the RFC) and because it would be a mess to implement at this
366 email
= Column(Unicode
, nullable
=False)
367 pw_hash
= Column(Unicode
)
369 # Intented to be nullable=False, but migrations would not work for it
370 # set to nullable=True implicitly.
371 wants_comment_notification
= Column(Boolean
, default
=True)
372 wants_notifications
= Column(Boolean
, default
=True)
373 license_preference
= Column(Unicode
)
374 uploaded
= Column(Integer
, default
=0)
375 upload_limit
= Column(Integer
)
378 "polymorphic_identity": "user_local",
382 # plugin data would be in a separate model
385 return '<{0} #{1} {2} {3} "{4}">'.format(
386 self
.__class
__.__name
__,
388 'verified' if self
.has_privilege(u
'active') else 'non-verified',
389 'admin' if self
.has_privilege(u
'admin') else 'user',
392 def get_public_id(self
, host
):
393 return "acct:{0}@{1}".format(self
.username
, host
)
395 def serialize(self
, request
):
397 "id": self
.get_public_id(request
.host
),
398 "preferredUsername": self
.username
,
399 "displayName": self
.get_public_id(request
.host
).split(":", 1)[1],
402 "href": request
.urlgen(
403 "mediagoblin.api.user.profile",
404 username
=self
.username
,
409 "href": request
.urlgen(
410 "mediagoblin.api.inbox",
411 username
=self
.username
,
416 "href": request
.urlgen(
417 "mediagoblin.api.feed",
418 username
=self
.username
,
425 user
.update(super(LocalUser
, self
).serialize(request
))
428 class RemoteUser(User
):
429 """ User that is on another (remote) instance """
430 __tablename__
= "core__remote_users"
432 id = Column(Integer
, ForeignKey("core__users.id"), primary_key
=True)
433 webfinger
= Column(Unicode
, unique
=True)
436 'polymorphic_identity': 'user_remote'
440 return "<{0} #{1} {2}>".format(
441 self
.__class
__.__name
__,
449 Model representing a client - Used for API Auth
451 __tablename__
= "core__clients"
453 id = Column(Unicode
, nullable
=True, primary_key
=True)
454 secret
= Column(Unicode
, nullable
=False)
455 expirey
= Column(DateTime
, nullable
=True)
456 application_type
= Column(Unicode
, nullable
=False)
457 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
458 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
461 redirect_uri
= Column(JSONEncoded
, nullable
=True)
462 logo_url
= Column(Unicode
, nullable
=True)
463 application_name
= Column(Unicode
, nullable
=True)
464 contacts
= Column(JSONEncoded
, nullable
=True)
467 if self
.application_name
:
468 return "<Client {0} - {1}>".format(self
.application_name
, self
.id)
470 return "<Client {0}>".format(self
.id)
472 class RequestToken(Base
):
474 Model for representing the request tokens
476 __tablename__
= "core__request_tokens"
478 token
= Column(Unicode
, primary_key
=True)
479 secret
= Column(Unicode
, nullable
=False)
480 client
= Column(Unicode
, ForeignKey(Client
.id))
481 actor
= Column(Integer
, ForeignKey(User
.id), nullable
=True)
482 used
= Column(Boolean
, default
=False)
483 authenticated
= Column(Boolean
, default
=False)
484 verifier
= Column(Unicode
, nullable
=True)
485 callback
= Column(Unicode
, nullable
=False, default
=u
"oob")
486 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
487 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
489 get_client
= relationship(Client
)
491 class AccessToken(Base
):
493 Model for representing the access tokens
495 __tablename__
= "core__access_tokens"
497 token
= Column(Unicode
, nullable
=False, primary_key
=True)
498 secret
= Column(Unicode
, nullable
=False)
499 actor
= Column(Integer
, ForeignKey(User
.id))
500 request_token
= Column(Unicode
, ForeignKey(RequestToken
.token
))
501 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
502 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
504 get_requesttoken
= relationship(RequestToken
)
507 class NonceTimestamp(Base
):
509 A place the timestamp and nonce can be stored - this is for OAuth1
511 __tablename__
= "core__nonce_timestamps"
513 nonce
= Column(Unicode
, nullable
=False, primary_key
=True)
514 timestamp
= Column(DateTime
, nullable
=False, primary_key
=True)
516 class MediaEntry(Base
, MediaEntryMixin
, CommentingMixin
):
518 TODO: Consider fetching the media_files using join
520 __tablename__
= "core__media_entries"
522 id = Column(Integer
, primary_key
=True)
523 public_id
= Column(Unicode
, unique
=True, nullable
=True)
524 remote
= Column(Boolean
, default
=False)
526 actor
= Column(Integer
, ForeignKey(User
.id), nullable
=False, index
=True)
527 title
= Column(Unicode
, nullable
=False)
528 slug
= Column(Unicode
)
529 description
= Column(UnicodeText
) # ??
530 media_type
= Column(Unicode
, nullable
=False)
531 state
= Column(Unicode
, default
=u
'unprocessed', nullable
=False)
532 # or use sqlalchemy.types.Enum?
533 license
= Column(Unicode
)
534 file_size
= Column(Integer
, default
=0)
535 location
= Column(Integer
, ForeignKey("core__locations.id"))
536 get_location
= relationship("Location", lazy
="joined")
538 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
,
540 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
542 fail_error
= Column(Unicode
)
543 fail_metadata
= Column(JSONEncoded
)
545 transcoding_progress
= Column(Float
, default
=0)
547 queued_media_file
= Column(PathTupleWithSlashes
)
549 queued_task_id
= Column(Unicode
)
552 UniqueConstraint('actor', 'slug'),
555 deletion_mode
= Base
.SOFT_DELETE
557 get_actor
= relationship(User
)
559 media_files_helper
= relationship("MediaFile",
560 collection_class
=attribute_mapped_collection("name"),
561 cascade
="all, delete-orphan"
563 media_files
= association_proxy('media_files_helper', 'file_path',
564 creator
=lambda k
, v
: MediaFile(name
=k
, file_path
=v
)
567 attachment_files_helper
= relationship("MediaAttachmentFile",
568 cascade
="all, delete-orphan",
569 order_by
="MediaAttachmentFile.created"
571 attachment_files
= association_proxy("attachment_files_helper", "dict_view",
572 creator
=lambda v
: MediaAttachmentFile(
573 name
=v
["name"], filepath
=v
["filepath"])
576 tags_helper
= relationship("MediaTag",
577 cascade
="all, delete-orphan" # should be automatically deleted
579 tags
= association_proxy("tags_helper", "dict_view",
580 creator
=lambda v
: MediaTag(name
=v
["name"], slug
=v
["slug"])
583 media_metadata
= Column(MutationDict
.as_mutable(JSONEncoded
),
584 default
=MutationDict())
590 def get_uploader(self
):
592 return self
.get_actor
600 def collections(self
):
601 """ Get any collections that this MediaEntry is in """
602 return list(Collection
.query
.join(Collection
.collection_items
).join(
603 CollectionItem
.object_helper
606 GenericModelReference
.model_type
== self
.__tablename
__,
607 GenericModelReference
.obj_pk
== self
.id
611 def get_comments(self
, ascending
=False):
612 query
= Comment
.query
.join(Comment
.target_helper
).filter(and_(
613 GenericModelReference
.obj_pk
== self
.id,
614 GenericModelReference
.model_type
== self
.__tablename
__
618 query
= query
.order_by(Comment
.added
.asc())
620 query
= query
.order_by(Comment
.added
.desc())
624 def url_to_prev(self
, urlgen
):
625 """get the next 'newer' entry by this user"""
626 media
= MediaEntry
.query
.filter(
627 (MediaEntry
.actor
== self
.actor
)
628 & (MediaEntry
.state
== u
'processed')
629 & (MediaEntry
.id > self
.id)).order_by(MediaEntry
.id).first()
631 if media
is not None:
632 return media
.url_for_self(urlgen
)
634 def url_to_next(self
, urlgen
):
635 """get the next 'older' entry by this user"""
636 media
= MediaEntry
.query
.filter(
637 (MediaEntry
.actor
== self
.actor
)
638 & (MediaEntry
.state
== u
'processed')
639 & (MediaEntry
.id < self
.id)).order_by(desc(MediaEntry
.id)).first()
641 if media
is not None:
642 return media
.url_for_self(urlgen
)
644 def get_file_metadata(self
, file_key
, metadata_key
=None):
646 Return the file_metadata dict of a MediaFile. If metadata_key is given,
647 return the value of the key.
649 media_file
= MediaFile
.query
.filter_by(media_entry
=self
.id,
650 name
=six
.text_type(file_key
)).first()
654 return media_file
.file_metadata
.get(metadata_key
, None)
656 return media_file
.file_metadata
658 def set_file_metadata(self
, file_key
, **kwargs
):
660 Update the file_metadata of a MediaFile.
662 media_file
= MediaFile
.query
.filter_by(media_entry
=self
.id,
663 name
=six
.text_type(file_key
)).first()
665 file_metadata
= media_file
.file_metadata
or {}
667 for key
, value
in six
.iteritems(kwargs
):
668 file_metadata
[key
] = value
670 media_file
.file_metadata
= file_metadata
674 def media_data(self
):
675 return getattr(self
, self
.media_data_ref
)
677 def media_data_init(self
, **kwargs
):
679 Initialize or update the contents of a media entry's media_data row
681 media_data
= self
.media_data
683 if media_data
is None:
684 # Get the correct table:
685 table
= import_component(self
.media_type
+ '.models:DATA_MODEL')
686 # No media data, so actually add a new one
687 media_data
= table(**kwargs
)
688 # Get the relationship set up.
689 media_data
.get_media_entry
= self
691 # Update old media data
692 for field
, value
in six
.iteritems(kwargs
):
693 setattr(media_data
, field
, value
)
696 def media_data_ref(self
):
697 return import_component(self
.media_type
+ '.models:BACKREF_NAME')
701 # obj.__repr__() should return a str on Python 2
702 safe_title
= self
.title
.encode('utf-8', 'replace')
704 safe_title
= self
.title
706 return '<{classname} {id}: {title}>'.format(
707 classname
=self
.__class
__.__name
__,
711 def soft_delete(self
, *args
, **kwargs
):
712 # Find all of the media comments for this and delete them
713 for comment
in self
.get_comments():
714 comment
.delete(*args
, **kwargs
)
716 super(MediaEntry
, self
).soft_delete(*args
, **kwargs
)
718 def delete(self
, del_orphan_tags
=True, **kwargs
):
719 """Delete MediaEntry and all related files/attachments/comments
721 This will *not* automatically delete unused collections, which
724 :param del_orphan_tags: True/false if we delete unused Tags too
725 :param commit: True/False if this should end the db transaction"""
726 # User's CollectionItems are automatically deleted via "cascade".
727 # Comments on this Media are deleted by cascade, hopefully.
729 # Delete all related files/attachments
731 delete_media_files(self
)
732 except OSError as error
:
733 # Returns list of files we failed to delete
734 _log
.error('No such files from the user "{1}" to delete: '
735 '{0}'.format(str(error
), self
.get_actor
))
736 _log
.info('Deleted Media entry id "{0}"'.format(self
.id))
737 # Related MediaTag's are automatically cleaned, but we might
738 # want to clean out unused Tag's too.
740 # TODO: Import here due to cyclic imports!!!
741 # This cries for refactoring
742 from mediagoblin
.db
.util
import clean_orphan_tags
743 clean_orphan_tags(commit
=False)
744 # pass through commit=False/True in kwargs
745 super(MediaEntry
, self
).delete(**kwargs
)
747 def serialize(self
, request
, show_comments
=True):
748 """ Unserialize MediaEntry to object """
749 author
= self
.get_actor
750 published
= UTC
.localize(self
.created
)
751 updated
= UTC
.localize(self
.updated
)
752 public_id
= self
.get_public_id(request
.urlgen
)
755 "author": author
.serialize(request
),
756 "objectType": self
.object_type
,
757 "url": self
.url_for_self(request
.urlgen
, qualified
=True),
759 "url": urljoin(request
.host_url
, self
.thumb_url
),
762 "url": urljoin(request
.host_url
, self
.original_url
),
764 "published": published
.isoformat(),
765 "updated": updated
.isoformat(),
778 context
["displayName"] = self
.title
781 context
["content"] = self
.description
784 context
["license"] = self
.license
787 context
["location"] = self
.get_location
.serialize(request
)
791 l
.comment().serialize(request
) for l
in self
.get_comments()]
792 total
= len(comments
)
793 context
["replies"] = {
796 "url": request
.urlgen(
797 "mediagoblin.api.object.comments",
798 object_type
=self
.object_type
,
804 # Add image height and width if possible. We didn't use to store this
805 # data and we're not able (and maybe not willing) to re-process all
806 # images so it's possible this might not exist.
807 if self
.get_file_metadata("thumb", "height"):
808 height
= self
.get_file_metadata("thumb", "height")
809 context
["image"]["height"] = height
810 if self
.get_file_metadata("thumb", "width"):
811 width
= self
.get_file_metadata("thumb", "width")
812 context
["image"]["width"] = width
813 if self
.get_file_metadata("original", "height"):
814 height
= self
.get_file_metadata("original", "height")
815 context
["fullImage"]["height"] = height
816 if self
.get_file_metadata("original", "height"):
817 width
= self
.get_file_metadata("original", "width")
818 context
["fullImage"]["width"] = width
822 def unserialize(self
, data
):
823 """ Takes API objects and unserializes on existing MediaEntry """
824 if "displayName" in data
:
825 self
.title
= data
["displayName"]
827 if "content" in data
:
828 self
.description
= data
["content"]
830 if "license" in data
:
831 self
.license
= data
["license"]
833 if "location" in data
:
834 License
.create(data
["location"], self
)
838 class FileKeynames(Base
):
840 keywords for various places.
841 currently the MediaFile keys
843 __tablename__
= "core__file_keynames"
844 id = Column(Integer
, primary_key
=True)
845 name
= Column(Unicode
, unique
=True)
848 return "<FileKeyname %r: %r>" % (self
.id, self
.name
)
851 def find_or_new(cls
, name
):
852 t
= cls
.query
.filter_by(name
=name
).first()
855 return cls(name
=name
)
858 class MediaFile(Base
):
860 TODO: Highly consider moving "name" into a new table.
861 TODO: Consider preloading said table in software
863 __tablename__
= "core__mediafiles"
865 media_entry
= Column(
866 Integer
, ForeignKey(MediaEntry
.id),
868 name_id
= Column(SmallInteger
, ForeignKey(FileKeynames
.id), nullable
=False)
869 file_path
= Column(PathTupleWithSlashes
)
870 file_metadata
= Column(MutationDict
.as_mutable(JSONEncoded
))
873 PrimaryKeyConstraint('media_entry', 'name_id'),
877 return "<MediaFile %s: %r>" % (self
.name
, self
.file_path
)
879 name_helper
= relationship(FileKeynames
, lazy
="joined", innerjoin
=True)
880 name
= association_proxy('name_helper', 'name',
881 creator
=FileKeynames
.find_or_new
885 class MediaAttachmentFile(Base
):
886 __tablename__
= "core__attachment_files"
888 id = Column(Integer
, primary_key
=True)
889 media_entry
= Column(
890 Integer
, ForeignKey(MediaEntry
.id),
892 name
= Column(Unicode
, nullable
=False)
893 filepath
= Column(PathTupleWithSlashes
)
894 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
898 """A dict like view on this object"""
899 return DictReadAttrProxy(self
)
903 __tablename__
= "core__tags"
905 id = Column(Integer
, primary_key
=True)
906 slug
= Column(Unicode
, nullable
=False, unique
=True)
909 return "<Tag %r: %r>" % (self
.id, self
.slug
)
912 def find_or_new(cls
, slug
):
913 t
= cls
.query
.filter_by(slug
=slug
).first()
916 return cls(slug
=slug
)
919 class MediaTag(Base
):
920 __tablename__
= "core__media_tags"
922 id = Column(Integer
, primary_key
=True)
923 media_entry
= Column(
924 Integer
, ForeignKey(MediaEntry
.id),
925 nullable
=False, index
=True)
926 tag
= Column(Integer
, ForeignKey(Tag
.id), nullable
=False, index
=True)
927 name
= Column(Unicode
)
928 # created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
931 UniqueConstraint('tag', 'media_entry'),
934 tag_helper
= relationship(Tag
)
935 slug
= association_proxy('tag_helper', 'slug',
936 creator
=Tag
.find_or_new
939 def __init__(self
, name
=None, slug
=None):
944 self
.tag_helper
= Tag
.find_or_new(slug
)
948 """A dict like view on this object"""
949 return DictReadAttrProxy(self
)
953 Link table between a response and another object that can have replies.
955 This acts as a link table between an object and the comments on it, it's
956 done like this so that you can look up all the comments without knowing
957 whhich comments are on an object before hand. Any object can be a comment
958 and more or less any object can accept comments too.
960 Important: This is NOT the old MediaComment table.
962 __tablename__
= "core__comment_links"
964 id = Column(Integer
, primary_key
=True)
966 # The GMR to the object the comment is on.
969 ForeignKey(GenericModelReference
.id),
972 target_helper
= relationship(
973 GenericModelReference
,
974 foreign_keys
=[target_id
]
976 target
= association_proxy("target_helper", "get_object",
977 creator
=GenericModelReference
.find_or_new
)
982 ForeignKey(GenericModelReference
.id),
985 comment_helper
= relationship(
986 GenericModelReference
,
987 foreign_keys
=[comment_id
]
989 comment
= association_proxy("comment_helper", "get_object",
990 creator
=GenericModelReference
.find_or_new
)
993 added
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
996 def get_author(self
):
998 return self
.comment().get_actor
# noqa
1000 def __getattr__(self
, attr
):
1001 if attr
.startswith('_'):
1002 # if attr starts with '_', then it's probably some internal
1003 # sqlalchemy variable. Since __getattr__ is called when
1004 # non-existing attributes are being accessed, we should not try to
1005 # fetch it from self.comment()
1006 raise AttributeError
1008 _log
.debug('Old attr is being accessed: {0}'.format(attr
))
1009 return getattr(self
.comment(), attr
) # noqa
1010 except Exception as e
:
1014 class TextComment(Base
, TextCommentMixin
, CommentingMixin
):
1016 A basic text comment, this is a usually short amount of text and nothing else
1018 # This is a legacy from when Comments where just on MediaEntry objects.
1019 __tablename__
= "core__media_comments"
1021 id = Column(Integer
, primary_key
=True)
1022 public_id
= Column(Unicode
, unique
=True)
1023 actor
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
1024 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1025 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1026 content
= Column(UnicodeText
, nullable
=False)
1027 location
= Column(Integer
, ForeignKey("core__locations.id"))
1028 get_location
= relationship("Location", lazy
="joined")
1030 # Cascade: Comments are owned by their creator. So do the full thing.
1031 # lazy=dynamic: People might post a *lot* of comments,
1032 # so make the "posted_comments" a query-like thing.
1033 get_actor
= relationship(User
,
1034 backref
=backref("posted_comments",
1036 cascade
="all, delete-orphan"))
1037 deletion_mode
= Base
.SOFT_DELETE
1039 def serialize(self
, request
):
1040 """ Unserialize to python dictionary for API """
1041 target
= self
.get_reply_to()
1042 # If this is target just.. give them nothing?
1046 target
= target
.serialize(request
, show_comments
=False)
1049 author
= self
.get_actor
1050 published
= UTC
.localize(self
.created
)
1052 "id": self
.get_public_id(request
.urlgen
),
1053 "objectType": self
.object_type
,
1054 "content": self
.content
,
1055 "inReplyTo": target
,
1056 "author": author
.serialize(request
),
1057 "published": published
.isoformat(),
1058 "updated": published
.isoformat(),
1062 context
["location"] = self
.get_location
.seralize(request
)
1066 def unserialize(self
, data
, request
):
1067 """ Takes API objects and unserializes on existing comment """
1068 if "content" in data
:
1069 self
.content
= data
["content"]
1071 if "location" in data
:
1072 Location
.create(data
["location"], self
)
1075 # Handle changing the reply ID
1076 if "inReplyTo" in data
:
1077 # Validate that the ID is correct
1079 id = extract_url_arguments(
1080 url
=data
["inReplyTo"]["id"],
1081 urlmap
=request
.app
.url_map
1086 public_id
= request
.urlgen(
1087 "mediagoblin.api.object",
1089 object_type
=data
["inReplyTo"]["objectType"],
1093 media
= MediaEntry
.query
.filter_by(public_id
=public_id
).first()
1097 # We need an ID for this model.
1098 self
.save(commit
=False)
1108 class Collection(Base
, CollectionMixin
, CommentingMixin
):
1109 """A representation of a collection of objects.
1111 This holds a group/collection of objects that could be a user defined album
1112 or their inbox, outbox, followers, etc. These are always ordered and accessable
1113 via the API and web.
1115 The collection has a number of types which determine what kind of collection
1116 it is, for example the users inbox will be of `Collection.INBOX_TYPE` that will
1117 be stored on the `Collection.type` field. It's important to set the correct type.
1119 On deletion, contained CollectionItems get automatically reaped via
1121 __tablename__
= "core__collections"
1123 id = Column(Integer
, primary_key
=True)
1124 public_id
= Column(Unicode
, unique
=True)
1125 title
= Column(Unicode
, nullable
=False)
1126 slug
= Column(Unicode
)
1127 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
,
1129 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1130 description
= Column(UnicodeText
)
1131 actor
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
1132 num_items
= Column(Integer
, default
=0)
1134 # There are lots of different special types of collections in the pump.io API
1135 # for example: followers, following, inbox, outbox, etc. See type constants
1136 # below the fields on this model.
1137 type = Column(Unicode
, nullable
=False)
1140 location
= Column(Integer
, ForeignKey("core__locations.id"))
1141 get_location
= relationship("Location", lazy
="joined")
1143 # Cascade: Collections are owned by their creator. So do the full thing.
1144 get_actor
= relationship(User
,
1145 backref
=backref("collections",
1146 cascade
="all, delete-orphan"))
1148 UniqueConstraint("actor", "slug"),
1151 deletion_mode
= Base
.SOFT_DELETE
1153 # These are the types, It's strongly suggested if new ones are invented they
1154 # are prefixed to ensure they're unique from other types. Any types used in
1155 # the main mediagoblin should be prefixed "core-"
1156 INBOX_TYPE
= "core-inbox"
1157 OUTBOX_TYPE
= "core-outbox"
1158 FOLLOWER_TYPE
= "core-followers"
1159 FOLLOWING_TYPE
= "core-following"
1160 COMMENT_TYPE
= "core-comments"
1161 USER_DEFINED_TYPE
= "core-user-defined"
1163 def get_collection_items(self
, ascending
=False):
1164 #TODO, is this still needed with self.collection_items being available?
1165 order_col
= CollectionItem
.position
1167 order_col
= desc(order_col
)
1168 return CollectionItem
.query
.filter_by(
1169 collection
=self
.id).order_by(order_col
)
1172 safe_title
= self
.title
.encode('ascii', 'replace')
1173 return '<{classname} #{id}: {title} by {actor}>'.format(
1175 classname
=self
.__class
__.__name
__,
1179 def serialize(self
, request
):
1180 # Get all serialized output in a list
1181 items
= [i
.serialize(request
) for i
in self
.get_collection_items()]
1183 "totalItems": self
.num_items
,
1184 "url": self
.url_for_self(request
.urlgen
, qualified
=True),
1189 class CollectionItem(Base
, CollectionItemMixin
):
1190 __tablename__
= "core__collection_items"
1192 id = Column(Integer
, primary_key
=True)
1194 collection
= Column(Integer
, ForeignKey(Collection
.id), nullable
=False)
1195 note
= Column(UnicodeText
, nullable
=True)
1196 added
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1197 position
= Column(Integer
)
1198 # Cascade: CollectionItems are owned by their Collection. So do the full thing.
1199 in_collection
= relationship(Collection
,
1202 cascade
="all, delete-orphan"))
1204 # Link to the object (could be anything.
1207 ForeignKey(GenericModelReference
.id),
1211 object_helper
= relationship(
1212 GenericModelReference
,
1213 foreign_keys
=[object_id
]
1215 get_object
= association_proxy(
1218 creator
=GenericModelReference
.find_or_new
1222 UniqueConstraint('collection', 'object_id'),
1226 def dict_view(self
):
1227 """A dict like view on this object"""
1228 return DictReadAttrProxy(self
)
1231 return '<{classname} #{id}: Object {obj} in {collection}>'.format(
1233 classname
=self
.__class
__.__name
__,
1234 collection
=self
.collection
,
1235 obj
=self
.get_object()
1238 def serialize(self
, request
):
1239 return self
.get_object().serialize(request
)
1242 class ProcessingMetaData(Base
):
1243 __tablename__
= 'core__processing_metadata'
1245 id = Column(Integer
, primary_key
=True)
1246 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=False,
1248 media_entry
= relationship(MediaEntry
,
1249 backref
=backref('processing_metadata',
1250 cascade
='all, delete-orphan'))
1251 callback_url
= Column(Unicode
)
1254 def dict_view(self
):
1255 """A dict like view on this object"""
1256 return DictReadAttrProxy(self
)
1259 class CommentSubscription(Base
):
1260 __tablename__
= 'core__comment_subscriptions'
1261 id = Column(Integer
, primary_key
=True)
1263 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1265 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=False)
1266 media_entry
= relationship(MediaEntry
,
1267 backref
=backref('comment_subscriptions',
1268 cascade
='all, delete-orphan'))
1270 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
1271 user
= relationship(User
,
1272 backref
=backref('comment_subscriptions',
1273 cascade
='all, delete-orphan'))
1275 notify
= Column(Boolean
, nullable
=False, default
=True)
1276 send_email
= Column(Boolean
, nullable
=False, default
=True)
1279 return ('<{classname} #{id}: {user} {media} notify: '
1280 '{notify} email: {email}>').format(
1282 classname
=self
.__class
__.__name
__,
1284 media
=self
.media_entry
,
1286 email
=self
.send_email
)
1289 class Notification(Base
):
1290 __tablename__
= 'core__notifications'
1291 id = Column(Integer
, primary_key
=True)
1293 object_id
= Column(Integer
, ForeignKey(GenericModelReference
.id))
1294 object_helper
= relationship(GenericModelReference
)
1295 obj
= association_proxy("object_helper", "get_object",
1296 creator
=GenericModelReference
.find_or_new
)
1298 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1299 user_id
= Column(Integer
, ForeignKey('core__users.id'), nullable
=False,
1301 seen
= Column(Boolean
, default
=lambda: False, index
=True)
1302 user
= relationship(
1304 backref
=backref('notifications', cascade
='all, delete-orphan'))
1307 return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
1309 klass
=self
.__class
__.__name
__,
1311 subject
=getattr(self
, 'subject', None),
1312 seen
='unseen' if not self
.seen
else 'seen')
1314 def __unicode__(self
):
1315 return u
'<{klass} #{id}: {user}: {subject} ({seen})>'.format(
1317 klass
=self
.__class
__.__name
__,
1319 subject
=getattr(self
, 'subject', None),
1320 seen
='unseen' if not self
.seen
else 'seen')
1324 Represents a report that someone might file against Media, Comments, etc.
1326 :keyword reporter_id Holds the id of the user who created
1327 the report, as an Integer column.
1328 :keyword report_content Hold the explanation left by the repor-
1329 -ter to indicate why they filed the
1330 report in the first place, as a
1332 :keyword reported_user_id Holds the id of the user who created
1333 the content which was reported, as
1335 :keyword created Holds a datetime column of when the re-
1337 :keyword resolver_id Holds the id of the moderator/admin who
1338 resolved the report.
1339 :keyword resolved Holds the DateTime object which descri-
1340 -bes when this report was resolved
1341 :keyword result Holds the UnicodeText column of the
1342 resolver's reasons for resolving
1343 the report this way. Some of this
1345 :keyword object_id Holds the ID of the GenericModelReference
1346 which points to the reported object.
1348 __tablename__
= 'core__reports'
1350 id = Column(Integer
, primary_key
=True)
1351 reporter_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
1352 reporter
= relationship(
1354 backref
=backref("reports_filed_by",
1356 cascade
="all, delete-orphan"),
1357 primaryjoin
="User.id==Report.reporter_id")
1358 report_content
= Column(UnicodeText
)
1359 reported_user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
1360 reported_user
= relationship(
1362 backref
=backref("reports_filed_on",
1364 cascade
="all, delete-orphan"),
1365 primaryjoin
="User.id==Report.reported_user_id")
1366 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1367 resolver_id
= Column(Integer
, ForeignKey(User
.id))
1368 resolver
= relationship(
1370 backref
=backref("reports_resolved_by",
1372 cascade
="all, delete-orphan"),
1373 primaryjoin
="User.id==Report.resolver_id")
1375 resolved
= Column(DateTime
)
1376 result
= Column(UnicodeText
)
1378 object_id
= Column(Integer
, ForeignKey(GenericModelReference
.id), nullable
=True)
1379 object_helper
= relationship(GenericModelReference
)
1380 obj
= association_proxy("object_helper", "get_object",
1381 creator
=GenericModelReference
.find_or_new
)
1383 def is_archived_report(self
):
1384 return self
.resolved
is not None
1386 def is_comment_report(self
):
1387 if self
.object_id
is None:
1389 return isinstance(self
.obj(), TextComment
)
1391 def is_media_entry_report(self
):
1392 if self
.object_id
is None:
1394 return isinstance(self
.obj(), MediaEntry
)
1396 def archive(self
,resolver_id
, resolved
, result
):
1397 self
.resolver_id
= resolver_id
1398 self
.resolved
= resolved
1399 self
.result
= result
1401 class UserBan(Base
):
1403 Holds the information on a specific user's ban-state. As long as one of
1404 these is attached to a user, they are banned from accessing mediagoblin.
1405 When they try to log in, they are greeted with a page that tells them
1406 the reason why they are banned and when (if ever) the ban will be
1409 :keyword user_id Holds the id of the user this object is
1410 attached to. This is a one-to-one
1412 :keyword expiration_date Holds the date that the ban will be lifted.
1413 If this is null, the ban is permanent
1414 unless a moderator manually lifts it.
1415 :keyword reason Holds the reason why the user was banned.
1417 __tablename__
= 'core__user_bans'
1419 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False,
1421 expiration_date
= Column(Date
)
1422 reason
= Column(UnicodeText
, nullable
=False)
1425 class Privilege(Base
):
1427 The Privilege table holds all of the different privileges a user can hold.
1428 If a user 'has' a privilege, the User object is in a relationship with the
1431 :keyword privilege_name Holds a unicode object that is the recognizable
1432 name of this privilege. This is the column
1433 used for identifying whether or not a user
1434 has a necessary privilege or not.
1437 __tablename__
= 'core__privileges'
1439 id = Column(Integer
, nullable
=False, primary_key
=True)
1440 privilege_name
= Column(Unicode
, nullable
=False, unique
=True)
1441 all_users
= relationship(
1443 backref
='all_privileges',
1444 secondary
="core__privileges_users")
1446 def __init__(self
, privilege_name
):
1448 Currently consructors are required for tables that are initialized thru
1449 the FOUNDATIONS system. This is because they need to be able to be con-
1450 -structed by a list object holding their arg*s
1452 self
.privilege_name
= privilege_name
1455 return "<Privilege %s>" % (self
.privilege_name
)
1458 class PrivilegeUserAssociation(Base
):
1460 This table holds the many-to-many relationship between User and Privilege
1463 __tablename__
= 'core__privileges_users'
1468 ForeignKey(User
.id),
1473 ForeignKey(Privilege
.id),
1476 class Generator(Base
):
1477 """ Information about what created an activity """
1478 __tablename__
= "core__generators"
1480 id = Column(Integer
, primary_key
=True)
1481 name
= Column(Unicode
, nullable
=False)
1482 published
= Column(DateTime
, default
=datetime
.datetime
.utcnow
)
1483 updated
= Column(DateTime
, default
=datetime
.datetime
.utcnow
)
1484 object_type
= Column(Unicode
, nullable
=False)
1486 deletion_mode
= Base
.SOFT_DELETE
1489 return "<{klass} {name}>".format(
1490 klass
=self
.__class
__.__name
__,
1494 def serialize(self
, request
):
1495 href
= request
.urlgen(
1496 "mediagoblin.api.object",
1497 object_type
=self
.object_type
,
1501 published
= UTC
.localize(self
.published
)
1502 updated
= UTC
.localize(self
.updated
)
1505 "displayName": self
.name
,
1506 "published": published
.isoformat(),
1507 "updated": updated
.isoformat(),
1508 "objectType": self
.object_type
,
1511 def unserialize(self
, data
):
1512 if "displayName" in data
:
1513 self
.name
= data
["displayName"]
1515 class Activity(Base
, ActivityMixin
):
1517 This holds all the metadata about an activity such as uploading an image,
1518 posting a comment, etc.
1520 __tablename__
= "core__activities"
1522 id = Column(Integer
, primary_key
=True)
1523 public_id
= Column(Unicode
, unique
=True)
1524 actor
= Column(Integer
,
1525 ForeignKey("core__users.id"),
1527 published
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1528 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1530 verb
= Column(Unicode
, nullable
=False)
1531 content
= Column(Unicode
, nullable
=True)
1532 title
= Column(Unicode
, nullable
=True)
1533 generator
= Column(Integer
,
1534 ForeignKey("core__generators.id"),
1537 # Create the generic foreign keys for the object
1538 object_id
= Column(Integer
, ForeignKey(GenericModelReference
.id), nullable
=False)
1539 object_helper
= relationship(GenericModelReference
, foreign_keys
=[object_id
])
1540 object = association_proxy("object_helper", "get_object",
1541 creator
=GenericModelReference
.find_or_new
)
1543 # Create the generic foreign Key for the target
1544 target_id
= Column(Integer
, ForeignKey(GenericModelReference
.id), nullable
=True)
1545 target_helper
= relationship(GenericModelReference
, foreign_keys
=[target_id
])
1546 target
= association_proxy("target_helper", "get_object",
1547 creator
=GenericModelReference
.find_or_new
)
1549 get_actor
= relationship(User
,
1550 backref
=backref("activities",
1551 cascade
="all, delete-orphan"))
1552 get_generator
= relationship(Generator
)
1554 deletion_mode
= Base
.SOFT_DELETE
1557 if self
.content
is None:
1558 return "<{klass} verb:{verb}>".format(
1559 klass
=self
.__class
__.__name
__,
1563 return "<{klass} {content}>".format(
1564 klass
=self
.__class
__.__name
__,
1565 content
=self
.content
1568 def save(self
, set_updated
=True, *args
, **kwargs
):
1570 self
.updated
= datetime
.datetime
.now()
1571 super(Activity
, self
).save(*args
, **kwargs
)
1573 class Graveyard(Base
):
1574 """ Where models come to die """
1575 __tablename__
= "core__graveyard"
1577 id = Column(Integer
, primary_key
=True)
1578 public_id
= Column(Unicode
, nullable
=True, unique
=True)
1580 deleted
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.utcnow
)
1581 object_type
= Column(Unicode
, nullable
=False)
1583 # This could either be a deleted actor or a real actor, this must be
1584 # nullable as it we shouldn't have it set for deleted actor
1585 actor_id
= Column(Integer
, ForeignKey(GenericModelReference
.id))
1586 actor_helper
= relationship(GenericModelReference
)
1587 actor
= association_proxy("actor_helper", "get_object",
1588 creator
=GenericModelReference
.find_or_new
)
1591 return "<{klass} deleted {obj_type}>".format(
1592 klass
=type(self
).__name
__,
1593 obj_type
=self
.object_type
1596 def serialize(self
, request
):
1597 deleted
= UTC
.localize(self
.deleted
).isoformat()
1599 "id": self
.public_id
,
1600 "objectType": self
.object_type
,
1601 "published": deleted
,
1606 if self
.actor_id
is not None:
1607 context
["actor"] = self
.actor().serialize(request
)
1611 LocalUser
, RemoteUser
, User
, MediaEntry
, Tag
, MediaTag
, Comment
, TextComment
,
1612 Collection
, CollectionItem
, MediaFile
, FileKeynames
, MediaAttachmentFile
,
1613 ProcessingMetaData
, Notification
, Client
, CommentSubscription
, Report
,
1614 UserBan
, Privilege
, PrivilegeUserAssociation
, RequestToken
, AccessToken
,
1615 NonceTimestamp
, Activity
, Generator
, Location
, GenericModelReference
, Graveyard
]
1618 Foundations are the default rows that are created immediately after the tables
1619 are initialized. Each entry to this dictionary should be in the format of:
1620 ModelConstructorObject:List of Dictionaries
1621 (Each Dictionary represents a row on the Table to be created, containing each
1622 of the columns' names as a key string, and each of the columns' values as a
1625 ex. [NOTE THIS IS NOT BASED OFF OF OUR USER TABLE]
1626 user_foundations = [{'name':u'Joanna', 'age':24},
1627 {'name':u'Andrea', 'age':41}]
1629 FOUNDATIONS = {User:user_foundations}
1631 privilege_foundations
= [{'privilege_name':u
'admin'},
1632 {'privilege_name':u
'moderator'},
1633 {'privilege_name':u
'uploader'},
1634 {'privilege_name':u
'reporter'},
1635 {'privilege_name':u
'commenter'},
1636 {'privilege_name':u
'active'}]
1637 FOUNDATIONS
= {Privilege
:privilege_foundations
}
1639 ######################################################
1640 # Special, migrations-tracking table
1642 # Not listed in MODELS because this is special and not
1643 # really migrated, but used for migrations (for now)
1644 ######################################################
1646 class MigrationData(Base
):
1647 __tablename__
= "core__migrations"
1649 name
= Column(Unicode
, primary_key
=True)
1650 version
= Column(Integer
, nullable
=False, default
=0)
1652 ######################################################
1655 def show_table_init(engine_uri
):
1656 if engine_uri
is None:
1657 engine_uri
= 'sqlite:///:memory:'
1658 from sqlalchemy
import create_engine
1659 engine
= create_engine(engine_uri
, echo
=True)
1661 Base
.metadata
.create_all(engine
)
1664 if __name__
== '__main__':
1665 from sys
import argv
1671 show_table_init(uri
)