Render webm_480 as default if webm_video is absent
[mediagoblin.git] / mediagoblin / user_pages / views.py
CommitLineData
7f4ebeed 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
9a16e16f
SS
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
fb2fbe2c 17import logging
64712915 18import datetime
5ab60299 19import json
52359e91 20
e49b7e02
BP
21import six
22
3a8c3a38 23from mediagoblin import messages, mg_globals
64a456a4
JT
24from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, Comment,
25 CollectionItem, LocalUser, Activity, \
26 GenericModelReference)
2a01c5e7 27from mediagoblin.plugins.api.tools import get_media_file_paths
2e6ee596
E
28from mediagoblin.tools.response import render_to_response, render_404, \
29 redirect, redirect_obj
5ab60299 30from mediagoblin.tools.text import cleaned_markdown_conversion
a789b713 31from mediagoblin.tools.translate import pass_to_ugettext as _
152a3bfa 32from mediagoblin.tools.pagination import Pagination
b9492011 33from mediagoblin.tools.federation import create_activity
9074ee7c 34from mediagoblin.user_pages import forms as user_forms
52a355b2 35from mediagoblin.user_pages.lib import (send_comment_email,
9d6e453f 36 add_media_to_collection, build_report_object)
2d7b6bde
JW
37from mediagoblin.notifications import trigger_notification, \
38 add_comment_subscription, mark_comment_notification_seen
2edd6b0b 39from mediagoblin.tools.pluginapi import hook_transform
f6249408 40
50854db0 41from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
8e91df87 42 get_media_entry_by_id, user_has_privilege, user_not_banned,
6d1e55b2 43 require_active_login, user_may_delete_media, user_may_alter_collection,
30a9fe7c 44 get_user_collection, get_user_collection_item, active_user_from_url,
6483b370 45 get_optional_media_comment_by_id, allow_reporting)
9a16e16f 46
00c39256 47from werkzeug.contrib.atom import AtomFeed
2d7b6bde 48from werkzeug.exceptions import MethodNotAllowed
5ab60299 49from werkzeug.wrappers import Response
1301a8ad 50
9074ee7c 51
fb2fbe2c
JAN
52_log = logging.getLogger(__name__)
53_log.setLevel(logging.DEBUG)
54
6bba33d7 55@user_not_banned
3eb6fc4f 56@uses_pagination
1301a8ad 57def user_home(request, page):
d88fcb03
JT
58 """'Homepage' of a LocalUser()"""
59 user = LocalUser.query.filter_by(username=request.matchdict['user']).first()
7acdbfd3 60 if not user:
de12b4e7 61 return render_404(request)
25625107 62 elif not user.has_privilege(u'active'):
990d3b69
CAW
63 return render_to_response(
64 request,
51b43180 65 'mediagoblin/user_pages/user_nonactive.html',
990d3b69 66 {'user': user})
9a16e16f 67
2fb36dac 68 cursor = MediaEntry.query.\
336508bb 69 filter_by(actor = user.id).order_by(MediaEntry.created.desc())
9a16e16f 70
1301a8ad 71 pagination = Pagination(page, cursor)
ca3ca51c 72 media_entries = pagination()
44e3e917 73
336508bb 74 # if no data is available, return NotFound
ae85ed0f 75 if media_entries == None:
de12b4e7 76 return render_404(request)
243c3843 77
5949be9a
CAW
78 user_gallery_url = request.urlgen(
79 'mediagoblin.user_pages.user_gallery',
5a4e3ff1 80 user=user.username)
5949be9a 81
9038c9f9
CAW
82 return render_to_response(
83 request,
c9c24934
E
84 'mediagoblin/user_pages/user.html',
85 {'user': user,
5949be9a 86 'user_gallery_url': user_gallery_url,
c9c24934
E
87 'media_entries': media_entries,
88 'pagination': pagination})
f6249408 89
6bba33d7 90@user_not_banned
64c2a400 91@active_user_from_url
184f2240 92@uses_pagination
64c2a400 93def user_gallery(request, page, url_user=None):
d88fcb03 94 """'Gallery' of a LocalUser()"""
f2c0bf3e 95 tag = request.matchdict.get('tag', None)
64c2a400 96 cursor = MediaEntry.query.filter_by(
0f3bf8d4 97 actor=url_user.id,
64c2a400 98 state=u'processed').order_by(MediaEntry.created.desc())
af008743 99
f2c0bf3e
SS
100 # Filter potentially by tag too:
101 if tag:
102 cursor = cursor.filter(
103 MediaEntry.tags_helper.any(
38905733 104 MediaTag.slug == request.matchdict['tag']))
f2c0bf3e 105
64c2a400 106 # Paginate gallery
184f2240 107 pagination = Pagination(page, cursor)
108 media_entries = pagination()
109
110 #if no data is available, return NotFound
64c2a400 111 # TODO: Should we really also return 404 for empty galleries?
184f2240 112 if media_entries == None:
de12b4e7 113 return render_404(request)
243c3843 114
4b5f5a08 115 return render_to_response(
116 request,
117 'mediagoblin/user_pages/gallery.html',
f2c0bf3e 118 {'user': url_user, 'tag': tag,
4b5f5a08 119 'media_entries': media_entries,
120 'pagination': pagination})
184f2240 121
2d7b6bde 122
6f59a3a3 123MEDIA_COMMENTS_PER_PAGE = 50
434b3221 124
6bba33d7 125@user_not_banned
01674e10 126@get_user_media_entry
9074ee7c 127@uses_pagination
6f59a3a3 128def media_home(request, media, page, **kwargs):
9074ee7c
JW
129 """
130 'Homepage' of a MediaEntry()
131 """
7d16a01b
E
132 comment_id = request.matchdict.get('comment', None)
133 if comment_id:
2d7b6bde
JW
134 if request.user:
135 mark_comment_notification_seen(comment_id, request.user)
136
af2fcba5 137 pagination = Pagination(
7c378f2c
CAW
138 page, media.get_comments(
139 mg_globals.app_config['comments_ascending']),
140 MEDIA_COMMENTS_PER_PAGE,
7d16a01b 141 comment_id)
af2fcba5
JW
142 else:
143 pagination = Pagination(
7c378f2c
CAW
144 page, media.get_comments(
145 mg_globals.app_config['comments_ascending']),
146 MEDIA_COMMENTS_PER_PAGE)
9074ee7c 147
6f59a3a3 148 comments = pagination()
9074ee7c 149
111a609d 150 comment_form = user_forms.MediaCommentForm(request.form)
9074ee7c 151
58a94757 152 media_template_name = media.media_manager.display_template
93bdab9d 153
2edd6b0b
CAW
154 context = {
155 'media': media,
156 'comments': comments,
157 'pagination': pagination,
158 'comment_form': comment_form,
159 'app_config': mg_globals.app_config}
160
161 # Since the media template name gets swapped out for each media
162 # type, normal context hooks don't work if you want to affect all
163 # media displays. This gives a general purpose hook.
164 context = hook_transform(
165 "media_home_context", context)
166
9038c9f9
CAW
167 return render_to_response(
168 request,
93bdab9d 169 media_template_name,
2edd6b0b 170 context)
9074ee7c 171
95e6da02 172
461dd971 173@get_media_entry_by_id
dfd66b78 174@user_has_privilege(u'commenter')
95e12bf2 175def media_post_comment(request, media):
9074ee7c
JW
176 """
177 recieves POST from a MediaEntry() comment form, saves the comment.
178 """
2d7b6bde
JW
179 if not request.method == 'POST':
180 raise MethodNotAllowed()
95e12bf2 181
64a456a4 182 comment = request.db.TextComment()
0f3bf8d4 183 comment.actor = request.user.id
e49b7e02 184 comment.content = six.text_type(request.form['comment_content'])
9074ee7c 185
99338b6a
DT
186 # Show error message if commenting is disabled.
187 if not mg_globals.app_config['allow_comments']:
188 messages.add_message(
189 request,
190 messages.ERROR,
191 _("Sorry, comments are disabled."))
192 elif not comment.content.strip():
7298ffa1
AW
193 messages.add_message(
194 request,
195 messages.ERROR,
eae7d058 196 _("Oops, your comment was empty."))
7298ffa1 197 else:
0f3bf8d4 198 create_activity("post", comment, comment.actor, target=media)
bc2c06a1 199 add_comment_subscription(request.user, media)
7298ffa1 200 comment.save()
b5d3aec6 201
64a456a4
JT
202 link = request.db.Comment()
203 link.target = media
204 link.comment = comment
205 link.save()
206
7298ffa1 207 messages.add_message(
5c7b2a63
AB
208 request,
209 messages.SUCCESS,
eae7d058 210 _('Your comment has been posted!'))
6efcab2d 211 trigger_notification(link, media, request)
252eaf21 212
2e6ee596 213 return redirect_obj(request, media)
00c39256 214
95e6da02 215
5ab60299
EL
216
217def media_preview_comment(request):
3bd62dc4 218 """Runs a comment through markdown so it can be previewed."""
202d951c
RE
219 # If this isn't an ajax request, render_404
220 if not request.is_xhr:
221 return render_404(request)
222
e49b7e02 223 comment = six.text_type(request.form['comment_content'])
3bd62dc4 224 cleancomment = { "content":cleaned_markdown_conversion(comment)}
5ab60299 225
3bd62dc4 226 return Response(json.dumps(cleancomment))
5ab60299 227
6bba33d7 228@user_not_banned
96a2249b 229@get_media_entry_by_id
be5be115
AW
230@require_active_login
231def media_collect(request, media):
f6bc0336 232 """Add media to collection submission"""
be5be115 233
111a609d 234 form = user_forms.MediaCollectForm(request.form)
f6bc0336 235 # A user's own collections:
2fb36dac 236 form.collection.query = Collection.query.filter_by(
0f3bf8d4
JT
237 actor=request.user.id,
238 type=Collection.USER_DEFINED_TYPE
239 ).order_by(Collection.title)
f6bc0336
SS
240
241 if request.method != 'POST' or not form.validate():
242 # No POST submission, or invalid form
243 if not form.validate():
5c7b2a63
AB
244 messages.add_message(
245 request,
246 messages.ERROR,
f6bc0336 247 _('Please check your entries and try again.'))
be5be115 248
f6bc0336
SS
249 return render_to_response(
250 request,
251 'mediagoblin/user_pages/media_collect.html',
252 {'media': media,
253 'form': form})
254
255 # If we are here, method=POST and the form is valid, submit things.
256 # If the user is adding a new collection, use that:
c5d341d7 257 if form.collection_title.data:
f6bc0336
SS
258 # Make sure this user isn't duplicating an existing collection
259 existing_collection = Collection.query.filter_by(
0f3bf8d4
JT
260 actor=request.user.id,
261 title=form.collection_title.data,
262 type=Collection.USER_DEFINED_TYPE
263 ).first()
f6bc0336 264 if existing_collection:
5c7b2a63
AB
265 messages.add_message(
266 request,
267 messages.ERROR,
268 _('You already have a collection called "%s"!') %
269 existing_collection.title)
be5be115 270 return redirect(request, "mediagoblin.user_pages.media_home",
0f3bf8d4 271 user=media.get_actor.username,
b7a3798e 272 media=media.slug_or_id)
be5be115 273
f6bc0336 274 collection = Collection()
2263a4cb
HL
275 collection.title = form.collection_title.data
276 collection.description = form.collection_description.data
0f3bf8d4
JT
277 collection.actor = request.user.id
278 collection.type = Collection.USER_DEFINED_TYPE
f6bc0336 279 collection.generate_slug()
d216d771 280 collection.get_public_id(request.urlgen)
0f3bf8d4 281 create_activity("create", collection, collection.actor)
bc2c06a1 282 collection.save()
be5be115 283
f6bc0336
SS
284 # Otherwise, use the collection selected from the drop-down
285 else:
e9330b95 286 collection = form.collection.data
0f3bf8d4 287 if collection and collection.actor != request.user.id:
e9330b95 288 collection = None
be5be115 289
f6bc0336 290 # Make sure the user actually selected a collection
0f3bf8d4
JT
291 item = CollectionItem.query.filter_by(collection=collection.id)
292 item = item.join(CollectionItem.object_helper).filter_by(
293 model_type=media.__tablename__,
294 obj_pk=media.id
295 ).first()
296
f6bc0336
SS
297 if not collection:
298 messages.add_message(
5c7b2a63
AB
299 request,
300 messages.ERROR,
f6bc0336 301 _('You have to select or add a collection'))
ba5ea989 302 return redirect(request, "mediagoblin.user_pages.media_collect",
0f3bf8d4 303 user=media.get_actor.username,
17e4679d 304 media_id=media.id)
ba5ea989 305
f6bc0336 306 # Check whether media already exists in collection
0f3bf8d4 307 elif item is not None:
5c7b2a63
AB
308 messages.add_message(
309 request,
310 messages.ERROR,
311 _('"%s" already in collection "%s"') %
312 (media.title, collection.title))
f6bc0336 313 else: # Add item to collection
6bea8a90 314 add_media_to_collection(collection, media, form.note.data)
6d36f75f 315 create_activity("add", media, request.user, target=collection)
5c7b2a63
AB
316 messages.add_message(
317 request,
318 messages.SUCCESS,
319 _('"%s" added to collection "%s"') %
320 (media.title, collection.title))
f6bc0336 321
2e6ee596 322 return redirect_obj(request, media)
f6bc0336
SS
323
324
325#TODO: Why does @user_may_delete_media not implicate @require_active_login?
686cbcd9 326@get_media_entry_by_id
d0ba136f 327@require_active_login
686cbcd9
SS
328@user_may_delete_media
329def media_confirm_delete(request, media):
330
111a609d 331 form = user_forms.ConfirmDeleteForm(request.form)
502073f2
JW
332
333 if request.method == 'POST' and form.validate():
8daef28d 334 if form.confirm.data is True:
0f3bf8d4 335 username = media.get_actor.username
bdd22421 336
d216d771
JT
337 # This probably is already filled but just in case it has slipped
338 # through the net somehow, we need to try and make sure the
339 # MediaEntry has a public ID so it gets properly soft-deleted.
340 media.get_public_id(request.urlgen)
341
342 # Decrement the users uploaded quota.
0f3bf8d4 343 media.get_actor.uploaded = media.get_actor.uploaded - \
0b95003c 344 media.file_size
0f3bf8d4 345 media.get_actor.save()
bdd22421 346
fdc34b8b 347 # Delete MediaEntry and all related files, comments etc.
502073f2 348 media.delete()
ea33f636 349 messages.add_message(
5c7b2a63
AB
350 request,
351 messages.SUCCESS,
352 _('You deleted the media.'))
502073f2 353
e7b8059f
AL
354 location = media.url_to_next(request.urlgen)
355 if not location:
356 location=media.url_to_prev(request.urlgen)
357 if not location:
bd0b5daa
RE
358 location=request.urlgen("mediagoblin.user_pages.user_home",
359 user=username)
360 return redirect(request, location=location)
502073f2 361 else:
d0ba62e2 362 messages.add_message(
5c7b2a63
AB
363 request,
364 messages.ERROR,
365 _("The media was not deleted because you didn't check "
366 "that you were sure."))
2e6ee596 367 return redirect_obj(request, media)
502073f2 368
8394febb 369 if ((request.user.has_privilege(u'admin') and
0f3bf8d4 370 request.user.id != media.actor)):
7a4c0126 371 messages.add_message(
5c7b2a63
AB
372 request,
373 messages.WARNING,
7a4c0126
CAW
374 _("You are about to delete another user's media. "
375 "Proceed with caution."))
376
502073f2
JW
377 return render_to_response(
378 request,
379 'mediagoblin/user_pages/media_confirm_delete.html',
380 {'media': media,
381 'form': form})
382
6bba33d7 383@user_not_banned
e2ae0f59 384@active_user_from_url
be5be115 385@uses_pagination
e2ae0f59 386def user_collection(request, page, url_user=None):
be5be115 387 """A User-defined Collection"""
e2ae0f59 388 collection = Collection.query.filter_by(
0f3bf8d4 389 get_actor=url_user,
e2ae0f59 390 slug=request.matchdict['collection']).first()
be5be115 391
61e39d90
JW
392 if not collection:
393 return render_404(request)
394
e2ae0f59 395 cursor = collection.get_collection_items()
be5be115
AW
396
397 pagination = Pagination(page, cursor)
398 collection_items = pagination()
399
e2ae0f59
SS
400 # if no data is available, return NotFound
401 # TODO: Should an empty collection really also return 404?
be5be115
AW
402 if collection_items == None:
403 return render_404(request)
404
405 return render_to_response(
406 request,
407 'mediagoblin/user_pages/collection.html',
e2ae0f59 408 {'user': url_user,
be5be115
AW
409 'collection': collection,
410 'collection_items': collection_items,
411 'pagination': pagination})
412
6bba33d7 413@user_not_banned
b0cc1ade 414@active_user_from_url
4f8f0353 415def collection_list(request, url_user=None):
b0cc1ade
SZ
416 """A User-defined Collection"""
417 collections = Collection.query.filter_by(
0f3bf8d4 418 get_actor=url_user)
b0cc1ade 419
b0cc1ade
SZ
420 return render_to_response(
421 request,
4f8f0353 422 'mediagoblin/user_pages/collection_list.html',
b0cc1ade 423 {'user': url_user,
947f38c0 424 'collections': collections})
b0cc1ade
SZ
425
426
be5be115
AW
427@get_user_collection_item
428@require_active_login
429@user_may_alter_collection
430def collection_item_confirm_remove(request, collection_item):
431
111a609d 432 form = user_forms.ConfirmCollectionItemRemoveForm(request.form)
be5be115
AW
433
434 if request.method == 'POST' and form.validate():
0f3bf8d4 435 username = collection_item.in_collection.get_actor.username
be5be115
AW
436 collection = collection_item.in_collection
437
438 if form.confirm.data is True:
0f3bf8d4
JT
439 obj = collection_item.get_object()
440 obj.save()
be5be115
AW
441
442 collection_item.delete()
0f3bf8d4 443 collection.num_items = collection.num_items - 1
be5be115
AW
444 collection.save()
445
446 messages.add_message(
5c7b2a63
AB
447 request,
448 messages.SUCCESS,
449 _('You deleted the item from the collection.'))
be5be115
AW
450 else:
451 messages.add_message(
5c7b2a63
AB
452 request,
453 messages.ERROR,
454 _("The item was not removed because you didn't check "
455 "that you were sure."))
be5be115 456
2e6ee596 457 return redirect_obj(request, collection)
be5be115 458
8394febb 459 if ((request.user.has_privilege(u'admin') and
0f3bf8d4 460 request.user.id != collection_item.in_collection.actor)):
be5be115 461 messages.add_message(
5c7b2a63
AB
462 request,
463 messages.WARNING,
be5be115
AW
464 _("You are about to delete an item from another user's collection. "
465 "Proceed with caution."))
466
467 return render_to_response(
468 request,
469 'mediagoblin/user_pages/collection_item_confirm_remove.html',
470 {'collection_item': collection_item,
471 'form': form})
472
473
474@get_user_collection
475@require_active_login
476@user_may_alter_collection
477def collection_confirm_delete(request, collection):
478
111a609d 479 form = user_forms.ConfirmDeleteForm(request.form)
be5be115
AW
480
481 if request.method == 'POST' and form.validate():
482
0f3bf8d4 483 username = collection.get_actor.username
be5be115
AW
484
485 if form.confirm.data is True:
486 collection_title = collection.title
487
d216d771
JT
488 # Firstly like with the MediaEntry delete, lets ensure the
489 # public_id is populated as this is really important!
490 collection.get_public_id(request.urlgen)
491
be5be115
AW
492 # Delete all the associated collection items
493 for item in collection.get_collection_items():
0f3bf8d4
JT
494 obj = item.get_object()
495 obj.save()
be5be115
AW
496 item.delete()
497
498 collection.delete()
5c7b2a63
AB
499 messages.add_message(
500 request,
501 messages.SUCCESS,
502 _('You deleted the collection "%s"') %
503 collection_title)
be5be115
AW
504
505 return redirect(request, "mediagoblin.user_pages.user_home",
506 user=username)
507 else:
508 messages.add_message(
5c7b2a63
AB
509 request,
510 messages.ERROR,
511 _("The collection was not deleted because you didn't "
512 "check that you were sure."))
be5be115 513
2e6ee596 514 return redirect_obj(request, collection)
be5be115 515
8394febb 516 if ((request.user.has_privilege(u'admin') and
0f3bf8d4 517 request.user.id != collection.actor)):
be5be115
AW
518 messages.add_message(
519 request, messages.WARNING,
520 _("You are about to delete another user's collection. "
521 "Proceed with caution."))
522
523 return render_to_response(
524 request,
525 'mediagoblin/user_pages/collection_confirm_delete.html',
526 {'collection': collection,
527 'form': form})
528
529
a5303e47 530ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15
00c39256 531
243c3843 532
00c39256
BK
533def atom_feed(request):
534 """
535 generates the atom feed with the newest images
536 """
d88fcb03 537 user = LocalUser.query.filter_by(
25625107 538 username = request.matchdict['user']).first()
539 if not user or not user.has_privilege(u'active'):
de12b4e7 540 return render_404(request)
2a01c5e7
AB
541 feed_title = "MediaGoblin Feed for user '%s'" % request.matchdict['user']
542 link = request.urlgen('mediagoblin.user_pages.user_home',
543 qualified=True, user=request.matchdict['user'])
544 cursor = MediaEntry.query.filter_by(actor=user.id, state=u'processed')
545 cursor = cursor.order_by(MediaEntry.created.desc())
546 cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
00c39256 547
00c39256 548
1df68a35
MA
549 """
550 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
551 """
5b1a7bae 552 atomlinks = [{
2a01c5e7
AB
553 'href': link,
554 'rel': 'alternate',
555 'type': 'text/html'}]
64712915 556
bb025ebd
MA
557 if mg_globals.app_config["push_urls"]:
558 for push_url in mg_globals.app_config["push_urls"]:
559 atomlinks.append({
560 'rel': 'hub',
561 'href': push_url})
5b1a7bae 562
1df68a35 563 feed = AtomFeed(
2a01c5e7
AB
564 feed_title,
565 feed_url=request.url,
566 id='tag:{host},{year}:gallery.user-{user}'.format(
567 host=request.host,
568 year=datetime.datetime.today().strftime('%Y'),
569 user=request.matchdict['user']),
570 links=atomlinks)
5b1a7bae 571
00c39256 572 for entry in cursor:
2a01c5e7
AB
573 # Include a thumbnail image in content.
574 file_urls = get_media_file_paths(entry.media_files, request.urlgen)
575 if 'thumb' in file_urls:
741c25fd 576 content = u'<img src="{thumb}" alt='' /> {desc}'.format(
2a01c5e7
AB
577 thumb=file_urls['thumb'], desc=entry.description_html)
578 else:
579 content = entry.description_html
580
0f3bf8d4
JT
581 feed.add(
582 entry.get('title'),
2a01c5e7 583 content,
64712915 584 id=entry.url_for_self(request.urlgen, qualified=True),
00c39256 585 content_type='html',
1df68a35 586 author={
0f3bf8d4 587 'name': entry.get_actor.username,
1df68a35
MA
588 'uri': request.urlgen(
589 'mediagoblin.user_pages.user_home',
2a01c5e7
AB
590 qualified=True,
591 user=entry.get_actor.username)},
00c39256 592 updated=entry.get('created'),
1df68a35
MA
593 links=[{
594 'href': entry.url_for_self(
595 request.urlgen,
be5be115
AW
596 qualified=True),
597 'rel': 'alternate',
598 'type': 'text/html'}])
599
600 return feed.get_response()
601
6d1e55b2 602
be5be115
AW
603def collection_atom_feed(request):
604 """
605 generates the atom feed with the newest images from a collection
606 """
d88fcb03 607 user = LocalUser.query.filter_by(
25625107 608 username = request.matchdict['user']).first()
609 if not user or not user.has_privilege(u'active'):
be5be115
AW
610 return render_404(request)
611
af008743 612 collection = Collection.query.filter_by(
0f3bf8d4 613 actor=user.id,
af008743 614 slug=request.matchdict['collection']).first()
61e39d90
JW
615 if not collection:
616 return render_404(request)
be5be115 617
af008743
SS
618 cursor = CollectionItem.query.filter_by(
619 collection=collection.id) \
20be9bb7 620 .order_by(CollectionItem.added.desc()) \
be5be115
AW
621 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
622
623 """
624 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
625 """
626 atomlinks = [{
fc45b386 627 'href': collection.url_for_self(request.urlgen, qualified=True),
be5be115
AW
628 'rel': 'alternate',
629 'type': 'text/html'
630 }]
631
632 if mg_globals.app_config["push_urls"]:
633 for push_url in mg_globals.app_config["push_urls"]:
634 atomlinks.append({
635 'rel': 'hub',
636 'href': push_url})
637
638 feed = AtomFeed(
19ad2e0c
JW
639 "MediaGoblin: Feed for %s's collection %s" %
640 (request.matchdict['user'], collection.title),
641 feed_url=request.url,
ab9b0b41 642 id=u'tag:{host},{year}:gnu-mediagoblin.{user}.collection.{slug}'\
19ad2e0c
JW
643 .format(
644 host=request.host,
645 year=collection.created.strftime('%Y'),
646 user=request.matchdict['user'],
647 slug=collection.slug),
648 links=atomlinks)
be5be115
AW
649
650 for item in cursor:
0f3bf8d4
JT
651 obj = item.get_object()
652 feed.add(
653 obj.get('title'),
be5be115 654 item.note_html,
0f3bf8d4 655 id=obj.url_for_self(request.urlgen, qualified=True),
be5be115
AW
656 content_type='html',
657 author={
47f7ff8d 658 'name': obj.get_actor.username,
be5be115
AW
659 'uri': request.urlgen(
660 'mediagoblin.user_pages.user_home',
47f7ff8d 661 qualified=True, user=obj.get_actor.username)},
be5be115
AW
662 updated=item.get('added'),
663 links=[{
0f3bf8d4 664 'href': obj.url_for_self(
be5be115 665 request.urlgen,
1df68a35
MA
666 qualified=True),
667 'rel': 'alternate',
668 'type': 'text/html'}])
00c39256 669
9074ee7c 670 return feed.get_response()
01c75c7e 671
75972f0a
BB
672@active_user_from_url
673@uses_pagination
01c75c7e 674@require_active_login
75972f0a 675def processing_panel(request, page, url_user):
01c75c7e
CAW
676 """
677 Show to the user what media is still in conversion/processing...
678 and what failed, and why!
679 """
d88fcb03 680 user = LocalUser.query.filter_by(username=request.matchdict['user']).first()
af008743 681 # TODO: XXX: Should this be a decorator?
01c75c7e
CAW
682 #
683 # Make sure we have permission to access this user's panel. Only
684 # admins and this user herself should be able to do so.
8394febb 685 if not (user.id == request.user.id or request.user.has_privilege(u'admin')):
af008743 686 # No? Simply redirect to this user's homepage.
01c75c7e
CAW
687 return redirect(
688 request, 'mediagoblin.user_pages.user_home',
af008743 689 user=user.username)
01c75c7e 690 # Get media entries which are in-processing
db6cc0c0 691 entries = (MediaEntry.query.filter_by(actor=user.id)
9ab3c66c 692 .order_by(MediaEntry.created.desc()))
64712915 693
906a00b6
BB
694 try:
695 state = request.matchdict['state']
696 # no exception was thrown, filter entries by state
697 entries = entries.filter_by(state=state)
698 except KeyError:
699 # show all entries
700 pass
701
75972f0a
BB
702 pagination = Pagination(page, entries)
703 pagination.per_page = 30
704 entries_on_a_page = pagination()
705
01c75c7e
CAW
706 # Render to response
707 return render_to_response(
708 request,
709 'mediagoblin/user_pages/processing_panel.html',
710 {'user': user,
75972f0a
BB
711 'entries': entries_on_a_page,
712 'pagination': pagination})
30a9fe7c 713
6483b370 714@allow_reporting
30a9fe7c 715@get_user_media_entry
3fb96fc9 716@user_has_privilege(u'reporter')
8e91df87 717@get_optional_media_comment_by_id
718def file_a_report(request, media, comment):
719 """
64a456a4 720 This view handles the filing of a Report.
8e91df87 721 """
30a9fe7c 722 if comment is not None:
64a456a4 723 if not comment.target().id == media.id:
8e91df87 724 return render_404(request)
725
f26c21cd 726 form = user_forms.CommentReportForm(request.form)
64a456a4 727 context = {'media': comment.target(),
b1d4973c 728 'comment':comment,
f26c21cd 729 'form':form}
30a9fe7c 730 else:
f26c21cd 731 form = user_forms.MediaReportForm(request.form)
f26c21cd 732 context = {'media': media,
733 'form':form}
8e91df87 734 form.reporter_id.data = request.user.id
735
f26c21cd 736
737 if request.method == "POST":
64a456a4
JT
738 report_object = build_report_object(
739 form,
dfd66b78 740 media_entry=media,
64a456a4
JT
741 comment=comment
742 )
f26c21cd 743
744 # if the object was built successfully, report_table will not be None
dfd66b78 745 if report_object:
746 report_object.save()
f26c21cd 747 return redirect(
748 request,
749 'index')
750
9b8ef022 751
30a9fe7c 752 return render_to_response(
9b8ef022 753 request,
754 'mediagoblin/user_pages/report.html',
755 context)
4fd52036
JT
756
757@require_active_login
758def activity_view(request):
759 """ /<username>/activity/<id> - Display activity
760
761 This should display a HTML presentation of the activity
762 this is NOT an API endpoint.
763 """
764 # Get the user object.
765 username = request.matchdict["username"]
d88fcb03 766 user = LocalUser.query.filter_by(username=username).first()
4fd52036
JT
767
768 activity_id = request.matchdict["id"]
769
770 if request.user is None:
771 return render_404(request)
772
773 activity = Activity.query.filter_by(
774 id=activity_id,
775 author=user.id
776 ).first()
777
d216d771
JT
778 # There isn't many places to check that the public_id is filled so this
779 # will do, it really should be, lets try and fix that if it isn't.
780 activity.get_public_id(request.urlgen)
781
4fd52036
JT
782 if activity is None:
783 return render_404(request)
784
785 return render_to_response(
786 request,
787 "mediagoblin/api/activity.html",
788 {"activity": activity}
789 )