Removing print statements from convert_gps_media_data migration
[mediagoblin.git] / mediagoblin / db / mongo / models.py
... / ...
CommitLineData
1# GNU MediaGoblin -- federated, autonomous media hosting
2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3#
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.
8#
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.
13#
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/>.
16
17import datetime
18
19from mongokit import Document
20
21from mediagoblin.db.mongo import migrations
22from mediagoblin.db.mongo.util import ASCENDING, DESCENDING, ObjectId
23from mediagoblin.tools.pagination import Pagination
24from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin
25
26
27class 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):
32 instance['_id'] = val
33 def __delete__(self, instance):
34 del instance['_id']
35
36
37###################
38# Custom validators
39###################
40
41########
42# Models
43########
44
45
46class User(Document, UserMixin):
47 """
48 A user of MediaGoblin.
49
50 Structure:
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
56 might... :)
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)
71 """
72 __collection__ = 'users'
73 use_dot_notation = True
74
75 structure = {
76 'username': unicode,
77 'email': unicode,
78 'created': datetime.datetime,
79 'plugin_data': dict, # plugins can dump stuff here.
80 'pw_hash': unicode,
81 'email_verified': bool,
82 'status': unicode,
83 'verification_key': unicode,
84 'is_admin': bool,
85 'url': unicode,
86 'bio': unicode, # May contain markdown
87 'fp_verification_key': unicode, # forgotten password verification key
88 'fp_token_expire': datetime.datetime,
89 }
90
91 required_fields = ['username', 'created', 'pw_hash', 'email']
92
93 default_values = {
94 'created': datetime.datetime.utcnow,
95 'email_verified': False,
96 'status': u'needs_email_verification',
97 'is_admin': False}
98
99 id = MongoPK()
100
101
102class MediaEntry(Document, MediaEntryMixin):
103 """
104 Record of a piece of media.
105
106 Structure:
107 - uploader: A reference to a User who uploaded this.
108
109 - title: Title of this work
110
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.)
117
118 - created: Date and time of when this piece of work was uploaded.
119
120 - description: Uploader-set description of this work. This can be marked
121 up with MarkDown for slight fanciness (links, boldness, italics,
122 paragraphs...)
123
124 - media_type: What type of media is this? Currently we only support
125 'image' ;)
126
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:
130
131 mediaentry.media_data['exif'] = {
132 'manufacturer': 'CASIO',
133 'model': 'QV-4000',
134 'exposure_time': .659}
135
136 Alternately for video you might store:
137
138 # play length in seconds
139 mediaentry.media_data['play_length'] = 340
140
141 ... so what's appropriate here really depends on the media type.
142
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
145 might... :)
146
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
149 looks like:
150 [{'name': 'Gully Gardens',
151 'slug': 'gully-gardens'},
152 {'name': 'Castle Adventure Time?!",
153 'slug': 'castle-adventure-time'}]
154
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
159
160 - license: URI for media's license.
161
162 - queued_media_file: storage interface style filepath describing a file
163 queued for processing. This is stored in the mg_globals.queue_store
164 storage system.
165
166 - queued_task_id: celery task id. Use this to fetch the task state.
167
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'}
171
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.)
175
176 - fail_error: path to the exception raised
177 - fail_metadata:
178 """
179 __collection__ = 'media_entries'
180 use_dot_notation = True
181
182 structure = {
183 'uploader': ObjectId,
184 'title': unicode,
185 'slug': unicode,
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.
191 'tags': [dict],
192 'state': unicode,
193 'license': unicode,
194
195 # For now let's assume there can only be one main file queued
196 # at a time
197 'queued_media_file': [unicode],
198 'queued_task_id': unicode,
199
200 # A dictionary of logical names to filepaths
201 'media_files': dict,
202
203 # The following should be lists of lists, in appropriate file
204 # record form
205 'attachment_files': list,
206
207 # If things go badly in processing things, we'll store that
208 # data here
209 'fail_error': unicode,
210 'fail_metadata': dict}
211
212 required_fields = [
213 'uploader', 'created', 'media_type', 'slug']
214
215 default_values = {
216 'created': datetime.datetime.utcnow,
217 'state': u'unprocessed'}
218
219 id = MongoPK()
220
221 def media_data_init(self, **kwargs):
222 self.media_data.update(kwargs)
223
224 def get_comments(self, ascending=False):
225 if ascending:
226 order = ASCENDING
227 else:
228 order = DESCENDING
229
230 return self.db.MediaComment.find({
231 'media_entry': self._id}).sort('created', order)
232
233 def url_to_prev(self, urlgen):
234 """
235 Provide a url to the previous entry from this user, if there is one
236 """
237 cursor = self.db.MediaEntry.find({'_id': {"$gt": self._id},
238 'uploader': self.uploader,
239 'state': 'processed'}).sort(
240 '_id', ASCENDING).limit(1)
241 for media in cursor:
242 return media.url_for_self(urlgen)
243
244 def url_to_next(self, urlgen):
245 """
246 Provide a url to the next entry from this user, if there is one
247 """
248 cursor = self.db.MediaEntry.find({'_id': {"$lt": self._id},
249 'uploader': self.uploader,
250 'state': 'processed'}).sort(
251 '_id', DESCENDING).limit(1)
252
253 for media in cursor:
254 return media.url_for_self(urlgen)
255
256 @property
257 def get_uploader(self):
258 return self.db.User.find_one({'_id': self.uploader})
259
260
261class MediaComment(Document, MediaCommentMixin):
262 """
263 A comment on a MediaEntry.
264
265 Structure:
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.
270 """
271
272 __collection__ = 'media_comments'
273 use_dot_notation = True
274
275 structure = {
276 'media_entry': ObjectId,
277 'author': ObjectId,
278 'created': datetime.datetime,
279 'content': unicode,
280 }
281
282 required_fields = [
283 'media_entry', 'author', 'created', 'content']
284
285 default_values = {
286 'created': datetime.datetime.utcnow}
287
288 def media_entry(self):
289 return self.db.MediaEntry.find_one({'_id': self['media_entry']})
290
291 @property
292 def get_author(self):
293 return self.db.User.find_one({'_id': self['author']})
294
295
296REGISTER_MODELS = [
297 MediaEntry,
298 User,
299 MediaComment]
300
301
302def register_models(connection):
303 """
304 Register all models in REGISTER_MODELS with this connection.
305 """
306 connection.register(REGISTER_MODELS)