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/>.
20 from mediagoblin import messages, mg_globals
21 from mediagoblin.db.models import (MediaEntry, MediaTag, Collection,
22 CollectionItem, User, MediaComment,
23 CommentReport, MediaReport)
24 from mediagoblin.tools.response import render_to_response, render_404, \
25 redirect, redirect_obj
26 from mediagoblin.tools.translate import pass_to_ugettext as _
27 from mediagoblin.tools.pagination import Pagination
28 from mediagoblin.user_pages import forms as user_forms
29 from mediagoblin.user_pages.lib import (send_comment_email, build_report_table,
30 add_media_to_collection)
32 from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
33 get_media_entry_by_id, user_has_privilege,
34 require_active_login, user_may_delete_media, user_may_alter_collection,
35 get_user_collection, get_user_collection_item, active_user_from_url,
36 get_media_comment_by_id, user_not_banned)
38 from werkzeug.contrib.atom import AtomFeed
41 _log = logging.getLogger(__name__)
42 _log.setLevel(logging.DEBUG)
46 def user_home(request, page):
47 """'Homepage' of a User()"""
48 # TODO: decide if we only want homepages for active users, we can
49 # then use the @get_active_user decorator and also simplify the
51 user = User.query.filter_by(username=request.matchdict['user']).first()
53 return render_404(request)
54 elif user.status != u'active':
55 return render_to_response(
57 'mediagoblin/user_pages/user.html',
60 cursor = MediaEntry.query.\
61 filter_by(uploader = user.id,
62 state = u'processed').order_by(MediaEntry.created.desc())
64 pagination = Pagination(page, cursor)
65 media_entries = pagination()
67 #if no data is available, return NotFound
68 if media_entries == None:
69 return render_404(request)
71 user_gallery_url = request.urlgen(
72 'mediagoblin.user_pages.user_gallery',
75 return render_to_response(
77 'mediagoblin/user_pages/user.html',
79 'user_gallery_url': user_gallery_url,
80 'media_entries': media_entries,
81 'pagination': pagination})
86 def user_gallery(request, page, url_user=None):
87 """'Gallery' of a User()"""
88 tag = request.matchdict.get('tag', None)
89 cursor = MediaEntry.query.filter_by(
91 state=u'processed').order_by(MediaEntry.created.desc())
93 # Filter potentially by tag too:
95 cursor = cursor.filter(
96 MediaEntry.tags_helper.any(
97 MediaTag.slug == request.matchdict['tag']))
100 pagination = Pagination(page, cursor)
101 media_entries = pagination()
103 #if no data is available, return NotFound
104 # TODO: Should we really also return 404 for empty galleries?
105 if media_entries == None:
106 return render_404(request)
108 return render_to_response(
110 'mediagoblin/user_pages/gallery.html',
111 {'user': url_user, 'tag': tag,
112 'media_entries': media_entries,
113 'pagination': pagination})
115 MEDIA_COMMENTS_PER_PAGE = 50
118 @get_user_media_entry
120 def media_home(request, media, page, **kwargs):
122 'Homepage' of a MediaEntry()
124 comment_id = request.matchdict.get('comment', None)
126 pagination = Pagination(
127 page, media.get_comments(
128 mg_globals.app_config['comments_ascending']),
129 MEDIA_COMMENTS_PER_PAGE,
132 pagination = Pagination(
133 page, media.get_comments(
134 mg_globals.app_config['comments_ascending']),
135 MEDIA_COMMENTS_PER_PAGE)
137 comments = pagination()
139 comment_form = user_forms.MediaCommentForm(request.form)
141 media_template_name = media.media_manager['display_template']
143 return render_to_response(
147 'comments': comments,
148 'pagination': pagination,
149 'comment_form': comment_form,
150 'app_config': mg_globals.app_config})
153 @get_media_entry_by_id
154 @require_active_login
155 @user_has_privilege(u'commenter')
156 def media_post_comment(request, media):
158 recieves POST from a MediaEntry() comment form, saves the comment.
160 assert request.method == 'POST'
162 comment = request.db.MediaComment()
163 comment.media_entry = media.id
164 comment.author = request.user.id
165 comment.content = unicode(request.form['comment_content'])
167 # Show error message if commenting is disabled.
168 if not mg_globals.app_config['allow_comments']:
169 messages.add_message(
172 _("Sorry, comments are disabled."))
173 elif not comment.content.strip():
174 messages.add_message(
177 _("Oops, your comment was empty."))
181 messages.add_message(
182 request, messages.SUCCESS,
183 _('Your comment has been posted!'))
185 media_uploader = media.get_uploader
186 #don't send email if you comment on your own post
187 if (comment.author != media_uploader and
188 media_uploader.wants_comment_notification):
189 send_comment_email(media_uploader, comment, media, request)
191 return redirect_obj(request, media)
194 @get_media_entry_by_id
195 @require_active_login
196 def media_collect(request, media):
197 """Add media to collection submission"""
199 form = user_forms.MediaCollectForm(request.form)
200 # A user's own collections:
201 form.collection.query = Collection.query.filter_by(
202 creator = request.user.id).order_by(Collection.title)
204 if request.method != 'POST' or not form.validate():
205 # No POST submission, or invalid form
206 if not form.validate():
207 messages.add_message(request, messages.ERROR,
208 _('Please check your entries and try again.'))
210 return render_to_response(
212 'mediagoblin/user_pages/media_collect.html',
216 # If we are here, method=POST and the form is valid, submit things.
217 # If the user is adding a new collection, use that:
218 if form.collection_title.data:
219 # Make sure this user isn't duplicating an existing collection
220 existing_collection = Collection.query.filter_by(
221 creator=request.user.id,
222 title=form.collection_title.data).first()
223 if existing_collection:
224 messages.add_message(request, messages.ERROR,
225 _('You already have a collection called "%s"!')
226 % existing_collection.title)
227 return redirect(request, "mediagoblin.user_pages.media_home",
228 user=media.get_uploader.username,
229 media=media.slug_or_id)
231 collection = Collection()
232 collection.title = form.collection_title.data
233 collection.description = form.collection_description.data
234 collection.creator = request.user.id
235 collection.generate_slug()
238 # Otherwise, use the collection selected from the drop-down
240 collection = form.collection.data
241 if collection and collection.creator != request.user.id:
244 # Make sure the user actually selected a collection
246 messages.add_message(
247 request, messages.ERROR,
248 _('You have to select or add a collection'))
249 return redirect(request, "mediagoblin.user_pages.media_collect",
250 user=media.get_uploader.username,
254 # Check whether media already exists in collection
255 elif CollectionItem.query.filter_by(
256 media_entry=media.id,
257 collection=collection.id).first():
258 messages.add_message(request, messages.ERROR,
259 _('"%s" already in collection "%s"')
260 % (media.title, collection.title))
261 else: # Add item to collection
262 add_media_to_collection(collection, media, form.note.data)
264 messages.add_message(request, messages.SUCCESS,
265 _('"%s" added to collection "%s"')
266 % (media.title, collection.title))
268 return redirect_obj(request, media)
271 #TODO: Why does @user_may_delete_media not implicate @require_active_login?
273 @get_media_entry_by_id
274 @require_active_login
275 @user_may_delete_media
276 def media_confirm_delete(request, media):
278 form = user_forms.ConfirmDeleteForm(request.form)
280 if request.method == 'POST' and form.validate():
281 if form.confirm.data is True:
282 username = media.get_uploader.username
283 # Delete MediaEntry and all related files, comments etc.
285 messages.add_message(
286 request, messages.SUCCESS, _('You deleted the media.'))
288 return redirect(request, "mediagoblin.user_pages.user_home",
291 messages.add_message(
292 request, messages.ERROR,
293 _("The media was not deleted because you didn't check that you were sure."))
294 return redirect_obj(request, media)
296 if ((request.user.is_admin and
297 request.user.id != media.uploader)):
298 messages.add_message(
299 request, messages.WARNING,
300 _("You are about to delete another user's media. "
301 "Proceed with caution."))
303 return render_to_response(
305 'mediagoblin/user_pages/media_confirm_delete.html',
310 @active_user_from_url
312 def user_collection(request, page, url_user=None):
313 """A User-defined Collection"""
314 collection = Collection.query.filter_by(
315 get_creator=url_user,
316 slug=request.matchdict['collection']).first()
319 return render_404(request)
321 cursor = collection.get_collection_items()
323 pagination = Pagination(page, cursor)
324 collection_items = pagination()
326 # if no data is available, return NotFound
327 # TODO: Should an empty collection really also return 404?
328 if collection_items == None:
329 return render_404(request)
331 return render_to_response(
333 'mediagoblin/user_pages/collection.html',
335 'collection': collection,
336 'collection_items': collection_items,
337 'pagination': pagination})
340 @active_user_from_url
341 def collection_list(request, url_user=None):
342 """A User-defined Collection"""
343 collections = Collection.query.filter_by(
344 get_creator=url_user)
346 return render_to_response(
348 'mediagoblin/user_pages/collection_list.html',
350 'collections': collections})
353 @get_user_collection_item
354 @require_active_login
355 @user_may_alter_collection
356 def collection_item_confirm_remove(request, collection_item):
358 form = user_forms.ConfirmCollectionItemRemoveForm(request.form)
360 if request.method == 'POST' and form.validate():
361 username = collection_item.in_collection.get_creator.username
362 collection = collection_item.in_collection
364 if form.confirm.data is True:
365 entry = collection_item.get_media_entry
366 entry.collected = entry.collected - 1
369 collection_item.delete()
370 collection.items = collection.items - 1
373 messages.add_message(
374 request, messages.SUCCESS, _('You deleted the item from the collection.'))
376 messages.add_message(
377 request, messages.ERROR,
378 _("The item was not removed because you didn't check that you were sure."))
380 return redirect_obj(request, collection)
382 if ((request.user.is_admin and
383 request.user.id != collection_item.in_collection.creator)):
384 messages.add_message(
385 request, messages.WARNING,
386 _("You are about to delete an item from another user's collection. "
387 "Proceed with caution."))
389 return render_to_response(
391 'mediagoblin/user_pages/collection_item_confirm_remove.html',
392 {'collection_item': collection_item,
397 @require_active_login
398 @user_may_alter_collection
399 def collection_confirm_delete(request, collection):
401 form = user_forms.ConfirmDeleteForm(request.form)
403 if request.method == 'POST' and form.validate():
405 username = collection.get_creator.username
407 if form.confirm.data is True:
408 collection_title = collection.title
410 # Delete all the associated collection items
411 for item in collection.get_collection_items():
412 entry = item.get_media_entry
413 entry.collected = entry.collected - 1
418 messages.add_message(request, messages.SUCCESS,
419 _('You deleted the collection "%s"') % collection_title)
421 return redirect(request, "mediagoblin.user_pages.user_home",
424 messages.add_message(
425 request, messages.ERROR,
426 _("The collection was not deleted because you didn't check that you were sure."))
428 return redirect_obj(request, collection)
430 if ((request.user.is_admin and
431 request.user.id != collection.creator)):
432 messages.add_message(
433 request, messages.WARNING,
434 _("You are about to delete another user's collection. "
435 "Proceed with caution."))
437 return render_to_response(
439 'mediagoblin/user_pages/collection_confirm_delete.html',
440 {'collection': collection,
444 ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15
447 def atom_feed(request):
449 generates the atom feed with the newest images
451 user = User.query.filter_by(
452 username = request.matchdict['user'],
453 status = u'active').first()
455 return render_404(request)
457 cursor = MediaEntry.query.filter_by(
459 state = u'processed').\
460 order_by(MediaEntry.created.desc()).\
461 limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
464 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
467 'href': request.urlgen(
468 'mediagoblin.user_pages.user_home',
469 qualified=True, user=request.matchdict['user']),
474 if mg_globals.app_config["push_urls"]:
475 for push_url in mg_globals.app_config["push_urls"]:
481 "MediaGoblin: Feed for user '%s'" % request.matchdict['user'],
482 feed_url=request.url,
483 id='tag:{host},{year}:gallery.user-{user}'.format(
485 year=datetime.datetime.today().strftime('%Y'),
486 user=request.matchdict['user']),
490 feed.add(entry.get('title'),
491 entry.description_html,
492 id=entry.url_for_self(request.urlgen, qualified=True),
495 'name': entry.get_uploader.username,
496 'uri': request.urlgen(
497 'mediagoblin.user_pages.user_home',
498 qualified=True, user=entry.get_uploader.username)},
499 updated=entry.get('created'),
501 'href': entry.url_for_self(
505 'type': 'text/html'}])
507 return feed.get_response()
510 def collection_atom_feed(request):
512 generates the atom feed with the newest images from a collection
514 user = User.query.filter_by(
515 username = request.matchdict['user'],
516 status = u'active').first()
518 return render_404(request)
520 collection = Collection.query.filter_by(
522 slug=request.matchdict['collection']).first()
524 return render_404(request)
526 cursor = CollectionItem.query.filter_by(
527 collection=collection.id) \
528 .order_by(CollectionItem.added.desc()) \
529 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
532 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
535 'href': collection.url_for_self(request.urlgen, qualified=True),
540 if mg_globals.app_config["push_urls"]:
541 for push_url in mg_globals.app_config["push_urls"]:
547 "MediaGoblin: Feed for %s's collection %s" %
548 (request.matchdict['user'], collection.title),
549 feed_url=request.url,
550 id=u'tag:{host},{year}:gnu-mediagoblin.{user}.collection.{slug}'\
553 year=collection.created.strftime('%Y'),
554 user=request.matchdict['user'],
555 slug=collection.slug),
559 entry = item.get_media_entry
560 feed.add(entry.get('title'),
562 id=entry.url_for_self(request.urlgen, qualified=True),
565 'name': entry.get_uploader.username,
566 'uri': request.urlgen(
567 'mediagoblin.user_pages.user_home',
568 qualified=True, user=entry.get_uploader.username)},
569 updated=item.get('added'),
571 'href': entry.url_for_self(
575 'type': 'text/html'}])
577 return feed.get_response()
580 @require_active_login
581 def processing_panel(request):
583 Show to the user what media is still in conversion/processing...
584 and what failed, and why!
586 user = User.query.filter_by(username=request.matchdict['user']).first()
587 # TODO: XXX: Should this be a decorator?
589 # Make sure we have permission to access this user's panel. Only
590 # admins and this user herself should be able to do so.
591 if not (user.id == request.user.id or request.user.is_admin):
592 # No? Simply redirect to this user's homepage.
594 request, 'mediagoblin.user_pages.user_home',
597 # Get media entries which are in-processing
598 processing_entries = MediaEntry.query.\
599 filter_by(uploader = user.id,
600 state = u'processing').\
601 order_by(MediaEntry.created.desc())
603 # Get media entries which have failed to process
604 failed_entries = MediaEntry.query.\
605 filter_by(uploader = user.id,
607 order_by(MediaEntry.created.desc())
609 processed_entries = MediaEntry.query.\
610 filter_by(uploader = user.id,
611 state = u'processed').\
612 order_by(MediaEntry.created.desc()).\
616 return render_to_response(
618 'mediagoblin/user_pages/processing_panel.html',
620 'processing_entries': processing_entries,
621 'failed_entries': failed_entries,
622 'processed_entries': processed_entries})
624 @require_active_login
625 @get_user_media_entry
626 @user_has_privilege(u'reporter')
627 def file_a_report(request, media, comment=None):
628 if request.method == "POST":
629 report_table = build_report_table(request.form)
636 if comment is not None:
637 context = {'media': media,
640 context = {'media': media}
642 return render_to_response(
644 'mediagoblin/user_pages/report.html',
647 @require_active_login
648 @get_user_media_entry
649 @get_media_comment_by_id
650 def file_a_comment_report(request, media, comment):
651 return file_a_report(request, comment=comment)