1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011 Free Software Foundation, Inc
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
import util
22 from mediagoblin
.auth
import lib
as auth_lib
23 from mediagoblin
import mg_globals
24 from mediagoblin
.db
import migrations
25 from mediagoblin
.db
.util
import ASCENDING
, DESCENDING
, ObjectId
26 from mediagoblin
.util
import Pagination
27 from mediagoblin
.util
import DISPLAY_IMAGE_FETCHING_ORDER
41 A user of MediaGoblin.
44 - username: The username of this user, should be unique to this instance.
45 - email: Email address of this user
46 - created: When the user was created
47 - plugin_data: a mapping of extra plugin information for this User.
48 Nothing uses this yet as we don't have plugins, but someday we
50 - pw_hash: Hashed version of user's password.
51 - email_verified: Whether or not the user has verified their email or not.
52 Most parts of the site are disabled for users who haven't yet.
53 - status: whether or not the user is active, etc. Currently only has two
54 values, 'needs_email_verification' or 'active'. (In the future, maybe
55 we'll change this to a boolean with a key of 'active' and have a
56 separate field for a reason the user's been disabled if that's
57 appropriate... email_verified is already separate, after all.)
58 - verification_key: If the user is awaiting email verification, the user
59 will have to provide this key (which will be encoded in the presented
60 URL) in order to confirm their email as active.
61 - is_admin: Whether or not this user is an administrator or not.
62 - url: this user's personal webpage/website, if appropriate.
63 - bio: biography of this user (plaintext, in markdown)
64 - bio_html: biography of the user converted to proper HTML.
66 __collection__
= 'users'
71 'created': datetime
.datetime
,
72 'plugin_data': dict, # plugins can dump stuff here.
74 'email_verified': bool,
76 'verification_key': unicode,
79 'bio' : unicode, # May contain markdown
80 'bio_html': unicode, # May contain plaintext, or HTML
83 required_fields
= ['username', 'created', 'pw_hash', 'email']
86 'created': datetime
.datetime
.utcnow
,
87 'email_verified': False,
88 'status': u
'needs_email_verification',
89 'verification_key': lambda: unicode(uuid
.uuid4()),
92 def check_login(self
, password
):
94 See if a user can login with this password
96 return auth_lib
.bcrypt_check_password(
97 password
, self
['pw_hash'])
100 class MediaEntry(Document
):
102 Record of a piece of media.
105 - uploader: A reference to a User who uploaded this.
107 - title: Title of this work
109 - slug: A normalized "slug" which can be used as part of a URL to retrieve
110 this work, such as 'my-works-name-in-slug-form' may be viewable by
111 'http://mg.example.org/u/username/m/my-works-name-in-slug-form/'
112 Note that since URLs are constructed this way, slugs must be unique
113 per-uploader. (An index is provided to enforce that but code should be
114 written on the python side to ensure this as well.)
116 - created: Date and time of when this piece of work was uploaded.
118 - description: Uploader-set description of this work. This can be marked
119 up with MarkDown for slight fanciness (links, boldness, italics,
122 - description_html: Rendered version of the description, run through
123 Markdown and cleaned with our cleaning tool.
125 - media_type: What type of media is this? Currently we only support
128 - media_data: Extra information that's media-format-dependent.
129 For example, images might contain some EXIF data that's not appropriate
130 to other formats. You might store it like:
132 mediaentry['media_data']['exif'] = {
133 'manufacturer': 'CASIO',
135 'exposure_time': .659}
137 Alternately for video you might store:
139 # play length in seconds
140 mediaentry['media_data']['play_length'] = 340
142 ... so what's appropriate here really depends on the media type.
144 - plugin_data: a mapping of extra plugin information for this User.
145 Nothing uses this yet as we don't have plugins, but someday we
148 - tags: A list of tags. Each tag is stored as a dictionary that has a key
149 for the actual name and the normalized name-as-slug, so ultimately this
151 [{'name': 'Gully Gardens',
152 'slug': 'gully-gardens'},
153 {'name': 'Castle Adventure Time?!",
154 'slug': 'castle-adventure-time'}]
156 - state: What's the state of this file? Active, inactive, disabled, etc...
157 But really for now there are only two states:
158 "unprocessed": uploaded but needs to go through processing for display
159 "processed": processed and able to be displayed
161 - queued_media_file: storage interface style filepath describing a file
162 queued for processing. This is stored in the mg_globals.queue_store
165 - media_files: Files relevant to this that have actually been processed
166 and are available for various types of display. Stored like:
167 {'thumb': ['dir1', 'dir2', 'pic.png'}
169 - attachment_files: A list of "attachment" files, ones that aren't
170 critical to this piece of media but may be usefully relevant to people
171 viewing the work. (currently unused.)
173 - thumbnail_file: Deprecated... we should remove this ;)
175 __collection__
= 'media_entries'
178 'uploader': ObjectId
,
181 'created': datetime
.datetime
,
182 'description': unicode, # May contain markdown/up
183 'description_html': unicode, # May contain plaintext, or HTML
184 'media_type': unicode,
185 'media_data': dict, # extra data relevant to this media_type
186 'plugin_data': dict, # plugins can dump stuff here.
190 # For now let's assume there can only be one main file queued
192 'queued_media_file': [unicode],
194 # A dictionary of logical names to filepaths
197 # The following should be lists of lists, in appropriate file
199 'attachment_files': list,
201 # This one should just be a single file record
202 'thumbnail_file': [unicode]}
205 'uploader', 'created', 'media_type', 'slug']
208 'created': datetime
.datetime
.utcnow
,
209 'state': u
'unprocessed'}
211 def get_comments(self
):
212 return self
.db
.MediaComment
.find({
213 'media_entry': self
['_id']}).sort('created', DESCENDING
)
215 def get_display_media(self
, media_map
, fetch_order
=DISPLAY_IMAGE_FETCHING_ORDER
):
217 Find the best media for display.
220 - media_map: a dict like
221 {u'image_size': [u'dir1', u'dir2', u'image.jpg']}
222 - fetch_order: the order we should try fetching images in
225 (media_size, media_path)
227 media_sizes
= media_map
.keys()
229 for media_size
in DISPLAY_IMAGE_FETCHING_ORDER
:
230 if media_size
in media_sizes
:
231 return media_map
[media_size
]
233 def main_mediafile(self
):
236 def generate_slug(self
):
237 self
['slug'] = util
.slugify(self
['title'])
239 duplicate
= mg_globals
.database
.media_entries
.find_one(
240 {'slug': self
['slug']})
243 self
['slug'] = "%s-%s" % (self
['_id'], self
['slug'])
245 def url_for_self(self
, urlgen
):
247 Generate an appropriate url for ourselves
249 Use a slug if we have one, else use our '_id'.
251 uploader
= self
.uploader()
255 'mediagoblin.user_pages.media_home',
256 user
=uploader
['username'],
260 'mediagoblin.user_pages.media_home',
261 user
=uploader
['username'],
262 media
=unicode(self
['_id']))
264 def url_to_prev(self
, urlgen
):
266 Provide a url to the previous entry from this user, if there is one
268 cursor
= self
.db
.MediaEntry
.find({'_id' : {"$gt": self
['_id']},
269 'uploader': self
['uploader'],
270 'state': 'processed'}).sort(
271 '_id', ASCENDING
).limit(1)
273 return urlgen('mediagoblin.user_pages.media_home',
274 user
=self
.uploader()['username'],
275 media
=unicode(cursor
[0]['slug']))
277 def url_to_next(self
, urlgen
):
279 Provide a url to the next entry from this user, if there is one
281 cursor
= self
.db
.MediaEntry
.find({'_id' : {"$lt": self
['_id']},
282 'uploader': self
['uploader'],
283 'state': 'processed'}).sort(
284 '_id', DESCENDING
).limit(1)
287 return urlgen('mediagoblin.user_pages.media_home',
288 user
=self
.uploader()['username'],
289 media
=unicode(cursor
[0]['slug']))
292 return self
.db
.User
.find_one({'_id': self
['uploader']})
295 class MediaComment(Document
):
297 A comment on a MediaEntry.
300 - media_entry: The media entry this comment is attached to
301 - author: user who posted this comment
302 - created: when the comment was created
303 - content: plaintext (but markdown'able) version of the comment's content.
304 - content_html: the actual html-rendered version of the comment displayed.
305 Run through Markdown and the HTML cleaner.
308 __collection__
= 'media_comments'
311 'media_entry': ObjectId
,
313 'created': datetime
.datetime
,
315 'content_html': unicode}
318 'media_entry', 'author', 'created', 'content']
321 'created': datetime
.datetime
.utcnow
}
323 def media_entry(self
):
324 return self
.db
.MediaEntry
.find_one({'_id': self
['media_entry']})
327 return self
.db
.User
.find_one({'_id': self
['author']})
336 def register_models(connection
):
338 Register all models in REGISTER_MODELS with this connection.
340 connection
.register(REGISTER_MODELS
)