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.
24 from sqlalchemy
import Column
, Integer
, Unicode
, UnicodeText
, DateTime
, \
25 Boolean
, ForeignKey
, UniqueConstraint
, PrimaryKeyConstraint
, \
27 from sqlalchemy
.orm
import relationship
, backref
, with_polymorphic
28 from sqlalchemy
.orm
.collections
import attribute_mapped_collection
29 from sqlalchemy
.sql
.expression
import desc
30 from sqlalchemy
.ext
.associationproxy
import association_proxy
31 from sqlalchemy
.util
import memoized_property
34 from mediagoblin
.db
.extratypes
import PathTupleWithSlashes
, JSONEncoded
35 from mediagoblin
.db
.base
import Base
, DictReadAttrProxy
36 from mediagoblin
.db
.mixin
import UserMixin
, MediaEntryMixin
, \
37 MediaCommentMixin
, CollectionMixin
, CollectionItemMixin
38 from mediagoblin
.tools
.files
import delete_media_files
39 from mediagoblin
.tools
.common
import import_component
41 # It's actually kind of annoying how sqlalchemy-migrate does this, if
42 # I understand it right, but whatever. Anyway, don't remove this :P
44 # We could do migration calls more manually instead of relying on
45 # this import-based meddling...
46 from migrate
import changeset
48 _log
= logging
.getLogger(__name__
)
51 class User(Base
, UserMixin
):
53 TODO: We should consider moving some rarely used fields
54 into some sort of "shadow" table.
56 __tablename__
= "core__users"
58 id = Column(Integer
, primary_key
=True)
59 username
= Column(Unicode
, nullable
=False, unique
=True)
60 # Note: no db uniqueness constraint on email because it's not
61 # reliable (many email systems case insensitive despite against
62 # the RFC) and because it would be a mess to implement at this
64 email
= Column(Unicode
, nullable
=False)
65 pw_hash
= Column(Unicode
)
66 #--column email_verified is VESTIGIAL with privileges and should not be used---
67 #--should be dropped ASAP though a bug in sqlite3 prevents this atm------------
68 email_verified
= Column(Boolean
, default
=False)
69 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
70 # Intented to be nullable=False, but migrations would not work for it
71 # set to nullable=True implicitly.
72 wants_comment_notification
= Column(Boolean
, default
=True)
73 wants_notifications
= Column(Boolean
, default
=True)
74 license_preference
= Column(Unicode
)
75 #--column admin is VESTIGIAL with privileges and should not be used------------
76 #--should be dropped ASAP though a bug in sqlite3 prevents this atm------------
77 is_admin
= Column(Boolean
, default
=False, nullable
=False)
79 bio
= Column(UnicodeText
) # ??
82 # plugin data would be in a separate model
85 return '<{0} #{1} {2} {3} "{4}">'.format(
86 self
.__class
__.__name
__,
88 'verified' if self
.has_privilege(u
'active') else 'non-verified',
89 'admin' if self
.has_privilege(u
'admin') else 'user',
92 def delete(self
, **kwargs
):
93 """Deletes a User and all related entries/comments/files/..."""
94 # Collections get deleted by relationships.
96 media_entries
= MediaEntry
.query
.filter(MediaEntry
.uploader
== self
.id)
97 for media
in media_entries
:
98 # TODO: Make sure that "MediaEntry.delete()" also deletes
99 # all related files/Comments
100 media
.delete(del_orphan_tags
=False, commit
=False)
102 # Delete now unused tags
103 # TODO: import here due to cyclic imports!!! This cries for refactoring
104 from mediagoblin
.db
.util
import clean_orphan_tags
105 clean_orphan_tags(commit
=False)
107 # Delete user, pass through commit=False/True in kwargs
108 super(User
, self
).delete(**kwargs
)
109 _log
.info('Deleted user "{0}" account'.format(self
.username
))
111 def has_privilege(self
,*priv_names
):
113 This method checks to make sure a user has all the correct privileges
114 to access a piece of content.
116 :param priv_names A variable number of unicode objects which rep-
117 -resent the different privileges which may give
118 the user access to this content. If you pass
119 multiple arguments, the user will be granted
120 access if they have ANY of the privileges
123 if len(priv_names
) == 1:
124 priv
= Privilege
.query
.filter(
125 Privilege
.privilege_name
==priv_names
[0]).one()
126 return (priv
in self
.all_privileges
)
127 elif len(priv_names
) > 1:
128 return self
.has_privilege(priv_names
[0]) or \
129 self
.has_privilege(*priv_names
[1:])
134 Checks if this user is banned.
136 :returns True if self is banned
137 :returns False if self is not
139 return UserBan
.query
.get(self
.id) is not None
144 Model representing a client - Used for API Auth
146 __tablename__
= "core__clients"
148 id = Column(Unicode
, nullable
=True, primary_key
=True)
149 secret
= Column(Unicode
, nullable
=False)
150 expirey
= Column(DateTime
, nullable
=True)
151 application_type
= Column(Unicode
, nullable
=False)
152 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
153 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
156 redirect_uri
= Column(JSONEncoded
, nullable
=True)
157 logo_url
= Column(Unicode
, nullable
=True)
158 application_name
= Column(Unicode
, nullable
=True)
159 contacts
= Column(JSONEncoded
, nullable
=True)
162 if self
.application_name
:
163 return "<Client {0} - {1}>".format(self
.application_name
, self
.id)
165 return "<Client {0}>".format(self
.id)
167 class RequestToken(Base
):
169 Model for representing the request tokens
171 __tablename__
= "core__request_tokens"
173 token
= Column(Unicode
, primary_key
=True)
174 secret
= Column(Unicode
, nullable
=False)
175 client
= Column(Unicode
, ForeignKey(Client
.id))
176 user
= Column(Integer
, ForeignKey(User
.id), nullable
=True)
177 used
= Column(Boolean
, default
=False)
178 authenticated
= Column(Boolean
, default
=False)
179 verifier
= Column(Unicode
, nullable
=True)
180 callback
= Column(Unicode
, nullable
=False, default
=u
"oob")
181 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
182 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
184 class AccessToken(Base
):
186 Model for representing the access tokens
188 __tablename__
= "core__access_tokens"
190 token
= Column(Unicode
, nullable
=False, primary_key
=True)
191 secret
= Column(Unicode
, nullable
=False)
192 user
= Column(Integer
, ForeignKey(User
.id))
193 request_token
= Column(Unicode
, ForeignKey(RequestToken
.token
))
194 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
195 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
198 class NonceTimestamp(Base
):
200 A place the timestamp and nonce can be stored - this is for OAuth1
202 __tablename__
= "core__nonce_timestamps"
204 nonce
= Column(Unicode
, nullable
=False, primary_key
=True)
205 timestamp
= Column(DateTime
, nullable
=False, primary_key
=True)
208 class MediaEntry(Base
, MediaEntryMixin
):
210 TODO: Consider fetching the media_files using join
212 __tablename__
= "core__media_entries"
214 id = Column(Integer
, primary_key
=True)
215 uploader
= Column(Integer
, ForeignKey(User
.id), nullable
=False, index
=True)
216 title
= Column(Unicode
, nullable
=False)
217 slug
= Column(Unicode
)
218 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
,
220 description
= Column(UnicodeText
) # ??
221 media_type
= Column(Unicode
, nullable
=False)
222 state
= Column(Unicode
, default
=u
'unprocessed', nullable
=False)
223 # or use sqlalchemy.types.Enum?
224 license
= Column(Unicode
)
225 collected
= Column(Integer
, default
=0)
227 fail_error
= Column(Unicode
)
228 fail_metadata
= Column(JSONEncoded
)
230 transcoding_progress
= Column(SmallInteger
)
232 queued_media_file
= Column(PathTupleWithSlashes
)
234 queued_task_id
= Column(Unicode
)
237 UniqueConstraint('uploader', 'slug'),
240 get_uploader
= relationship(User
)
242 media_files_helper
= relationship("MediaFile",
243 collection_class
=attribute_mapped_collection("name"),
244 cascade
="all, delete-orphan"
246 media_files
= association_proxy('media_files_helper', 'file_path',
247 creator
=lambda k
, v
: MediaFile(name
=k
, file_path
=v
)
250 attachment_files_helper
= relationship("MediaAttachmentFile",
251 cascade
="all, delete-orphan",
252 order_by
="MediaAttachmentFile.created"
254 attachment_files
= association_proxy("attachment_files_helper", "dict_view",
255 creator
=lambda v
: MediaAttachmentFile(
256 name
=v
["name"], filepath
=v
["filepath"])
259 tags_helper
= relationship("MediaTag",
260 cascade
="all, delete-orphan" # should be automatically deleted
262 tags
= association_proxy("tags_helper", "dict_view",
263 creator
=lambda v
: MediaTag(name
=v
["name"], slug
=v
["slug"])
266 collections_helper
= relationship("CollectionItem",
267 cascade
="all, delete-orphan"
269 collections
= association_proxy("collections_helper", "in_collection")
274 def get_comments(self
, ascending
=False):
275 order_col
= MediaComment
.created
277 order_col
= desc(order_col
)
278 return self
.all_comments
.order_by(order_col
)
280 def url_to_prev(self
, urlgen
):
281 """get the next 'newer' entry by this user"""
282 media
= MediaEntry
.query
.filter(
283 (MediaEntry
.uploader
== self
.uploader
)
284 & (MediaEntry
.state
== u
'processed')
285 & (MediaEntry
.id > self
.id)).order_by(MediaEntry
.id).first()
287 if media
is not None:
288 return media
.url_for_self(urlgen
)
290 def url_to_next(self
, urlgen
):
291 """get the next 'older' entry by this user"""
292 media
= MediaEntry
.query
.filter(
293 (MediaEntry
.uploader
== self
.uploader
)
294 & (MediaEntry
.state
== u
'processed')
295 & (MediaEntry
.id < self
.id)).order_by(desc(MediaEntry
.id)).first()
297 if media
is not None:
298 return media
.url_for_self(urlgen
)
301 def media_data(self
):
302 return getattr(self
, self
.media_data_ref
)
304 def media_data_init(self
, **kwargs
):
306 Initialize or update the contents of a media entry's media_data row
308 media_data
= self
.media_data
310 if media_data
is None:
311 # Get the correct table:
312 table
= import_component(self
.media_type
+ '.models:DATA_MODEL')
313 # No media data, so actually add a new one
314 media_data
= table(**kwargs
)
315 # Get the relationship set up.
316 media_data
.get_media_entry
= self
318 # Update old media data
319 for field
, value
in kwargs
.iteritems():
320 setattr(media_data
, field
, value
)
323 def media_data_ref(self
):
324 return import_component(self
.media_type
+ '.models:BACKREF_NAME')
327 safe_title
= self
.title
.encode('ascii', 'replace')
329 return '<{classname} {id}: {title}>'.format(
330 classname
=self
.__class
__.__name
__,
334 def delete(self
, del_orphan_tags
=True, **kwargs
):
335 """Delete MediaEntry and all related files/attachments/comments
337 This will *not* automatically delete unused collections, which
340 :param del_orphan_tags: True/false if we delete unused Tags too
341 :param commit: True/False if this should end the db transaction"""
342 # User's CollectionItems are automatically deleted via "cascade".
343 # Comments on this Media are deleted by cascade, hopefully.
345 # Delete all related files/attachments
347 delete_media_files(self
)
348 except OSError, error
:
349 # Returns list of files we failed to delete
350 _log
.error('No such files from the user "{1}" to delete: '
351 '{0}'.format(str(error
), self
.get_uploader
))
352 _log
.info('Deleted Media entry id "{0}"'.format(self
.id))
353 # Related MediaTag's are automatically cleaned, but we might
354 # want to clean out unused Tag's too.
356 # TODO: Import here due to cyclic imports!!!
357 # This cries for refactoring
358 from mediagoblin
.db
.util
import clean_orphan_tags
359 clean_orphan_tags(commit
=False)
360 # pass through commit=False/True in kwargs
361 super(MediaEntry
, self
).delete(**kwargs
)
364 class FileKeynames(Base
):
366 keywords for various places.
367 currently the MediaFile keys
369 __tablename__
= "core__file_keynames"
370 id = Column(Integer
, primary_key
=True)
371 name
= Column(Unicode
, unique
=True)
374 return "<FileKeyname %r: %r>" % (self
.id, self
.name
)
377 def find_or_new(cls
, name
):
378 t
= cls
.query
.filter_by(name
=name
).first()
381 return cls(name
=name
)
384 class MediaFile(Base
):
386 TODO: Highly consider moving "name" into a new table.
387 TODO: Consider preloading said table in software
389 __tablename__
= "core__mediafiles"
391 media_entry
= Column(
392 Integer
, ForeignKey(MediaEntry
.id),
394 name_id
= Column(SmallInteger
, ForeignKey(FileKeynames
.id), nullable
=False)
395 file_path
= Column(PathTupleWithSlashes
)
398 PrimaryKeyConstraint('media_entry', 'name_id'),
402 return "<MediaFile %s: %r>" % (self
.name
, self
.file_path
)
404 name_helper
= relationship(FileKeynames
, lazy
="joined", innerjoin
=True)
405 name
= association_proxy('name_helper', 'name',
406 creator
=FileKeynames
.find_or_new
410 class MediaAttachmentFile(Base
):
411 __tablename__
= "core__attachment_files"
413 id = Column(Integer
, primary_key
=True)
414 media_entry
= Column(
415 Integer
, ForeignKey(MediaEntry
.id),
417 name
= Column(Unicode
, nullable
=False)
418 filepath
= Column(PathTupleWithSlashes
)
419 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
423 """A dict like view on this object"""
424 return DictReadAttrProxy(self
)
428 __tablename__
= "core__tags"
430 id = Column(Integer
, primary_key
=True)
431 slug
= Column(Unicode
, nullable
=False, unique
=True)
434 return "<Tag %r: %r>" % (self
.id, self
.slug
)
437 def find_or_new(cls
, slug
):
438 t
= cls
.query
.filter_by(slug
=slug
).first()
441 return cls(slug
=slug
)
444 class MediaTag(Base
):
445 __tablename__
= "core__media_tags"
447 id = Column(Integer
, primary_key
=True)
448 media_entry
= Column(
449 Integer
, ForeignKey(MediaEntry
.id),
450 nullable
=False, index
=True)
451 tag
= Column(Integer
, ForeignKey(Tag
.id), nullable
=False, index
=True)
452 name
= Column(Unicode
)
453 # created = Column(DateTime, nullable=False, default=datetime.datetime.now)
456 UniqueConstraint('tag', 'media_entry'),
459 tag_helper
= relationship(Tag
)
460 slug
= association_proxy('tag_helper', 'slug',
461 creator
=Tag
.find_or_new
464 def __init__(self
, name
=None, slug
=None):
469 self
.tag_helper
= Tag
.find_or_new(slug
)
473 """A dict like view on this object"""
474 return DictReadAttrProxy(self
)
477 class MediaComment(Base
, MediaCommentMixin
):
478 __tablename__
= "core__media_comments"
480 id = Column(Integer
, primary_key
=True)
481 media_entry
= Column(
482 Integer
, ForeignKey(MediaEntry
.id), nullable
=False, index
=True)
483 author
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
484 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
485 content
= Column(UnicodeText
, nullable
=False)
487 # Cascade: Comments are owned by their creator. So do the full thing.
488 # lazy=dynamic: People might post a *lot* of comments,
489 # so make the "posted_comments" a query-like thing.
490 get_author
= relationship(User
,
491 backref
=backref("posted_comments",
493 cascade
="all, delete-orphan"))
494 get_entry
= relationship(MediaEntry
,
495 backref
=backref("comments",
497 cascade
="all, delete-orphan"))
499 # Cascade: Comments are somewhat owned by their MediaEntry.
500 # So do the full thing.
501 # lazy=dynamic: MediaEntries might have many comments,
502 # so make the "all_comments" a query-like thing.
503 get_media_entry
= relationship(MediaEntry
,
504 backref
=backref("all_comments",
506 cascade
="all, delete-orphan"))
509 class Collection(Base
, CollectionMixin
):
510 """An 'album' or 'set' of media by a user.
512 On deletion, contained CollectionItems get automatically reaped via
514 __tablename__
= "core__collections"
516 id = Column(Integer
, primary_key
=True)
517 title
= Column(Unicode
, nullable
=False)
518 slug
= Column(Unicode
)
519 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
,
521 description
= Column(UnicodeText
)
522 creator
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
523 # TODO: No of items in Collection. Badly named, can we migrate to num_items?
524 items
= Column(Integer
, default
=0)
526 # Cascade: Collections are owned by their creator. So do the full thing.
527 get_creator
= relationship(User
,
528 backref
=backref("collections",
529 cascade
="all, delete-orphan"))
532 UniqueConstraint('creator', 'slug'),
535 def get_collection_items(self
, ascending
=False):
536 #TODO, is this still needed with self.collection_items being available?
537 order_col
= CollectionItem
.position
539 order_col
= desc(order_col
)
540 return CollectionItem
.query
.filter_by(
541 collection
=self
.id).order_by(order_col
)
544 class CollectionItem(Base
, CollectionItemMixin
):
545 __tablename__
= "core__collection_items"
547 id = Column(Integer
, primary_key
=True)
548 media_entry
= Column(
549 Integer
, ForeignKey(MediaEntry
.id), nullable
=False, index
=True)
550 collection
= Column(Integer
, ForeignKey(Collection
.id), nullable
=False)
551 note
= Column(UnicodeText
, nullable
=True)
552 added
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
553 position
= Column(Integer
)
555 # Cascade: CollectionItems are owned by their Collection. So do the full thing.
556 in_collection
= relationship(Collection
,
559 cascade
="all, delete-orphan"))
561 get_media_entry
= relationship(MediaEntry
)
564 UniqueConstraint('collection', 'media_entry'),
569 """A dict like view on this object"""
570 return DictReadAttrProxy(self
)
573 class ProcessingMetaData(Base
):
574 __tablename__
= 'core__processing_metadata'
576 id = Column(Integer
, primary_key
=True)
577 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=False,
579 media_entry
= relationship(MediaEntry
,
580 backref
=backref('processing_metadata',
581 cascade
='all, delete-orphan'))
582 callback_url
= Column(Unicode
)
586 """A dict like view on this object"""
587 return DictReadAttrProxy(self
)
590 class CommentSubscription(Base
):
591 __tablename__
= 'core__comment_subscriptions'
592 id = Column(Integer
, primary_key
=True)
594 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
596 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=False)
597 media_entry
= relationship(MediaEntry
,
598 backref
=backref('comment_subscriptions',
599 cascade
='all, delete-orphan'))
601 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
602 user
= relationship(User
,
603 backref
=backref('comment_subscriptions',
604 cascade
='all, delete-orphan'))
606 notify
= Column(Boolean
, nullable
=False, default
=True)
607 send_email
= Column(Boolean
, nullable
=False, default
=True)
610 return ('<{classname} #{id}: {user} {media} notify: '
611 '{notify} email: {email}>').format(
613 classname
=self
.__class
__.__name
__,
615 media
=self
.media_entry
,
617 email
=self
.send_email
)
620 class Notification(Base
):
621 __tablename__
= 'core__notifications'
622 id = Column(Integer
, primary_key
=True)
623 type = Column(Unicode
)
625 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
627 user_id
= Column(Integer
, ForeignKey('core__users.id'), nullable
=False,
629 seen
= Column(Boolean
, default
=lambda: False, index
=True)
632 backref
=backref('notifications', cascade
='all, delete-orphan'))
635 'polymorphic_identity': 'notification',
636 'polymorphic_on': type
640 return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
642 klass
=self
.__class
__.__name
__,
644 subject
=getattr(self
, 'subject', None),
645 seen
='unseen' if not self
.seen
else 'seen')
648 class CommentNotification(Notification
):
649 __tablename__
= 'core__comment_notifications'
650 id = Column(Integer
, ForeignKey(Notification
.id), primary_key
=True)
652 subject_id
= Column(Integer
, ForeignKey(MediaComment
.id))
653 subject
= relationship(
655 backref
=backref('comment_notifications', cascade
='all, delete-orphan'))
658 'polymorphic_identity': 'comment_notification'
662 class ProcessingNotification(Notification
):
663 __tablename__
= 'core__processing_notifications'
665 id = Column(Integer
, ForeignKey(Notification
.id), primary_key
=True)
667 subject_id
= Column(Integer
, ForeignKey(MediaEntry
.id))
668 subject
= relationship(
670 backref
=backref('processing_notifications',
671 cascade
='all, delete-orphan'))
674 'polymorphic_identity': 'processing_notification'
679 [ProcessingNotification
, CommentNotification
])
681 class ReportBase(Base
):
683 This is the basic report object which the other reports are based off of.
685 :keyword reporter_id Holds the id of the user who created
686 the report, as an Integer column.
687 :keyword report_content Hold the explanation left by the repor-
688 -ter to indicate why they filed the
689 report in the first place, as a
691 :keyword reported_user_id Holds the id of the user who created
692 the content which was reported, as
694 :keyword created Holds a datetime column of when the re-
696 :keyword discriminator This column distinguishes between the
697 different types of reports.
698 :keyword resolver_id Holds the id of the moderator/admin who
700 :keyword resolved Holds the DateTime object which descri-
701 -bes when this report was resolved
702 :keyword result Holds the UnicodeText column of the
703 resolver's reasons for resolving
704 the report this way. Some of this
707 __tablename__
= 'core__reports'
708 id = Column(Integer
, primary_key
=True)
709 reporter_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
710 reporter
= relationship(
712 backref
=backref("reports_filed_by",
714 cascade
="all, delete-orphan"),
715 primaryjoin
="User.id==ReportBase.reporter_id")
716 report_content
= Column(UnicodeText
)
717 reported_user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
718 reported_user
= relationship(
720 backref
=backref("reports_filed_on",
722 cascade
="all, delete-orphan"),
723 primaryjoin
="User.id==ReportBase.reported_user_id")
724 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now())
725 discriminator
= Column('type', Unicode(50))
726 resolver_id
= Column(Integer
, ForeignKey(User
.id))
727 resolver
= relationship(
729 backref
=backref("reports_resolved_by",
731 cascade
="all, delete-orphan"),
732 primaryjoin
="User.id==ReportBase.resolver_id")
734 resolved
= Column(DateTime
)
735 result
= Column(UnicodeText
)
736 __mapper_args__
= {'polymorphic_on': discriminator
}
738 def is_comment_report(self
):
739 return self
.discriminator
=='comment_report'
741 def is_media_entry_report(self
):
742 return self
.discriminator
=='media_report'
744 def is_archived_report(self
):
745 return self
.resolved
is not None
747 def archive(self
,resolver_id
, resolved
, result
):
748 self
.resolver_id
= resolver_id
749 self
.resolved
= resolved
753 class CommentReport(ReportBase
):
755 Reports that have been filed on comments.
756 :keyword comment_id Holds the integer value of the reported
759 __tablename__
= 'core__reports_on_comments'
760 __mapper_args__
= {'polymorphic_identity': 'comment_report'}
762 id = Column('id',Integer
, ForeignKey('core__reports.id'),
764 comment_id
= Column(Integer
, ForeignKey(MediaComment
.id), nullable
=True)
765 comment
= relationship(
766 MediaComment
, backref
=backref("reports_filed_on",
770 class MediaReport(ReportBase
):
772 Reports that have been filed on media entries
773 :keyword media_entry_id Holds the integer value of the reported
776 __tablename__
= 'core__reports_on_media'
777 __mapper_args__
= {'polymorphic_identity': 'media_report'}
779 id = Column('id',Integer
, ForeignKey('core__reports.id'),
781 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=True)
782 media_entry
= relationship(
784 backref
=backref("reports_filed_on",
789 Holds the information on a specific user's ban-state. As long as one of
790 these is attached to a user, they are banned from accessing mediagoblin.
791 When they try to log in, they are greeted with a page that tells them
792 the reason why they are banned and when (if ever) the ban will be
795 :keyword user_id Holds the id of the user this object is
796 attached to. This is a one-to-one
798 :keyword expiration_date Holds the date that the ban will be lifted.
799 If this is null, the ban is permanent
800 unless a moderator manually lifts it.
801 :keyword reason Holds the reason why the user was banned.
803 __tablename__
= 'core__user_bans'
805 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False,
807 expiration_date
= Column(Date
)
808 reason
= Column(UnicodeText
, nullable
=False)
811 class Privilege(Base
):
813 The Privilege table holds all of the different privileges a user can hold.
814 If a user 'has' a privilege, the User object is in a relationship with the
817 :keyword privilege_name Holds a unicode object that is the recognizable
818 name of this privilege. This is the column
819 used for identifying whether or not a user
820 has a necessary privilege or not.
823 __tablename__
= 'core__privileges'
825 id = Column(Integer
, nullable
=False, primary_key
=True)
826 privilege_name
= Column(Unicode
, nullable
=False, unique
=True)
827 all_users
= relationship(
829 backref
='all_privileges',
830 secondary
="core__privileges_users")
832 def __init__(self
, privilege_name
):
834 Currently consructors are required for tables that are initialized thru
835 the FOUNDATIONS system. This is because they need to be able to be con-
836 -structed by a list object holding their arg*s
838 self
.privilege_name
= privilege_name
841 return "<Privilege %s>" % (self
.privilege_name
)
844 class PrivilegeUserAssociation(Base
):
846 This table holds the many-to-many relationship between User and Privilege
849 __tablename__
= 'core__privileges_users'
851 privilege_id
= Column(
852 'core__privilege_id',
859 ForeignKey(Privilege
.id),
863 User
, MediaEntry
, Tag
, MediaTag
, MediaComment
, Collection
, CollectionItem
,
864 MediaFile
, FileKeynames
, MediaAttachmentFile
, ProcessingMetaData
,
865 Notification
, CommentNotification
, ProcessingNotification
, Client
,
866 CommentSubscription
, ReportBase
, CommentReport
, MediaReport
, UserBan
,
867 Privilege
, PrivilegeUserAssociation
,
868 RequestToken
, AccessToken
, NonceTimestamp
]
871 Foundations are the default rows that are created immediately after the tables
872 are initialized. Each entry to this dictionary should be in the format of:
873 ModelConstructorObject:List of Dictionaries
874 (Each Dictionary represents a row on the Table to be created, containing each
875 of the columns' names as a key string, and each of the columns' values as a
878 ex. [NOTE THIS IS NOT BASED OFF OF OUR USER TABLE]
879 user_foundations = [{'name':u'Joanna', 'age':24},
880 {'name':u'Andrea', 'age':41}]
882 FOUNDATIONS = {User:user_foundations}
884 privilege_foundations
= [{'privilege_name':u
'admin'},
885 {'privilege_name':u
'moderator'},
886 {'privilege_name':u
'uploader'},
887 {'privilege_name':u
'reporter'},
888 {'privilege_name':u
'commenter'},
889 {'privilege_name':u
'active'}]
890 FOUNDATIONS
= {Privilege
:privilege_foundations
}
892 ######################################################
893 # Special, migrations-tracking table
895 # Not listed in MODELS because this is special and not
896 # really migrated, but used for migrations (for now)
897 ######################################################
899 class MigrationData(Base
):
900 __tablename__
= "core__migrations"
902 name
= Column(Unicode
, primary_key
=True)
903 version
= Column(Integer
, nullable
=False, default
=0)
905 ######################################################
908 def show_table_init(engine_uri
):
909 if engine_uri
is None:
910 engine_uri
= 'sqlite:///:memory:'
911 from sqlalchemy
import create_engine
912 engine
= create_engine(engine_uri
, echo
=True)
914 Base
.metadata
.create_all(engine
)
917 if __name__
== '__main__':