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/>.
21 from mediagoblin
import messages
, mg_globals
22 from mediagoblin
.db
.util
import DESCENDING
, ObjectId
23 from mediagoblin
.tools
.response
import render_to_response
, render_404
, redirect
24 from mediagoblin
.tools
.translate
import pass_to_ugettext
as _
25 from mediagoblin
.tools
.pagination
import Pagination
26 from mediagoblin
.tools
.files
import delete_media_files
27 from mediagoblin
.user_pages
import forms
as user_forms
28 from mediagoblin
.user_pages
.lib
import send_comment_email
30 from mediagoblin
.decorators
import (uses_pagination
, get_user_media_entry
,
31 require_active_login
, user_may_delete_media
, user_may_alter_collection
,
32 get_user_collection
, get_user_collection_item
)
34 from werkzeug
.contrib
.atom
import AtomFeed
36 from mediagoblin
.media_types
import get_media_manager
38 _log
= logging
.getLogger(__name__
)
39 _log
.setLevel(logging
.DEBUG
)
43 def user_home(request
, page
):
44 """'Homepage' of a User()"""
45 user
= request
.db
.User
.find_one({
46 'username': request
.matchdict
['user']})
48 return render_404(request
)
49 elif user
.status
!= u
'active':
50 return render_to_response(
52 'mediagoblin/user_pages/user.html',
55 cursor
= request
.db
.MediaEntry
.find(
56 {'uploader': user
._id
,
57 'state': u
'processed'}).sort('created', DESCENDING
)
59 pagination
= Pagination(page
, cursor
)
60 media_entries
= pagination()
62 #if no data is available, return NotFound
63 if media_entries
== None:
64 return render_404(request
)
66 user_gallery_url
= request
.urlgen(
67 'mediagoblin.user_pages.user_gallery',
70 return render_to_response(
72 'mediagoblin/user_pages/user.html',
74 'user_gallery_url': user_gallery_url
,
75 'media_entries': media_entries
,
76 'pagination': pagination
})
80 def user_gallery(request
, page
):
81 """'Gallery' of a User()"""
82 user
= request
.db
.User
.find_one({
83 'username': request
.matchdict
['user'],
86 return render_404(request
)
88 cursor
= request
.db
.MediaEntry
.find(
89 {'uploader': user
._id
,
90 'state': u
'processed'}).sort('created', DESCENDING
)
92 pagination
= Pagination(page
, cursor
)
93 media_entries
= pagination()
95 #if no data is available, return NotFound
96 if media_entries
== None:
97 return render_404(request
)
99 return render_to_response(
101 'mediagoblin/user_pages/gallery.html',
103 'media_entries': media_entries
,
104 'pagination': pagination
})
106 MEDIA_COMMENTS_PER_PAGE
= 50
109 @get_user_media_entry
111 def media_home(request
, media
, page
, **kwargs
):
113 'Homepage' of a MediaEntry()
115 if ObjectId(request
.matchdict
.get('comment')):
116 pagination
= Pagination(
117 page
, media
.get_comments(
118 mg_globals
.app_config
['comments_ascending']),
119 MEDIA_COMMENTS_PER_PAGE
,
120 ObjectId(request
.matchdict
.get('comment')))
122 pagination
= Pagination(
123 page
, media
.get_comments(
124 mg_globals
.app_config
['comments_ascending']),
125 MEDIA_COMMENTS_PER_PAGE
)
127 comments
= pagination()
129 comment_form
= user_forms
.MediaCommentForm(request
.form
)
131 media_template_name
= get_media_manager(
132 media
.media_type
)['display_template']
134 return render_to_response(
138 'comments': comments
,
139 'pagination': pagination
,
140 'comment_form': comment_form
,
141 'app_config': mg_globals
.app_config
})
144 @get_user_media_entry
145 @require_active_login
146 def media_post_comment(request
, media
):
148 recieves POST from a MediaEntry() comment form, saves the comment.
150 assert request
.method
== 'POST'
152 comment
= request
.db
.MediaComment()
153 comment
.media_entry
= media
.id
154 comment
.author
= request
.user
.id
155 comment
.content
= unicode(request
.form
['comment_content'])
157 if not comment
.content
.strip():
158 messages
.add_message(
161 _("Oops, your comment was empty."))
165 messages
.add_message(
166 request
, messages
.SUCCESS
,
167 _('Your comment has been posted!'))
169 media_uploader
= media
.get_uploader
170 #don't send email if you comment on your own post
171 if (comment
.author
!= media_uploader
and
172 media_uploader
.wants_comment_notification
):
173 send_comment_email(media_uploader
, comment
, media
, request
)
175 return exc
.HTTPFound(
176 location
=media
.url_for_self(request
.urlgen
))
179 @get_user_media_entry
180 @require_active_login
181 def media_collect(request
, media
):
183 form
= user_forms
.MediaCollectForm(request
.form
)
184 filt
= (request
.db
.Collection
.creator
== request
.user
.id)
185 form
.collection
.query
= request
.db
.Collection
.query
.filter(
186 filt
).order_by(request
.db
.Collection
.title
)
188 if request
.method
== 'POST':
192 collection_item
= request
.db
.CollectionItem()
194 # If the user is adding a new collection, use that
195 if request
.form
['collection_title']:
196 collection
= request
.db
.Collection()
197 collection
.id = ObjectId()
200 unicode(request
.form
['collection_title']))
202 collection
.description
= unicode(
203 request
.form
.get('collection_description'))
204 collection
.creator
= request
.user
._id
205 collection
.generate_slug()
207 # Make sure this user isn't duplicating an existing collection
208 existing_collection
= request
.db
.Collection
.find_one({
209 'creator': request
.user
._id
,
210 'title': collection
.title
})
212 if existing_collection
:
213 messages
.add_message(
214 request
, messages
.ERROR
,
215 _('You already have a collection called "%s"!'
218 return redirect(request
, "mediagoblin.user_pages.media_home",
219 user
=request
.user
.username
,
222 collection
.save(validate
=True)
224 collection_item
.collection
= collection
.id
225 # Otherwise, use the collection selected from the drop-down
227 collection
= request
.db
.Collection
.find_one({
228 '_id': request
.form
.get('collection')})
229 collection_item
.collection
= collection
.id
231 # Make sure the user actually selected a collection
233 messages
.add_message(
234 request
, messages
.ERROR
,
235 _('You have to select or add a collection'))
236 # Check whether media already exists in collection
237 elif request
.db
.CollectionItem
.find_one({
238 'media_entry': media
.id,
239 'collection': collection_item
.collection
}):
240 messages
.add_message(
241 request
, messages
.ERROR
,
242 _('"%s" already in collection "%s"'
243 % (media
.title
, collection
.title
)))
245 collection_item
.media_entry
= media
.id
246 collection_item
.author
= request
.user
.id
247 collection_item
.note
= unicode(request
.form
['note'])
248 collection_item
.save(validate
=True)
250 collection
.items
= collection
.items
+ 1
251 collection
.save(validate
=True)
253 media
.collected
= media
.collected
+ 1
256 messages
.add_message(
257 request
, messages
.SUCCESS
, _('"%s" added to collection "%s"'
258 % (media
.title
, collection
.title
)))
260 return redirect(request
, "mediagoblin.user_pages.media_home",
261 user
=media
.get_uploader
.username
,
264 messages
.add_message(
265 request
, messages
.ERROR
,
266 _('Please check your entries and try again.'))
268 return render_to_response(
270 'mediagoblin/user_pages/media_collect.html',
275 @get_user_media_entry
276 @require_active_login
277 @user_may_delete_media
278 def media_confirm_delete(request
, media
):
280 form
= user_forms
.ConfirmDeleteForm(request
.form
)
282 if request
.method
== 'POST' and form
.validate():
283 if form
.confirm
.data
is True:
284 username
= media
.get_uploader
.username
286 # Delete all the associated comments
287 for comment
in media
.get_comments():
290 # Delete all files on the public storage
292 delete_media_files(media
)
293 except OSError, error
:
294 _log
.error('No such files from the user "{1}"'
295 ' to delete: {0}'.format(str(error
), username
))
296 messages
.add_message(request
, messages
.ERROR
,
297 _('Some of the files with this entry seem'
298 ' to be missing. Deleting anyway.'))
301 messages
.add_message(
302 request
, messages
.SUCCESS
, _('You deleted the media.'))
304 return redirect(request
, "mediagoblin.user_pages.user_home",
307 messages
.add_message(
308 request
, messages
.ERROR
,
309 _("The media was not deleted because you didn't check that you were sure."))
310 return exc
.HTTPFound(
311 location
=media
.url_for_self(request
.urlgen
))
313 if ((request
.user
.is_admin
and
314 request
.user
._id
!= media
.uploader
)):
315 messages
.add_message(
316 request
, messages
.WARNING
,
317 _("You are about to delete another user's media. "
318 "Proceed with caution."))
320 return render_to_response(
322 'mediagoblin/user_pages/media_confirm_delete.html',
328 def user_collection(request
, page
):
329 """A User-defined Collection"""
330 user
= request
.db
.User
.find_one({
331 'username': request
.matchdict
['user'],
332 'status': u
'active'})
334 return render_404(request
)
336 collection
= request
.db
.Collection
.find_one(
337 {'slug': request
.matchdict
['collection']})
339 cursor
= request
.db
.CollectionItem
.find(
340 {'collection': collection
.id})
342 pagination
= Pagination(page
, cursor
)
343 collection_items
= pagination()
345 #if no data is available, return NotFound
346 if collection_items
== None:
347 return render_404(request
)
349 return render_to_response(
351 'mediagoblin/user_pages/collection.html',
353 'collection': collection
,
354 'collection_items': collection_items
,
355 'pagination': pagination
})
358 @get_user_collection_item
359 @require_active_login
360 @user_may_alter_collection
361 def collection_item_confirm_remove(request
, collection_item
):
363 form
= user_forms
.ConfirmCollectionItemRemoveForm(request
.form
)
365 if request
.method
== 'POST' and form
.validate():
366 username
= collection_item
.in_collection
.get_creator
.username
367 collection
= collection_item
.in_collection
369 if form
.confirm
.data
is True:
370 entry
= collection_item
.get_media_entry
371 entry
.collected
= entry
.collected
- 1
374 collection_item
.delete()
375 collection
.items
= collection
.items
- 1
378 messages
.add_message(
379 request
, messages
.SUCCESS
, _('You deleted the item from the collection.'))
381 messages
.add_message(
382 request
, messages
.ERROR
,
383 _("The item was not removed because you didn't check that you were sure."))
385 return redirect(request
, "mediagoblin.user_pages.user_collection",
387 collection
=collection
.slug
)
389 if ((request
.user
.is_admin
and
390 request
.user
._id
!= collection_item
.in_collection
.creator
)):
391 messages
.add_message(
392 request
, messages
.WARNING
,
393 _("You are about to delete an item from another user's collection. "
394 "Proceed with caution."))
396 return render_to_response(
398 'mediagoblin/user_pages/collection_item_confirm_remove.html',
399 {'collection_item': collection_item
,
404 @require_active_login
405 @user_may_alter_collection
406 def collection_confirm_delete(request
, collection
):
408 form
= user_forms
.ConfirmDeleteForm(request
.form
)
410 if request
.method
== 'POST' and form
.validate():
412 username
= collection
.get_creator
.username
414 if form
.confirm
.data
is True:
415 collection_title
= collection
.title
417 # Delete all the associated collection items
418 for item
in collection
.get_collection_items():
419 entry
= item
.get_media_entry
420 entry
.collected
= entry
.collected
- 1
425 messages
.add_message(
426 request
, messages
.SUCCESS
, _('You deleted the collection "%s"' % collection_title
))
428 return redirect(request
, "mediagoblin.user_pages.user_home",
431 messages
.add_message(
432 request
, messages
.ERROR
,
433 _("The collection was not deleted because you didn't check that you were sure."))
435 return redirect(request
, "mediagoblin.user_pages.user_collection",
437 collection
=collection
.slug
)
439 if ((request
.user
.is_admin
and
440 request
.user
._id
!= collection
.creator
)):
441 messages
.add_message(
442 request
, messages
.WARNING
,
443 _("You are about to delete another user's collection. "
444 "Proceed with caution."))
446 return render_to_response(
448 'mediagoblin/user_pages/collection_confirm_delete.html',
449 {'collection': collection
,
453 ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
= 15
456 def atom_feed(request
):
458 generates the atom feed with the newest images
461 user
= request
.db
.User
.find_one({
462 'username': request
.matchdict
['user'],
463 'status': u
'active'})
465 return render_404(request
)
467 cursor
= request
.db
.MediaEntry
.find({
468 'uploader': user
._id
,
469 'state': u
'processed'}) \
470 .sort('created', DESCENDING
) \
471 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
)
474 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
477 'href': request
.urlgen(
478 'mediagoblin.user_pages.user_home',
479 qualified
=True, user
=request
.matchdict
['user']),
484 if mg_globals
.app_config
["push_urls"]:
485 for push_url
in mg_globals
.app_config
["push_urls"]:
491 "MediaGoblin: Feed for user '%s'" % request
.matchdict
['user'],
492 feed_url
=request
.url
,
493 id='tag:{host},{year}:gallery.user-{user}'.format(
495 year
=datetime
.datetime
.today().strftime('%Y'),
496 user
=request
.matchdict
['user']),
500 feed
.add(entry
.get('title'),
501 entry
.description_html
,
502 id=entry
.url_for_self(request
.urlgen
, qualified
=True),
505 'name': entry
.get_uploader
.username
,
506 'uri': request
.urlgen(
507 'mediagoblin.user_pages.user_home',
508 qualified
=True, user
=entry
.get_uploader
.username
)},
509 updated
=entry
.get('created'),
511 'href': entry
.url_for_self(
515 'type': 'text/html'}])
517 return feed
.get_response()
520 def collection_atom_feed(request
):
522 generates the atom feed with the newest images from a collection
525 user
= request
.db
.User
.find_one({
526 'username': request
.matchdict
['user'],
527 'status': u
'active'})
529 return render_404(request
)
531 collection
= request
.db
.Collection
.find_one({
533 'slug': request
.matchdict
['collection']})
535 cursor
= request
.db
.CollectionItem
.find({
536 'collection': collection
._id
}) \
537 .sort('added', DESCENDING
) \
538 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
)
541 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
544 'href': request
.urlgen(
545 'mediagoblin.user_pages.user_collection',
546 qualified
=True, user
=request
.matchdict
['user'], collection
=collection
.slug
),
551 if mg_globals
.app_config
["push_urls"]:
552 for push_url
in mg_globals
.app_config
["push_urls"]:
558 "MediaGoblin: Feed for %s's collection %s" % (request
.matchdict
['user'], collection
.title
),
559 feed_url
=request
.url
,
560 id='tag:{host},{year}:collection.user-{user}.title-{title}'.format(
562 year
=datetime
.datetime
.today().strftime('%Y'),
563 user
=request
.matchdict
['user'],
564 title
=collection
.title
),
568 entry
= item
.get_media_entry
569 feed
.add(entry
.get('title'),
571 id=entry
.url_for_self(request
.urlgen
, qualified
=True),
574 'name': entry
.get_uploader
.username
,
575 'uri': request
.urlgen(
576 'mediagoblin.user_pages.user_home',
577 qualified
=True, user
=entry
.get_uploader
.username
)},
578 updated
=item
.get('added'),
580 'href': entry
.url_for_self(
584 'type': 'text/html'}])
586 return feed
.get_response()
589 @require_active_login
590 def processing_panel(request
):
592 Show to the user what media is still in conversion/processing...
593 and what failed, and why!
596 user
= request
.db
.User
.find_one(
597 {'username': request
.matchdict
['user'],
598 'status': u
'active'})
600 # Make sure the user exists and is active
602 return render_404(request
)
603 elif user
.status
!= u
'active':
604 return render_to_response(
606 'mediagoblin/user_pages/user.html',
609 # XXX: Should this be a decorator?
611 # Make sure we have permission to access this user's panel. Only
612 # admins and this user herself should be able to do so.
613 if not (user
._id
== request
.user
._id
614 or request
.user
.is_admin
):
615 # No? Let's simply redirect to this user's homepage then.
617 request
, 'mediagoblin.user_pages.user_home',
618 user
=request
.matchdict
['user'])
620 # Get media entries which are in-processing
621 processing_entries
= request
.db
.MediaEntry
.find(
622 {'uploader': user
._id
,
623 'state': u
'processing'}).sort('created', DESCENDING
)
625 # Get media entries which have failed to process
626 failed_entries
= request
.db
.MediaEntry
.find(
627 {'uploader': user
._id
,
628 'state': u
'failed'}).sort('created', DESCENDING
)
630 processed_entries
= request
.db
.MediaEntry
.find(
631 {'uploader': user
._id
,
632 'state': u
'processed'}).sort('created', DESCENDING
).limit(10)
635 return render_to_response(
637 'mediagoblin/user_pages/processing_panel.html',
639 'processing_entries': processing_entries
,
640 'failed_entries': failed_entries
,
641 'processed_entries': processed_entries
})