Note issues uploading H264 video under Guix.
[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
2f2b4cba 182 # If media is not processed, return NotFound.
183 if not media.state == u'processed':
184 return render_404(request)
185
64a456a4 186 comment = request.db.TextComment()
0f3bf8d4 187 comment.actor = request.user.id
e49b7e02 188 comment.content = six.text_type(request.form['comment_content'])
9074ee7c 189
99338b6a
DT
190 # Show error message if commenting is disabled.
191 if not mg_globals.app_config['allow_comments']:
192 messages.add_message(
193 request,
194 messages.ERROR,
195 _("Sorry, comments are disabled."))
196 elif not comment.content.strip():
7298ffa1
AW
197 messages.add_message(
198 request,
199 messages.ERROR,
eae7d058 200 _("Oops, your comment was empty."))
7298ffa1 201 else:
0f3bf8d4 202 create_activity("post", comment, comment.actor, target=media)
bc2c06a1 203 add_comment_subscription(request.user, media)
7298ffa1 204 comment.save()
b5d3aec6 205
64a456a4
JT
206 link = request.db.Comment()
207 link.target = media
208 link.comment = comment
209 link.save()
210
7298ffa1 211 messages.add_message(
5c7b2a63
AB
212 request,
213 messages.SUCCESS,
eae7d058 214 _('Your comment has been posted!'))
6efcab2d 215 trigger_notification(link, media, request)
252eaf21 216
2e6ee596 217 return redirect_obj(request, media)
00c39256 218
95e6da02 219
5ab60299
EL
220
221def media_preview_comment(request):
3bd62dc4 222 """Runs a comment through markdown so it can be previewed."""
202d951c
RE
223 # If this isn't an ajax request, render_404
224 if not request.is_xhr:
225 return render_404(request)
226
e49b7e02 227 comment = six.text_type(request.form['comment_content'])
3bd62dc4 228 cleancomment = { "content":cleaned_markdown_conversion(comment)}
5ab60299 229
3bd62dc4 230 return Response(json.dumps(cleancomment))
5ab60299 231
6bba33d7 232@user_not_banned
96a2249b 233@get_media_entry_by_id
be5be115
AW
234@require_active_login
235def media_collect(request, media):
f6bc0336 236 """Add media to collection submission"""
be5be115 237
2f2b4cba 238 # If media is not processed, return NotFound.
239 if not media.state == u'processed':
240 return render_404(request)
241
111a609d 242 form = user_forms.MediaCollectForm(request.form)
f6bc0336 243 # A user's own collections:
2fb36dac 244 form.collection.query = Collection.query.filter_by(
0f3bf8d4
JT
245 actor=request.user.id,
246 type=Collection.USER_DEFINED_TYPE
247 ).order_by(Collection.title)
f6bc0336
SS
248
249 if request.method != 'POST' or not form.validate():
250 # No POST submission, or invalid form
251 if not form.validate():
5c7b2a63
AB
252 messages.add_message(
253 request,
254 messages.ERROR,
f6bc0336 255 _('Please check your entries and try again.'))
be5be115 256
f6bc0336
SS
257 return render_to_response(
258 request,
259 'mediagoblin/user_pages/media_collect.html',
260 {'media': media,
261 'form': form})
262
263 # If we are here, method=POST and the form is valid, submit things.
264 # If the user is adding a new collection, use that:
c5d341d7 265 if form.collection_title.data:
f6bc0336
SS
266 # Make sure this user isn't duplicating an existing collection
267 existing_collection = Collection.query.filter_by(
0f3bf8d4
JT
268 actor=request.user.id,
269 title=form.collection_title.data,
270 type=Collection.USER_DEFINED_TYPE
271 ).first()
f6bc0336 272 if existing_collection:
5c7b2a63
AB
273 messages.add_message(
274 request,
275 messages.ERROR,
276 _('You already have a collection called "%s"!') %
277 existing_collection.title)
be5be115 278 return redirect(request, "mediagoblin.user_pages.media_home",
0f3bf8d4 279 user=media.get_actor.username,
b7a3798e 280 media=media.slug_or_id)
be5be115 281
f6bc0336 282 collection = Collection()
2263a4cb
HL
283 collection.title = form.collection_title.data
284 collection.description = form.collection_description.data
0f3bf8d4
JT
285 collection.actor = request.user.id
286 collection.type = Collection.USER_DEFINED_TYPE
f6bc0336 287 collection.generate_slug()
d216d771 288 collection.get_public_id(request.urlgen)
0f3bf8d4 289 create_activity("create", collection, collection.actor)
bc2c06a1 290 collection.save()
be5be115 291
f6bc0336
SS
292 # Otherwise, use the collection selected from the drop-down
293 else:
e9330b95 294 collection = form.collection.data
0f3bf8d4 295 if collection and collection.actor != request.user.id:
e9330b95 296 collection = None
be5be115 297
f6bc0336
SS
298 # Make sure the user actually selected a collection
299 if not collection:
300 messages.add_message(
5c7b2a63
AB
301 request,
302 messages.ERROR,
f6bc0336 303 _('You have to select or add a collection'))
ba5ea989 304 return redirect(request, "mediagoblin.user_pages.media_collect",
0f3bf8d4 305 user=media.get_actor.username,
17e4679d 306 media_id=media.id)
ba5ea989 307
763eae89 308 item = CollectionItem.query.filter_by(collection=collection.id)
309 item = item.join(CollectionItem.object_helper).filter_by(
310 model_type=media.__tablename__,
311 obj_pk=media.id
312 ).first()
313
f6bc0336 314 # Check whether media already exists in collection
763eae89 315 if item is not None:
5c7b2a63
AB
316 messages.add_message(
317 request,
318 messages.ERROR,
319 _('"%s" already in collection "%s"') %
320 (media.title, collection.title))
f6bc0336 321 else: # Add item to collection
6bea8a90 322 add_media_to_collection(collection, media, form.note.data)
6d36f75f 323 create_activity("add", media, request.user, target=collection)
5c7b2a63
AB
324 messages.add_message(
325 request,
326 messages.SUCCESS,
327 _('"%s" added to collection "%s"') %
328 (media.title, collection.title))
f6bc0336 329
2e6ee596 330 return redirect_obj(request, media)
f6bc0336
SS
331
332
333#TODO: Why does @user_may_delete_media not implicate @require_active_login?
686cbcd9 334@get_media_entry_by_id
d0ba136f 335@require_active_login
686cbcd9
SS
336@user_may_delete_media
337def media_confirm_delete(request, media):
338
111a609d 339 form = user_forms.ConfirmDeleteForm(request.form)
502073f2
JW
340
341 if request.method == 'POST' and form.validate():
8daef28d 342 if form.confirm.data is True:
0f3bf8d4 343 username = media.get_actor.username
bdd22421 344
d216d771
JT
345 # This probably is already filled but just in case it has slipped
346 # through the net somehow, we need to try and make sure the
347 # MediaEntry has a public ID so it gets properly soft-deleted.
348 media.get_public_id(request.urlgen)
349
350 # Decrement the users uploaded quota.
0f3bf8d4 351 media.get_actor.uploaded = media.get_actor.uploaded - \
0b95003c 352 media.file_size
0f3bf8d4 353 media.get_actor.save()
bdd22421 354
fdc34b8b 355 # Delete MediaEntry and all related files, comments etc.
502073f2 356 media.delete()
ea33f636 357 messages.add_message(
5c7b2a63
AB
358 request,
359 messages.SUCCESS,
360 _('You deleted the media.'))
502073f2 361
e7b8059f
AL
362 location = media.url_to_next(request.urlgen)
363 if not location:
364 location=media.url_to_prev(request.urlgen)
365 if not location:
bd0b5daa
RE
366 location=request.urlgen("mediagoblin.user_pages.user_home",
367 user=username)
368 return redirect(request, location=location)
502073f2 369 else:
d0ba62e2 370 messages.add_message(
5c7b2a63
AB
371 request,
372 messages.ERROR,
373 _("The media was not deleted because you didn't check "
374 "that you were sure."))
2e6ee596 375 return redirect_obj(request, media)
502073f2 376
8394febb 377 if ((request.user.has_privilege(u'admin') and
0f3bf8d4 378 request.user.id != media.actor)):
7a4c0126 379 messages.add_message(
5c7b2a63
AB
380 request,
381 messages.WARNING,
7a4c0126
CAW
382 _("You are about to delete another user's media. "
383 "Proceed with caution."))
384
502073f2
JW
385 return render_to_response(
386 request,
387 'mediagoblin/user_pages/media_confirm_delete.html',
388 {'media': media,
389 'form': form})
390
6bba33d7 391@user_not_banned
e2ae0f59 392@active_user_from_url
be5be115 393@uses_pagination
e2ae0f59 394def user_collection(request, page, url_user=None):
be5be115 395 """A User-defined Collection"""
e2ae0f59 396 collection = Collection.query.filter_by(
0f3bf8d4 397 get_actor=url_user,
e2ae0f59 398 slug=request.matchdict['collection']).first()
be5be115 399
61e39d90
JW
400 if not collection:
401 return render_404(request)
402
e2ae0f59 403 cursor = collection.get_collection_items()
be5be115
AW
404
405 pagination = Pagination(page, cursor)
406 collection_items = pagination()
407
e2ae0f59
SS
408 # if no data is available, return NotFound
409 # TODO: Should an empty collection really also return 404?
be5be115
AW
410 if collection_items == None:
411 return render_404(request)
412
413 return render_to_response(
414 request,
415 'mediagoblin/user_pages/collection.html',
e2ae0f59 416 {'user': url_user,
be5be115
AW
417 'collection': collection,
418 'collection_items': collection_items,
419 'pagination': pagination})
420
6bba33d7 421@user_not_banned
b0cc1ade 422@active_user_from_url
4f8f0353 423def collection_list(request, url_user=None):
b0cc1ade
SZ
424 """A User-defined Collection"""
425 collections = Collection.query.filter_by(
0f3bf8d4 426 get_actor=url_user)
b0cc1ade 427
b0cc1ade
SZ
428 return render_to_response(
429 request,
4f8f0353 430 'mediagoblin/user_pages/collection_list.html',
b0cc1ade 431 {'user': url_user,
947f38c0 432 'collections': collections})
b0cc1ade
SZ
433
434
be5be115
AW
435@get_user_collection_item
436@require_active_login
437@user_may_alter_collection
438def collection_item_confirm_remove(request, collection_item):
439
111a609d 440 form = user_forms.ConfirmCollectionItemRemoveForm(request.form)
be5be115
AW
441
442 if request.method == 'POST' and form.validate():
0f3bf8d4 443 username = collection_item.in_collection.get_actor.username
be5be115
AW
444 collection = collection_item.in_collection
445
446 if form.confirm.data is True:
0f3bf8d4
JT
447 obj = collection_item.get_object()
448 obj.save()
be5be115
AW
449
450 collection_item.delete()
0f3bf8d4 451 collection.num_items = collection.num_items - 1
be5be115
AW
452 collection.save()
453
454 messages.add_message(
5c7b2a63
AB
455 request,
456 messages.SUCCESS,
457 _('You deleted the item from the collection.'))
be5be115
AW
458 else:
459 messages.add_message(
5c7b2a63
AB
460 request,
461 messages.ERROR,
462 _("The item was not removed because you didn't check "
463 "that you were sure."))
be5be115 464
2e6ee596 465 return redirect_obj(request, collection)
be5be115 466
8394febb 467 if ((request.user.has_privilege(u'admin') and
0f3bf8d4 468 request.user.id != collection_item.in_collection.actor)):
be5be115 469 messages.add_message(
5c7b2a63
AB
470 request,
471 messages.WARNING,
be5be115
AW
472 _("You are about to delete an item from another user's collection. "
473 "Proceed with caution."))
474
475 return render_to_response(
476 request,
477 'mediagoblin/user_pages/collection_item_confirm_remove.html',
478 {'collection_item': collection_item,
479 'form': form})
480
481
482@get_user_collection
483@require_active_login
484@user_may_alter_collection
485def collection_confirm_delete(request, collection):
486
111a609d 487 form = user_forms.ConfirmDeleteForm(request.form)
be5be115
AW
488
489 if request.method == 'POST' and form.validate():
490
0f3bf8d4 491 username = collection.get_actor.username
be5be115
AW
492
493 if form.confirm.data is True:
494 collection_title = collection.title
495
d216d771
JT
496 # Firstly like with the MediaEntry delete, lets ensure the
497 # public_id is populated as this is really important!
498 collection.get_public_id(request.urlgen)
499
be5be115
AW
500 # Delete all the associated collection items
501 for item in collection.get_collection_items():
0f3bf8d4
JT
502 obj = item.get_object()
503 obj.save()
be5be115
AW
504 item.delete()
505
506 collection.delete()
5c7b2a63
AB
507 messages.add_message(
508 request,
509 messages.SUCCESS,
510 _('You deleted the collection "%s"') %
511 collection_title)
be5be115
AW
512
513 return redirect(request, "mediagoblin.user_pages.user_home",
514 user=username)
515 else:
516 messages.add_message(
5c7b2a63
AB
517 request,
518 messages.ERROR,
519 _("The collection was not deleted because you didn't "
520 "check that you were sure."))
be5be115 521
2e6ee596 522 return redirect_obj(request, collection)
be5be115 523
8394febb 524 if ((request.user.has_privilege(u'admin') and
0f3bf8d4 525 request.user.id != collection.actor)):
be5be115
AW
526 messages.add_message(
527 request, messages.WARNING,
528 _("You are about to delete another user's collection. "
529 "Proceed with caution."))
530
531 return render_to_response(
532 request,
533 'mediagoblin/user_pages/collection_confirm_delete.html',
534 {'collection': collection,
535 'form': form})
536
537
a5303e47 538ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15
00c39256 539
243c3843 540
00c39256
BK
541def atom_feed(request):
542 """
543 generates the atom feed with the newest images
544 """
d88fcb03 545 user = LocalUser.query.filter_by(
25625107 546 username = request.matchdict['user']).first()
547 if not user or not user.has_privilege(u'active'):
de12b4e7 548 return render_404(request)
2a01c5e7
AB
549 feed_title = "MediaGoblin Feed for user '%s'" % request.matchdict['user']
550 link = request.urlgen('mediagoblin.user_pages.user_home',
551 qualified=True, user=request.matchdict['user'])
552 cursor = MediaEntry.query.filter_by(actor=user.id, state=u'processed')
553 cursor = cursor.order_by(MediaEntry.created.desc())
554 cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
00c39256 555
00c39256 556
1df68a35
MA
557 """
558 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
559 """
5b1a7bae 560 atomlinks = [{
2a01c5e7
AB
561 'href': link,
562 'rel': 'alternate',
563 'type': 'text/html'}]
64712915 564
bb025ebd
MA
565 if mg_globals.app_config["push_urls"]:
566 for push_url in mg_globals.app_config["push_urls"]:
567 atomlinks.append({
568 'rel': 'hub',
569 'href': push_url})
5b1a7bae 570
1df68a35 571 feed = AtomFeed(
2a01c5e7
AB
572 feed_title,
573 feed_url=request.url,
574 id='tag:{host},{year}:gallery.user-{user}'.format(
575 host=request.host,
576 year=datetime.datetime.today().strftime('%Y'),
577 user=request.matchdict['user']),
578 links=atomlinks)
5b1a7bae 579
00c39256 580 for entry in cursor:
2a01c5e7
AB
581 # Include a thumbnail image in content.
582 file_urls = get_media_file_paths(entry.media_files, request.urlgen)
583 if 'thumb' in file_urls:
741c25fd 584 content = u'<img src="{thumb}" alt='' /> {desc}'.format(
2a01c5e7
AB
585 thumb=file_urls['thumb'], desc=entry.description_html)
586 else:
587 content = entry.description_html
588
0f3bf8d4
JT
589 feed.add(
590 entry.get('title'),
2a01c5e7 591 content,
64712915 592 id=entry.url_for_self(request.urlgen, qualified=True),
00c39256 593 content_type='html',
1df68a35 594 author={
0f3bf8d4 595 'name': entry.get_actor.username,
1df68a35
MA
596 'uri': request.urlgen(
597 'mediagoblin.user_pages.user_home',
2a01c5e7
AB
598 qualified=True,
599 user=entry.get_actor.username)},
00c39256 600 updated=entry.get('created'),
1df68a35
MA
601 links=[{
602 'href': entry.url_for_self(
603 request.urlgen,
be5be115
AW
604 qualified=True),
605 'rel': 'alternate',
606 'type': 'text/html'}])
607
608 return feed.get_response()
609
6d1e55b2 610
be5be115
AW
611def collection_atom_feed(request):
612 """
613 generates the atom feed with the newest images from a collection
614 """
d88fcb03 615 user = LocalUser.query.filter_by(
25625107 616 username = request.matchdict['user']).first()
617 if not user or not user.has_privilege(u'active'):
be5be115
AW
618 return render_404(request)
619
af008743 620 collection = Collection.query.filter_by(
0f3bf8d4 621 actor=user.id,
af008743 622 slug=request.matchdict['collection']).first()
61e39d90
JW
623 if not collection:
624 return render_404(request)
be5be115 625
af008743
SS
626 cursor = CollectionItem.query.filter_by(
627 collection=collection.id) \
20be9bb7 628 .order_by(CollectionItem.added.desc()) \
be5be115
AW
629 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
630
631 """
632 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
633 """
634 atomlinks = [{
fc45b386 635 'href': collection.url_for_self(request.urlgen, qualified=True),
be5be115
AW
636 'rel': 'alternate',
637 'type': 'text/html'
638 }]
639
640 if mg_globals.app_config["push_urls"]:
641 for push_url in mg_globals.app_config["push_urls"]:
642 atomlinks.append({
643 'rel': 'hub',
644 'href': push_url})
645
646 feed = AtomFeed(
19ad2e0c
JW
647 "MediaGoblin: Feed for %s's collection %s" %
648 (request.matchdict['user'], collection.title),
649 feed_url=request.url,
ab9b0b41 650 id=u'tag:{host},{year}:gnu-mediagoblin.{user}.collection.{slug}'\
19ad2e0c
JW
651 .format(
652 host=request.host,
653 year=collection.created.strftime('%Y'),
654 user=request.matchdict['user'],
655 slug=collection.slug),
656 links=atomlinks)
be5be115
AW
657
658 for item in cursor:
0f3bf8d4
JT
659 obj = item.get_object()
660 feed.add(
661 obj.get('title'),
be5be115 662 item.note_html,
0f3bf8d4 663 id=obj.url_for_self(request.urlgen, qualified=True),
be5be115
AW
664 content_type='html',
665 author={
47f7ff8d 666 'name': obj.get_actor.username,
be5be115
AW
667 'uri': request.urlgen(
668 'mediagoblin.user_pages.user_home',
47f7ff8d 669 qualified=True, user=obj.get_actor.username)},
be5be115
AW
670 updated=item.get('added'),
671 links=[{
0f3bf8d4 672 'href': obj.url_for_self(
be5be115 673 request.urlgen,
1df68a35
MA
674 qualified=True),
675 'rel': 'alternate',
676 'type': 'text/html'}])
00c39256 677
9074ee7c 678 return feed.get_response()
01c75c7e 679
75972f0a
BB
680@active_user_from_url
681@uses_pagination
01c75c7e 682@require_active_login
75972f0a 683def processing_panel(request, page, url_user):
01c75c7e
CAW
684 """
685 Show to the user what media is still in conversion/processing...
686 and what failed, and why!
687 """
d88fcb03 688 user = LocalUser.query.filter_by(username=request.matchdict['user']).first()
af008743 689 # TODO: XXX: Should this be a decorator?
01c75c7e
CAW
690 #
691 # Make sure we have permission to access this user's panel. Only
692 # admins and this user herself should be able to do so.
8394febb 693 if not (user.id == request.user.id or request.user.has_privilege(u'admin')):
af008743 694 # No? Simply redirect to this user's homepage.
01c75c7e
CAW
695 return redirect(
696 request, 'mediagoblin.user_pages.user_home',
af008743 697 user=user.username)
01c75c7e 698 # Get media entries which are in-processing
db6cc0c0 699 entries = (MediaEntry.query.filter_by(actor=user.id)
9ab3c66c 700 .order_by(MediaEntry.created.desc()))
64712915 701
906a00b6
BB
702 try:
703 state = request.matchdict['state']
704 # no exception was thrown, filter entries by state
705 entries = entries.filter_by(state=state)
706 except KeyError:
707 # show all entries
708 pass
709
75972f0a
BB
710 pagination = Pagination(page, entries)
711 pagination.per_page = 30
712 entries_on_a_page = pagination()
713
01c75c7e
CAW
714 # Render to response
715 return render_to_response(
716 request,
717 'mediagoblin/user_pages/processing_panel.html',
718 {'user': user,
75972f0a
BB
719 'entries': entries_on_a_page,
720 'pagination': pagination})
30a9fe7c 721
6483b370 722@allow_reporting
30a9fe7c 723@get_user_media_entry
3fb96fc9 724@user_has_privilege(u'reporter')
8e91df87 725@get_optional_media_comment_by_id
726def file_a_report(request, media, comment):
727 """
64a456a4 728 This view handles the filing of a Report.
8e91df87 729 """
30a9fe7c 730 if comment is not None:
64a456a4 731 if not comment.target().id == media.id:
8e91df87 732 return render_404(request)
733
f26c21cd 734 form = user_forms.CommentReportForm(request.form)
64a456a4 735 context = {'media': comment.target(),
b1d4973c 736 'comment':comment,
f26c21cd 737 'form':form}
30a9fe7c 738 else:
f26c21cd 739 form = user_forms.MediaReportForm(request.form)
f26c21cd 740 context = {'media': media,
741 'form':form}
8e91df87 742 form.reporter_id.data = request.user.id
743
f26c21cd 744
745 if request.method == "POST":
64a456a4
JT
746 report_object = build_report_object(
747 form,
dfd66b78 748 media_entry=media,
64a456a4
JT
749 comment=comment
750 )
f26c21cd 751
752 # if the object was built successfully, report_table will not be None
dfd66b78 753 if report_object:
754 report_object.save()
f26c21cd 755 return redirect(
756 request,
757 'index')
758
9b8ef022 759
30a9fe7c 760 return render_to_response(
9b8ef022 761 request,
762 'mediagoblin/user_pages/report.html',
763 context)
4fd52036
JT
764
765@require_active_login
766def activity_view(request):
767 """ /<username>/activity/<id> - Display activity
768
769 This should display a HTML presentation of the activity
770 this is NOT an API endpoint.
771 """
772 # Get the user object.
773 username = request.matchdict["username"]
d88fcb03 774 user = LocalUser.query.filter_by(username=username).first()
4fd52036
JT
775
776 activity_id = request.matchdict["id"]
777
778 if request.user is None:
779 return render_404(request)
780
781 activity = Activity.query.filter_by(
782 id=activity_id,
783 author=user.id
784 ).first()
785
d216d771
JT
786 # There isn't many places to check that the public_id is filled so this
787 # will do, it really should be, lets try and fix that if it isn't.
788 activity.get_public_id(request.urlgen)
789
4fd52036
JT
790 if activity is None:
791 return render_404(request)
792
793 return render_to_response(
794 request,
795 "mediagoblin/api/activity.html",
796 {"activity": activity}
797 )