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 mongokit
import Document
21 from mediagoblin
.db
.mongo
import migrations
22 from mediagoblin
.db
.mongo
.util
import ASCENDING
, DESCENDING
, ObjectId
23 from mediagoblin
.tools
.pagination
import Pagination
24 from mediagoblin
.db
.mixin
import UserMixin
, MediaEntryMixin
, MediaCommentMixin
27 class MongoPK(object):
28 """An alias for the _id primary key"""
29 def __get__(self
, instance
, cls
):
30 return instance
['_id']
31 def __set__(self
, instance
, val
):
33 def __delete__(self
, instance
):
46 class User(Document
, UserMixin
):
48 A user of MediaGoblin.
51 - username: The username of this user, should be unique to this instance.
52 - email: Email address of this user
53 - created: When the user was created
54 - plugin_data: a mapping of extra plugin information for this User.
55 Nothing uses this yet as we don't have plugins, but someday we
57 - pw_hash: Hashed version of user's password.
58 - email_verified: Whether or not the user has verified their email or not.
59 Most parts of the site are disabled for users who haven't yet.
60 - status: whether or not the user is active, etc. Currently only has two
61 values, 'needs_email_verification' or 'active'. (In the future, maybe
62 we'll change this to a boolean with a key of 'active' and have a
63 separate field for a reason the user's been disabled if that's
64 appropriate... email_verified is already separate, after all.)
65 - verification_key: If the user is awaiting email verification, the user
66 will have to provide this key (which will be encoded in the presented
67 URL) in order to confirm their email as active.
68 - is_admin: Whether or not this user is an administrator or not.
69 - url: this user's personal webpage/website, if appropriate.
70 - bio: biography of this user (plaintext, in markdown)
72 __collection__
= 'users'
73 use_dot_notation
= True
78 'created': datetime
.datetime
,
79 'plugin_data': dict, # plugins can dump stuff here.
81 'email_verified': bool,
83 'verification_key': unicode,
86 'bio': unicode, # May contain markdown
87 'fp_verification_key': unicode, # forgotten password verification key
88 'fp_token_expire': datetime
.datetime
,
91 required_fields
= ['username', 'created', 'pw_hash', 'email']
94 'created': datetime
.datetime
.utcnow
,
95 'email_verified': False,
96 'status': u
'needs_email_verification',
102 class MediaEntry(Document
, MediaEntryMixin
):
104 Record of a piece of media.
107 - uploader: A reference to a User who uploaded this.
109 - title: Title of this work
111 - slug: A normalized "slug" which can be used as part of a URL to retrieve
112 this work, such as 'my-works-name-in-slug-form' may be viewable by
113 'http://mg.example.org/u/username/m/my-works-name-in-slug-form/'
114 Note that since URLs are constructed this way, slugs must be unique
115 per-uploader. (An index is provided to enforce that but code should be
116 written on the python side to ensure this as well.)
118 - created: Date and time of when this piece of work was uploaded.
120 - description: Uploader-set description of this work. This can be marked
121 up with MarkDown for slight fanciness (links, boldness, italics,
124 - media_type: What type of media is this? Currently we only support
127 - media_data: Extra information that's media-format-dependent.
128 For example, images might contain some EXIF data that's not appropriate
129 to other formats. You might store it like:
131 mediaentry.media_data['exif'] = {
132 'manufacturer': 'CASIO',
134 'exposure_time': .659}
136 Alternately for video you might store:
138 # play length in seconds
139 mediaentry.media_data['play_length'] = 340
141 ... so what's appropriate here really depends on the media type.
143 - plugin_data: a mapping of extra plugin information for this User.
144 Nothing uses this yet as we don't have plugins, but someday we
147 - tags: A list of tags. Each tag is stored as a dictionary that has a key
148 for the actual name and the normalized name-as-slug, so ultimately this
150 [{'name': 'Gully Gardens',
151 'slug': 'gully-gardens'},
152 {'name': 'Castle Adventure Time?!",
153 'slug': 'castle-adventure-time'}]
155 - state: What's the state of this file? Active, inactive, disabled, etc...
156 But really for now there are only two states:
157 "unprocessed": uploaded but needs to go through processing for display
158 "processed": processed and able to be displayed
160 - license: URI for media's license.
162 - queued_media_file: storage interface style filepath describing a file
163 queued for processing. This is stored in the mg_globals.queue_store
166 - queued_task_id: celery task id. Use this to fetch the task state.
168 - media_files: Files relevant to this that have actually been processed
169 and are available for various types of display. Stored like:
170 {'thumb': ['dir1', 'dir2', 'pic.png'}
172 - attachment_files: A list of "attachment" files, ones that aren't
173 critical to this piece of media but may be usefully relevant to people
174 viewing the work. (currently unused.)
176 - fail_error: path to the exception raised
179 __collection__
= 'media_entries'
180 use_dot_notation
= True
183 'uploader': ObjectId
,
186 'created': datetime
.datetime
,
187 'description': unicode, # May contain markdown/up
188 'media_type': unicode,
189 'media_data': dict, # extra data relevant to this media_type
190 'plugin_data': dict, # plugins can dump stuff here.
195 # For now let's assume there can only be one main file queued
197 'queued_media_file': [unicode],
198 'queued_task_id': unicode,
200 # A dictionary of logical names to filepaths
203 # The following should be lists of lists, in appropriate file
205 'attachment_files': list,
207 # If things go badly in processing things, we'll store that
209 'fail_error': unicode,
210 'fail_metadata': dict}
213 'uploader', 'created', 'media_type', 'slug']
216 'created': datetime
.datetime
.utcnow
,
217 'state': u
'unprocessed'}
221 def media_data_init(self
, **kwargs
):
222 self
.media_data
.update(kwargs
)
224 def get_comments(self
, ascending
=False):
230 return self
.db
.MediaComment
.find({
231 'media_entry': self
._id
}).sort('created', order
)
233 def url_to_prev(self
, urlgen
):
235 Provide a url to the previous entry from this user, if there is one
237 cursor
= self
.db
.MediaEntry
.find({'_id': {"$gt": self
._id
},
238 'uploader': self
.uploader
,
239 'state': 'processed'}).sort(
240 '_id', ASCENDING
).limit(1)
242 return media
.url_for_self(urlgen
)
244 def url_to_next(self
, urlgen
):
246 Provide a url to the next entry from this user, if there is one
248 cursor
= self
.db
.MediaEntry
.find({'_id': {"$lt": self
._id
},
249 'uploader': self
.uploader
,
250 'state': 'processed'}).sort(
251 '_id', DESCENDING
).limit(1)
254 return media
.url_for_self(urlgen
)
257 def get_uploader(self
):
258 return self
.db
.User
.find_one({'_id': self
.uploader
})
261 class MediaComment(Document
, MediaCommentMixin
):
263 A comment on a MediaEntry.
266 - media_entry: The media entry this comment is attached to
267 - author: user who posted this comment
268 - created: when the comment was created
269 - content: plaintext (but markdown'able) version of the comment's content.
272 __collection__
= 'media_comments'
273 use_dot_notation
= True
276 'media_entry': ObjectId
,
278 'created': datetime
.datetime
,
283 'media_entry', 'author', 'created', 'content']
286 'created': datetime
.datetime
.utcnow
}
288 def media_entry(self
):
289 return self
.db
.MediaEntry
.find_one({'_id': self
['media_entry']})
292 def get_author(self
):
293 return self
.db
.User
.find_one({'_id': self
['author']})
302 def register_models(connection
):
304 Register all models in REGISTER_MODELS with this connection.
306 connection
.register(REGISTER_MODELS
)