skip audio reprocessing if necessary
[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
3a8c3a38 21from mediagoblin import messages, mg_globals
f2c0bf3e
SS
22from mediagoblin.db.models import (MediaEntry, MediaTag, Collection,
23 CollectionItem, User)
2e6ee596
E
24from mediagoblin.tools.response import render_to_response, render_404, \
25 redirect, redirect_obj
5ab60299 26from mediagoblin.tools.text import cleaned_markdown_conversion
a789b713 27from mediagoblin.tools.translate import pass_to_ugettext as _
152a3bfa 28from mediagoblin.tools.pagination import Pagination
9074ee7c 29from mediagoblin.user_pages import forms as user_forms
2d7b6bde
JW
30from mediagoblin.user_pages.lib import add_media_to_collection
31from mediagoblin.notifications import trigger_notification, \
32 add_comment_subscription, mark_comment_notification_seen
50854db0 33from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
461dd971 34 get_media_entry_by_id,
6d1e55b2 35 require_active_login, user_may_delete_media, user_may_alter_collection,
64c2a400 36 get_user_collection, get_user_collection_item, active_user_from_url)
9a16e16f 37
00c39256 38from werkzeug.contrib.atom import AtomFeed
2d7b6bde 39from werkzeug.exceptions import MethodNotAllowed
5ab60299 40from werkzeug.wrappers import Response
1301a8ad 41
9074ee7c 42
fb2fbe2c
JAN
43_log = logging.getLogger(__name__)
44_log.setLevel(logging.DEBUG)
45
64712915 46
3eb6fc4f 47@uses_pagination
1301a8ad 48def user_home(request, page):
9a16e16f 49 """'Homepage' of a User()"""
af008743
SS
50 # TODO: decide if we only want homepages for active users, we can
51 # then use the @get_active_user decorator and also simplify the
52 # template html.
53 user = User.query.filter_by(username=request.matchdict['user']).first()
7acdbfd3 54 if not user:
de12b4e7 55 return render_404(request)
7a3d00ec 56 elif user.status != u'active':
990d3b69
CAW
57 return render_to_response(
58 request,
59 'mediagoblin/user_pages/user.html',
60 {'user': user})
9a16e16f 61
2fb36dac
SS
62 cursor = MediaEntry.query.\
63 filter_by(uploader = user.id,
0efe9e27 64 state = u'processed').order_by(MediaEntry.created.desc())
9a16e16f 65
1301a8ad 66 pagination = Pagination(page, cursor)
ca3ca51c 67 media_entries = pagination()
44e3e917 68
ae85ed0f
BK
69 #if no data is available, return NotFound
70 if media_entries == None:
de12b4e7 71 return render_404(request)
243c3843 72
5949be9a
CAW
73 user_gallery_url = request.urlgen(
74 'mediagoblin.user_pages.user_gallery',
5a4e3ff1 75 user=user.username)
5949be9a 76
9038c9f9
CAW
77 return render_to_response(
78 request,
c9c24934
E
79 'mediagoblin/user_pages/user.html',
80 {'user': user,
5949be9a 81 'user_gallery_url': user_gallery_url,
c9c24934
E
82 'media_entries': media_entries,
83 'pagination': pagination})
f6249408 84
243c3843 85
64c2a400 86@active_user_from_url
184f2240 87@uses_pagination
64c2a400 88def user_gallery(request, page, url_user=None):
184f2240 89 """'Gallery' of a User()"""
f2c0bf3e 90 tag = request.matchdict.get('tag', None)
64c2a400
SS
91 cursor = MediaEntry.query.filter_by(
92 uploader=url_user.id,
93 state=u'processed').order_by(MediaEntry.created.desc())
af008743 94
f2c0bf3e
SS
95 # Filter potentially by tag too:
96 if tag:
97 cursor = cursor.filter(
98 MediaEntry.tags_helper.any(
38905733 99 MediaTag.slug == request.matchdict['tag']))
f2c0bf3e 100
64c2a400 101 # Paginate gallery
184f2240 102 pagination = Pagination(page, cursor)
103 media_entries = pagination()
104
105 #if no data is available, return NotFound
64c2a400 106 # TODO: Should we really also return 404 for empty galleries?
184f2240 107 if media_entries == None:
de12b4e7 108 return render_404(request)
243c3843 109
4b5f5a08 110 return render_to_response(
111 request,
112 'mediagoblin/user_pages/gallery.html',
f2c0bf3e 113 {'user': url_user, 'tag': tag,
4b5f5a08 114 'media_entries': media_entries,
115 'pagination': pagination})
184f2240 116
2d7b6bde 117
6f59a3a3 118MEDIA_COMMENTS_PER_PAGE = 50
434b3221 119
243c3843 120
01674e10 121@get_user_media_entry
9074ee7c 122@uses_pagination
6f59a3a3 123def media_home(request, media, page, **kwargs):
9074ee7c
JW
124 """
125 'Homepage' of a MediaEntry()
126 """
7d16a01b
E
127 comment_id = request.matchdict.get('comment', None)
128 if comment_id:
2d7b6bde
JW
129 if request.user:
130 mark_comment_notification_seen(comment_id, request.user)
131
af2fcba5 132 pagination = Pagination(
7c378f2c
CAW
133 page, media.get_comments(
134 mg_globals.app_config['comments_ascending']),
135 MEDIA_COMMENTS_PER_PAGE,
7d16a01b 136 comment_id)
af2fcba5
JW
137 else:
138 pagination = Pagination(
7c378f2c
CAW
139 page, media.get_comments(
140 mg_globals.app_config['comments_ascending']),
141 MEDIA_COMMENTS_PER_PAGE)
9074ee7c 142
6f59a3a3 143 comments = pagination()
9074ee7c 144
111a609d 145 comment_form = user_forms.MediaCommentForm(request.form)
9074ee7c 146
58a94757 147 media_template_name = media.media_manager.display_template
93bdab9d 148
9038c9f9
CAW
149 return render_to_response(
150 request,
93bdab9d 151 media_template_name,
9074ee7c
JW
152 {'media': media,
153 'comments': comments,
154 'pagination': pagination,
3a8c3a38
JW
155 'comment_form': comment_form,
156 'app_config': mg_globals.app_config})
9074ee7c 157
95e6da02 158
461dd971 159@get_media_entry_by_id
9074ee7c 160@require_active_login
95e12bf2 161def media_post_comment(request, media):
9074ee7c
JW
162 """
163 recieves POST from a MediaEntry() comment form, saves the comment.
164 """
2d7b6bde
JW
165 if not request.method == 'POST':
166 raise MethodNotAllowed()
95e12bf2 167
9074ee7c 168 comment = request.db.MediaComment()
8efcd405
E
169 comment.media_entry = media.id
170 comment.author = request.user.id
5ab60299 171 print request.form['comment_content']
111a609d 172 comment.content = unicode(request.form['comment_content'])
9074ee7c 173
99338b6a
DT
174 # Show error message if commenting is disabled.
175 if not mg_globals.app_config['allow_comments']:
176 messages.add_message(
177 request,
178 messages.ERROR,
179 _("Sorry, comments are disabled."))
180 elif not comment.content.strip():
7298ffa1
AW
181 messages.add_message(
182 request,
183 messages.ERROR,
eae7d058 184 _("Oops, your comment was empty."))
7298ffa1
AW
185 else:
186 comment.save()
b5d3aec6 187
7298ffa1
AW
188 messages.add_message(
189 request, messages.SUCCESS,
eae7d058 190 _('Your comment has been posted!'))
52359e91 191
2d7b6bde
JW
192 trigger_notification(comment, media, request)
193
194 add_comment_subscription(request.user, media)
252eaf21 195
2e6ee596 196 return redirect_obj(request, media)
00c39256 197
95e6da02 198
5ab60299
EL
199
200def media_preview_comment(request):
3bd62dc4 201 """Runs a comment through markdown so it can be previewed."""
202d951c
RE
202 # If this isn't an ajax request, render_404
203 if not request.is_xhr:
204 return render_404(request)
205
3bd62dc4
EL
206 comment = unicode(request.form['comment_content'])
207 cleancomment = { "content":cleaned_markdown_conversion(comment)}
5ab60299 208
3bd62dc4 209 return Response(json.dumps(cleancomment))
5ab60299 210
96a2249b 211@get_media_entry_by_id
be5be115
AW
212@require_active_login
213def media_collect(request, media):
f6bc0336 214 """Add media to collection submission"""
be5be115 215
111a609d 216 form = user_forms.MediaCollectForm(request.form)
f6bc0336 217 # A user's own collections:
2fb36dac
SS
218 form.collection.query = Collection.query.filter_by(
219 creator = request.user.id).order_by(Collection.title)
f6bc0336
SS
220
221 if request.method != 'POST' or not form.validate():
222 # No POST submission, or invalid form
223 if not form.validate():
224 messages.add_message(request, messages.ERROR,
225 _('Please check your entries and try again.'))
be5be115 226
f6bc0336
SS
227 return render_to_response(
228 request,
229 'mediagoblin/user_pages/media_collect.html',
230 {'media': media,
231 'form': form})
232
233 # If we are here, method=POST and the form is valid, submit things.
234 # If the user is adding a new collection, use that:
c5d341d7 235 if form.collection_title.data:
f6bc0336
SS
236 # Make sure this user isn't duplicating an existing collection
237 existing_collection = Collection.query.filter_by(
238 creator=request.user.id,
2263a4cb 239 title=form.collection_title.data).first()
f6bc0336
SS
240 if existing_collection:
241 messages.add_message(request, messages.ERROR,
918d811a
E
242 _('You already have a collection called "%s"!')
243 % existing_collection.title)
be5be115 244 return redirect(request, "mediagoblin.user_pages.media_home",
b7a3798e
E
245 user=media.get_uploader.username,
246 media=media.slug_or_id)
be5be115 247
f6bc0336 248 collection = Collection()
2263a4cb
HL
249 collection.title = form.collection_title.data
250 collection.description = form.collection_description.data
f6bc0336
SS
251 collection.creator = request.user.id
252 collection.generate_slug()
b39d1f23 253 collection.save()
be5be115 254
f6bc0336
SS
255 # Otherwise, use the collection selected from the drop-down
256 else:
e9330b95
E
257 collection = form.collection.data
258 if collection and collection.creator != request.user.id:
259 collection = None
be5be115 260
f6bc0336
SS
261 # Make sure the user actually selected a collection
262 if not collection:
263 messages.add_message(
264 request, messages.ERROR,
265 _('You have to select or add a collection'))
ba5ea989
SS
266 return redirect(request, "mediagoblin.user_pages.media_collect",
267 user=media.get_uploader.username,
17e4679d 268 media_id=media.id)
ba5ea989 269
f6bc0336
SS
270
271 # Check whether media already exists in collection
272 elif CollectionItem.query.filter_by(
273 media_entry=media.id,
274 collection=collection.id).first():
275 messages.add_message(request, messages.ERROR,
918d811a
E
276 _('"%s" already in collection "%s"')
277 % (media.title, collection.title))
f6bc0336 278 else: # Add item to collection
6bea8a90 279 add_media_to_collection(collection, media, form.note.data)
f6bc0336
SS
280
281 messages.add_message(request, messages.SUCCESS,
918d811a
E
282 _('"%s" added to collection "%s"')
283 % (media.title, collection.title))
f6bc0336 284
2e6ee596 285 return redirect_obj(request, media)
f6bc0336
SS
286
287
288#TODO: Why does @user_may_delete_media not implicate @require_active_login?
461dd971 289@get_media_entry_by_id
502073f2
JW
290@require_active_login
291@user_may_delete_media
292def media_confirm_delete(request, media):
293
111a609d 294 form = user_forms.ConfirmDeleteForm(request.form)
502073f2
JW
295
296 if request.method == 'POST' and form.validate():
8daef28d 297 if form.confirm.data is True:
05751758 298 username = media.get_uploader.username
bdd22421 299
0b95003c
RE
300 media.get_uploader.uploaded = media.get_uploader.uploaded - \
301 media.file_size
302 media.get_uploader.save()
bdd22421 303
fdc34b8b 304 # Delete MediaEntry and all related files, comments etc.
502073f2 305 media.delete()
ea33f636
E
306 messages.add_message(
307 request, messages.SUCCESS, _('You deleted the media.'))
502073f2 308
e7b8059f
AL
309 location = media.url_to_next(request.urlgen)
310 if not location:
311 location=media.url_to_prev(request.urlgen)
312 if not location:
bd0b5daa
RE
313 location=request.urlgen("mediagoblin.user_pages.user_home",
314 user=username)
315 return redirect(request, location=location)
502073f2 316 else:
d0ba62e2
PUS
317 messages.add_message(
318 request, messages.ERROR,
56bfd91a 319 _("The media was not deleted because you didn't check that you were sure."))
2e6ee596 320 return redirect_obj(request, media)
502073f2 321
bec591d8 322 if ((request.user.is_admin and
5c2b8486 323 request.user.id != media.uploader)):
7a4c0126
CAW
324 messages.add_message(
325 request, messages.WARNING,
326 _("You are about to delete another user's media. "
327 "Proceed with caution."))
328
502073f2
JW
329 return render_to_response(
330 request,
331 'mediagoblin/user_pages/media_confirm_delete.html',
332 {'media': media,
333 'form': form})
334
335
e2ae0f59 336@active_user_from_url
be5be115 337@uses_pagination
e2ae0f59 338def user_collection(request, page, url_user=None):
be5be115 339 """A User-defined Collection"""
e2ae0f59
SS
340 collection = Collection.query.filter_by(
341 get_creator=url_user,
342 slug=request.matchdict['collection']).first()
be5be115 343
61e39d90
JW
344 if not collection:
345 return render_404(request)
346
e2ae0f59 347 cursor = collection.get_collection_items()
be5be115
AW
348
349 pagination = Pagination(page, cursor)
350 collection_items = pagination()
351
e2ae0f59
SS
352 # if no data is available, return NotFound
353 # TODO: Should an empty collection really also return 404?
be5be115
AW
354 if collection_items == None:
355 return render_404(request)
356
357 return render_to_response(
358 request,
359 'mediagoblin/user_pages/collection.html',
e2ae0f59 360 {'user': url_user,
be5be115
AW
361 'collection': collection,
362 'collection_items': collection_items,
363 'pagination': pagination})
364
365
b0cc1ade 366@active_user_from_url
4f8f0353 367def collection_list(request, url_user=None):
b0cc1ade
SZ
368 """A User-defined Collection"""
369 collections = Collection.query.filter_by(
370 get_creator=url_user)
371
b0cc1ade
SZ
372 return render_to_response(
373 request,
4f8f0353 374 'mediagoblin/user_pages/collection_list.html',
b0cc1ade 375 {'user': url_user,
947f38c0 376 'collections': collections})
b0cc1ade
SZ
377
378
be5be115
AW
379@get_user_collection_item
380@require_active_login
381@user_may_alter_collection
382def collection_item_confirm_remove(request, collection_item):
383
111a609d 384 form = user_forms.ConfirmCollectionItemRemoveForm(request.form)
be5be115
AW
385
386 if request.method == 'POST' and form.validate():
387 username = collection_item.in_collection.get_creator.username
388 collection = collection_item.in_collection
389
390 if form.confirm.data is True:
391 entry = collection_item.get_media_entry
392 entry.collected = entry.collected - 1
393 entry.save()
394
395 collection_item.delete()
6d1e55b2 396 collection.items = collection.items - 1
be5be115
AW
397 collection.save()
398
399 messages.add_message(
400 request, messages.SUCCESS, _('You deleted the item from the collection.'))
401 else:
402 messages.add_message(
403 request, messages.ERROR,
404 _("The item was not removed because you didn't check that you were sure."))
405
2e6ee596 406 return redirect_obj(request, collection)
be5be115
AW
407
408 if ((request.user.is_admin and
5c2b8486 409 request.user.id != collection_item.in_collection.creator)):
be5be115
AW
410 messages.add_message(
411 request, messages.WARNING,
412 _("You are about to delete an item from another user's collection. "
413 "Proceed with caution."))
414
415 return render_to_response(
416 request,
417 'mediagoblin/user_pages/collection_item_confirm_remove.html',
418 {'collection_item': collection_item,
419 'form': form})
420
421
422@get_user_collection
423@require_active_login
424@user_may_alter_collection
425def collection_confirm_delete(request, collection):
426
111a609d 427 form = user_forms.ConfirmDeleteForm(request.form)
be5be115
AW
428
429 if request.method == 'POST' and form.validate():
430
431 username = collection.get_creator.username
432
433 if form.confirm.data is True:
434 collection_title = collection.title
435
436 # Delete all the associated collection items
437 for item in collection.get_collection_items():
438 entry = item.get_media_entry
439 entry.collected = entry.collected - 1
440 entry.save()
441 item.delete()
442
443 collection.delete()
2041ceae
E
444 messages.add_message(request, messages.SUCCESS,
445 _('You deleted the collection "%s"') % collection_title)
be5be115
AW
446
447 return redirect(request, "mediagoblin.user_pages.user_home",
448 user=username)
449 else:
450 messages.add_message(
451 request, messages.ERROR,
452 _("The collection was not deleted because you didn't check that you were sure."))
453
2e6ee596 454 return redirect_obj(request, collection)
be5be115
AW
455
456 if ((request.user.is_admin and
5c2b8486 457 request.user.id != collection.creator)):
be5be115
AW
458 messages.add_message(
459 request, messages.WARNING,
460 _("You are about to delete another user's collection. "
461 "Proceed with caution."))
462
463 return render_to_response(
464 request,
465 'mediagoblin/user_pages/collection_confirm_delete.html',
466 {'collection': collection,
467 'form': form})
468
469
a5303e47 470ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15
00c39256 471
243c3843 472
00c39256
BK
473def atom_feed(request):
474 """
475 generates the atom feed with the newest images
476 """
2fb36dac
SS
477 user = User.query.filter_by(
478 username = request.matchdict['user'],
479 status = u'active').first()
00c39256 480 if not user:
de12b4e7 481 return render_404(request)
00c39256 482
2fb36dac
SS
483 cursor = MediaEntry.query.filter_by(
484 uploader = user.id,
485 state = u'processed').\
0efe9e27 486 order_by(MediaEntry.created.desc()).\
2fb36dac 487 limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
00c39256 488
1df68a35
MA
489 """
490 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
491 """
5b1a7bae
MA
492 atomlinks = [{
493 'href': request.urlgen(
494 'mediagoblin.user_pages.user_home',
64712915 495 qualified=True, user=request.matchdict['user']),
5b1a7bae
MA
496 'rel': 'alternate',
497 'type': 'text/html'
64712915
JW
498 }]
499
bb025ebd
MA
500 if mg_globals.app_config["push_urls"]:
501 for push_url in mg_globals.app_config["push_urls"]:
502 atomlinks.append({
503 'rel': 'hub',
504 'href': push_url})
5b1a7bae 505
1df68a35
MA
506 feed = AtomFeed(
507 "MediaGoblin: Feed for user '%s'" % request.matchdict['user'],
00c39256 508 feed_url=request.url,
64712915
JW
509 id='tag:{host},{year}:gallery.user-{user}'.format(
510 host=request.host,
511 year=datetime.datetime.today().strftime('%Y'),
512 user=request.matchdict['user']),
5b1a7bae
MA
513 links=atomlinks)
514
00c39256
BK
515 for entry in cursor:
516 feed.add(entry.get('title'),
1e72e075 517 entry.description_html,
64712915 518 id=entry.url_for_self(request.urlgen, qualified=True),
00c39256 519 content_type='html',
1df68a35
MA
520 author={
521 'name': entry.get_uploader.username,
522 'uri': request.urlgen(
523 'mediagoblin.user_pages.user_home',
524 qualified=True, user=entry.get_uploader.username)},
00c39256 525 updated=entry.get('created'),
1df68a35
MA
526 links=[{
527 'href': entry.url_for_self(
528 request.urlgen,
be5be115
AW
529 qualified=True),
530 'rel': 'alternate',
531 'type': 'text/html'}])
532
533 return feed.get_response()
534
6d1e55b2 535
be5be115
AW
536def collection_atom_feed(request):
537 """
538 generates the atom feed with the newest images from a collection
539 """
2fb36dac
SS
540 user = User.query.filter_by(
541 username = request.matchdict['user'],
542 status = u'active').first()
be5be115
AW
543 if not user:
544 return render_404(request)
545
af008743
SS
546 collection = Collection.query.filter_by(
547 creator=user.id,
548 slug=request.matchdict['collection']).first()
61e39d90
JW
549 if not collection:
550 return render_404(request)
be5be115 551
af008743
SS
552 cursor = CollectionItem.query.filter_by(
553 collection=collection.id) \
20be9bb7 554 .order_by(CollectionItem.added.desc()) \
be5be115
AW
555 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
556
557 """
558 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
559 """
560 atomlinks = [{
fc45b386 561 'href': collection.url_for_self(request.urlgen, qualified=True),
be5be115
AW
562 'rel': 'alternate',
563 'type': 'text/html'
564 }]
565
566 if mg_globals.app_config["push_urls"]:
567 for push_url in mg_globals.app_config["push_urls"]:
568 atomlinks.append({
569 'rel': 'hub',
570 'href': push_url})
571
572 feed = AtomFeed(
19ad2e0c
JW
573 "MediaGoblin: Feed for %s's collection %s" %
574 (request.matchdict['user'], collection.title),
575 feed_url=request.url,
ab9b0b41 576 id=u'tag:{host},{year}:gnu-mediagoblin.{user}.collection.{slug}'\
19ad2e0c
JW
577 .format(
578 host=request.host,
579 year=collection.created.strftime('%Y'),
580 user=request.matchdict['user'],
581 slug=collection.slug),
582 links=atomlinks)
be5be115
AW
583
584 for item in cursor:
585 entry = item.get_media_entry
586 feed.add(entry.get('title'),
587 item.note_html,
588 id=entry.url_for_self(request.urlgen, qualified=True),
589 content_type='html',
590 author={
591 'name': entry.get_uploader.username,
592 'uri': request.urlgen(
593 'mediagoblin.user_pages.user_home',
594 qualified=True, user=entry.get_uploader.username)},
595 updated=item.get('added'),
596 links=[{
597 'href': entry.url_for_self(
598 request.urlgen,
1df68a35
MA
599 qualified=True),
600 'rel': 'alternate',
601 'type': 'text/html'}])
00c39256 602
9074ee7c 603 return feed.get_response()
01c75c7e
CAW
604
605
606@require_active_login
607def processing_panel(request):
608 """
609 Show to the user what media is still in conversion/processing...
610 and what failed, and why!
611 """
af008743
SS
612 user = User.query.filter_by(username=request.matchdict['user']).first()
613 # TODO: XXX: Should this be a decorator?
01c75c7e
CAW
614 #
615 # Make sure we have permission to access this user's panel. Only
616 # admins and this user herself should be able to do so.
dfc23dd1 617 if not (user.id == request.user.id or request.user.is_admin):
af008743 618 # No? Simply redirect to this user's homepage.
01c75c7e
CAW
619 return redirect(
620 request, 'mediagoblin.user_pages.user_home',
af008743 621 user=user.username)
01c75c7e
CAW
622
623 # Get media entries which are in-processing
af008743 624 processing_entries = MediaEntry.query.\
2fb36dac
SS
625 filter_by(uploader = user.id,
626 state = u'processing').\
af008743 627 order_by(MediaEntry.created.desc())
01c75c7e
CAW
628
629 # Get media entries which have failed to process
af008743 630 failed_entries = MediaEntry.query.\
2fb36dac
SS
631 filter_by(uploader = user.id,
632 state = u'failed').\
af008743
SS
633 order_by(MediaEntry.created.desc())
634
635 processed_entries = MediaEntry.query.\
2fb36dac
SS
636 filter_by(uploader = user.id,
637 state = u'processed').\
af008743
SS
638 order_by(MediaEntry.created.desc()).\
639 limit(10)
64712915 640
01c75c7e
CAW
641 # Render to response
642 return render_to_response(
643 request,
644 'mediagoblin/user_pages/processing_panel.html',
645 {'user': user,
646 'processing_entries': processing_entries,
64712915
JW
647 'failed_entries': failed_entries,
648 'processed_entries': processed_entries})