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
.util
import DESCENDING
, ObjectId
22 from mediagoblin
.db
.sql
.models
import MediaEntry
, Collection
, CollectionItem
23 from mediagoblin
.tools
.response
import render_to_response
, render_404
, redirect
24 from mediagoblin
.tools
.translate
import pass_to_ugettext
as _
25 from mediagoblin
.tools
.pagination
import Pagination
26 from mediagoblin
.tools
.files
import delete_media_files
27 from mediagoblin
.user_pages
import forms
as user_forms
28 from mediagoblin
.user_pages
.lib
import send_comment_email
30 from mediagoblin
.decorators
import (uses_pagination
, get_user_media_entry
,
31 require_active_login
, user_may_delete_media
, user_may_alter_collection
,
32 get_user_collection
, get_user_collection_item
, active_user_from_url
)
34 from werkzeug
.contrib
.atom
import AtomFeed
37 _log
= logging
.getLogger(__name__
)
38 _log
.setLevel(logging
.DEBUG
)
42 def user_home(request
, page
):
43 """'Homepage' of a User()"""
44 user
= request
.db
.User
.find_one({
45 'username': request
.matchdict
['user']})
47 return render_404(request
)
48 elif user
.status
!= u
'active':
49 return render_to_response(
51 'mediagoblin/user_pages/user.html',
54 cursor
= request
.db
.MediaEntry
.find(
56 'state': u
'processed'}).sort('created', DESCENDING
)
58 pagination
= Pagination(page
, cursor
)
59 media_entries
= pagination()
61 #if no data is available, return NotFound
62 if media_entries
== None:
63 return render_404(request
)
65 user_gallery_url
= request
.urlgen(
66 'mediagoblin.user_pages.user_gallery',
69 return render_to_response(
71 'mediagoblin/user_pages/user.html',
73 'user_gallery_url': user_gallery_url
,
74 'media_entries': media_entries
,
75 'pagination': pagination
})
80 def user_gallery(request
, page
, url_user
=None):
81 """'Gallery' of a User()"""
82 cursor
= MediaEntry
.query
.filter_by(
84 state
=u
'processed').order_by(MediaEntry
.created
.desc())
86 pagination
= Pagination(page
, cursor
)
87 media_entries
= pagination()
89 #if no data is available, return NotFound
90 # TODO: Should we really also return 404 for empty galleries?
91 if media_entries
== None:
92 return render_404(request
)
94 return render_to_response(
96 'mediagoblin/user_pages/gallery.html',
98 'media_entries': media_entries
,
99 'pagination': pagination
})
101 MEDIA_COMMENTS_PER_PAGE
= 50
104 @get_user_media_entry
106 def media_home(request
, media
, page
, **kwargs
):
108 'Homepage' of a MediaEntry()
110 if ObjectId(request
.matchdict
.get('comment')):
111 pagination
= Pagination(
112 page
, media
.get_comments(
113 mg_globals
.app_config
['comments_ascending']),
114 MEDIA_COMMENTS_PER_PAGE
,
115 ObjectId(request
.matchdict
.get('comment')))
117 pagination
= Pagination(
118 page
, media
.get_comments(
119 mg_globals
.app_config
['comments_ascending']),
120 MEDIA_COMMENTS_PER_PAGE
)
122 comments
= pagination()
124 comment_form
= user_forms
.MediaCommentForm(request
.form
)
126 media_template_name
= media
.media_manager
['display_template']
128 return render_to_response(
132 'comments': comments
,
133 'pagination': pagination
,
134 'comment_form': comment_form
,
135 'app_config': mg_globals
.app_config
})
138 @get_user_media_entry
139 @require_active_login
140 def media_post_comment(request
, media
):
142 recieves POST from a MediaEntry() comment form, saves the comment.
144 assert request
.method
== 'POST'
146 comment
= request
.db
.MediaComment()
147 comment
.media_entry
= media
.id
148 comment
.author
= request
.user
.id
149 comment
.content
= unicode(request
.form
['comment_content'])
151 if not comment
.content
.strip():
152 messages
.add_message(
155 _("Oops, your comment was empty."))
159 messages
.add_message(
160 request
, messages
.SUCCESS
,
161 _('Your comment has been posted!'))
163 media_uploader
= media
.get_uploader
164 #don't send email if you comment on your own post
165 if (comment
.author
!= media_uploader
and
166 media_uploader
.wants_comment_notification
):
167 send_comment_email(media_uploader
, comment
, media
, request
)
169 return redirect(request
, location
=media
.url_for_self(request
.urlgen
))
172 @get_user_media_entry
173 @require_active_login
174 def media_collect(request
, media
):
175 """Add media to collection submission"""
177 form
= user_forms
.MediaCollectForm(request
.form
)
178 # A user's own collections:
179 form
.collection
.query
= Collection
.query
.filter(
180 request
.db
.Collection
.creator
== request
.user
.id)\
181 .order_by(Collection
.title
)
183 if request
.method
!= 'POST' or not form
.validate():
184 # No POST submission, or invalid form
185 if not form
.validate():
186 messages
.add_message(request
, messages
.ERROR
,
187 _('Please check your entries and try again.'))
189 return render_to_response(
191 'mediagoblin/user_pages/media_collect.html',
195 # If we are here, method=POST and the form is valid, submit things.
196 # If the user is adding a new collection, use that:
197 if request
.form
['collection_title']:
198 # Make sure this user isn't duplicating an existing collection
199 existing_collection
= Collection
.query
.filter_by(
200 creator
=request
.user
.id,
201 title
=request
.form
['collection_title']).first()
202 if existing_collection
:
203 messages
.add_message(request
, messages
.ERROR
,
204 _('You already have a collection called "%s"!'
206 return redirect(request
, "mediagoblin.user_pages.media_home",
207 user
=request
.user
.username
,
210 collection
= Collection()
211 collection
.title
= request
.form
['collection_title']
212 collection
.description
= request
.form
.get('collection_description')
213 collection
.creator
= request
.user
.id
214 collection
.generate_slug()
215 collection
.save(validate
=True)
217 # Otherwise, use the collection selected from the drop-down
219 collection
= Collection
.query
.filter_by(
220 id=request
.form
.get('collection')).first()
222 # Make sure the user actually selected a collection
224 messages
.add_message(
225 request
, messages
.ERROR
,
226 _('You have to select or add a collection'))
228 # Check whether media already exists in collection
229 elif CollectionItem
.query
.filter_by(
230 media_entry
=media
.id,
231 collection
=collection
.id).first():
232 messages
.add_message(request
, messages
.ERROR
,
233 _('"%s" already in collection "%s"'
234 % (media
.title
, collection
.title
)))
235 else: # Add item to collection
236 collection_item
= request
.db
.CollectionItem()
237 collection_item
.collection
= collection
.id
238 collection_item
.media_entry
= media
.id
239 collection_item
.author
= request
.user
.id
240 collection_item
.note
= request
.form
['note']
241 collection_item
.save(validate
=True)
243 collection
.items
= collection
.items
+ 1
244 collection
.save(validate
=True)
246 media
.collected
= media
.collected
+ 1
249 messages
.add_message(request
, messages
.SUCCESS
,
250 _('"%s" added to collection "%s"'
251 % (media
.title
, collection
.title
)))
253 return redirect(request
, "mediagoblin.user_pages.media_home",
254 user
=media
.get_uploader
.username
,
258 #TODO: Why does @user_may_delete_media not implicate @require_active_login?
259 @get_user_media_entry
260 @require_active_login
261 @user_may_delete_media
262 def media_confirm_delete(request
, media
):
264 form
= user_forms
.ConfirmDeleteForm(request
.form
)
266 if request
.method
== 'POST' and form
.validate():
267 if form
.confirm
.data
is True:
268 username
= media
.get_uploader
.username
270 # Delete all the associated comments
271 for comment
in media
.get_comments():
274 # Delete all files on the public storage
276 delete_media_files(media
)
277 except OSError, error
:
278 _log
.error('No such files from the user "{1}"'
279 ' to delete: {0}'.format(str(error
), username
))
280 messages
.add_message(request
, messages
.ERROR
,
281 _('Some of the files with this entry seem'
282 ' to be missing. Deleting anyway.'))
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(request
,
295 location
=media
.url_for_self(request
.urlgen
))
297 if ((request
.user
.is_admin
and
298 request
.user
.id != media
.uploader
)):
299 messages
.add_message(
300 request
, messages
.WARNING
,
301 _("You are about to delete another user's media. "
302 "Proceed with caution."))
304 return render_to_response(
306 'mediagoblin/user_pages/media_confirm_delete.html',
311 @active_user_from_url
313 def user_collection(request
, page
, url_user
=None):
314 """A User-defined Collection"""
315 collection
= Collection
.query
.filter_by(
316 get_creator
=url_user
,
317 slug
=request
.matchdict
['collection']).first()
319 cursor
= collection
.get_collection_items()
321 pagination
= Pagination(page
, cursor
)
322 collection_items
= pagination()
324 # if no data is available, return NotFound
325 # TODO: Should an empty collection really also return 404?
326 if collection_items
== None:
327 return render_404(request
)
329 return render_to_response(
331 'mediagoblin/user_pages/collection.html',
333 'collection': collection
,
334 'collection_items': collection_items
,
335 'pagination': pagination
})
338 @get_user_collection_item
339 @require_active_login
340 @user_may_alter_collection
341 def collection_item_confirm_remove(request
, collection_item
):
343 form
= user_forms
.ConfirmCollectionItemRemoveForm(request
.form
)
345 if request
.method
== 'POST' and form
.validate():
346 username
= collection_item
.in_collection
.get_creator
.username
347 collection
= collection_item
.in_collection
349 if form
.confirm
.data
is True:
350 entry
= collection_item
.get_media_entry
351 entry
.collected
= entry
.collected
- 1
354 collection_item
.delete()
355 collection
.items
= collection
.items
- 1
358 messages
.add_message(
359 request
, messages
.SUCCESS
, _('You deleted the item from the collection.'))
361 messages
.add_message(
362 request
, messages
.ERROR
,
363 _("The item was not removed because you didn't check that you were sure."))
365 return redirect(request
, "mediagoblin.user_pages.user_collection",
367 collection
=collection
.slug
)
369 if ((request
.user
.is_admin
and
370 request
.user
.id != collection_item
.in_collection
.creator
)):
371 messages
.add_message(
372 request
, messages
.WARNING
,
373 _("You are about to delete an item from another user's collection. "
374 "Proceed with caution."))
376 return render_to_response(
378 'mediagoblin/user_pages/collection_item_confirm_remove.html',
379 {'collection_item': collection_item
,
384 @require_active_login
385 @user_may_alter_collection
386 def collection_confirm_delete(request
, collection
):
388 form
= user_forms
.ConfirmDeleteForm(request
.form
)
390 if request
.method
== 'POST' and form
.validate():
392 username
= collection
.get_creator
.username
394 if form
.confirm
.data
is True:
395 collection_title
= collection
.title
397 # Delete all the associated collection items
398 for item
in collection
.get_collection_items():
399 entry
= item
.get_media_entry
400 entry
.collected
= entry
.collected
- 1
405 messages
.add_message(
406 request
, messages
.SUCCESS
, _('You deleted the collection "%s"' % collection_title
))
408 return redirect(request
, "mediagoblin.user_pages.user_home",
411 messages
.add_message(
412 request
, messages
.ERROR
,
413 _("The collection was not deleted because you didn't check that you were sure."))
415 return redirect(request
, "mediagoblin.user_pages.user_collection",
417 collection
=collection
.slug
)
419 if ((request
.user
.is_admin
and
420 request
.user
.id != collection
.creator
)):
421 messages
.add_message(
422 request
, messages
.WARNING
,
423 _("You are about to delete another user's collection. "
424 "Proceed with caution."))
426 return render_to_response(
428 'mediagoblin/user_pages/collection_confirm_delete.html',
429 {'collection': collection
,
433 ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
= 15
436 def atom_feed(request
):
438 generates the atom feed with the newest images
441 user
= request
.db
.User
.find_one({
442 'username': request
.matchdict
['user'],
443 'status': u
'active'})
445 return render_404(request
)
447 cursor
= request
.db
.MediaEntry
.find({
449 'state': u
'processed'}) \
450 .sort('created', DESCENDING
) \
451 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
)
454 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
457 'href': request
.urlgen(
458 'mediagoblin.user_pages.user_home',
459 qualified
=True, user
=request
.matchdict
['user']),
464 if mg_globals
.app_config
["push_urls"]:
465 for push_url
in mg_globals
.app_config
["push_urls"]:
471 "MediaGoblin: Feed for user '%s'" % request
.matchdict
['user'],
472 feed_url
=request
.url
,
473 id='tag:{host},{year}:gallery.user-{user}'.format(
475 year
=datetime
.datetime
.today().strftime('%Y'),
476 user
=request
.matchdict
['user']),
480 feed
.add(entry
.get('title'),
481 entry
.description_html
,
482 id=entry
.url_for_self(request
.urlgen
, qualified
=True),
485 'name': entry
.get_uploader
.username
,
486 'uri': request
.urlgen(
487 'mediagoblin.user_pages.user_home',
488 qualified
=True, user
=entry
.get_uploader
.username
)},
489 updated
=entry
.get('created'),
491 'href': entry
.url_for_self(
495 'type': 'text/html'}])
497 return feed
.get_response()
500 def collection_atom_feed(request
):
502 generates the atom feed with the newest images from a collection
505 user
= request
.db
.User
.find_one({
506 'username': request
.matchdict
['user'],
507 'status': u
'active'})
509 return render_404(request
)
511 collection
= request
.db
.Collection
.find_one({
513 'slug': request
.matchdict
['collection']})
515 cursor
= request
.db
.CollectionItem
.find({
516 'collection': collection
.id}) \
517 .sort('added', DESCENDING
) \
518 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
)
521 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
524 'href': request
.urlgen(
525 'mediagoblin.user_pages.user_collection',
526 qualified
=True, user
=request
.matchdict
['user'], collection
=collection
.slug
),
531 if mg_globals
.app_config
["push_urls"]:
532 for push_url
in mg_globals
.app_config
["push_urls"]:
538 "MediaGoblin: Feed for %s's collection %s" % (request
.matchdict
['user'], collection
.title
),
539 feed_url
=request
.url
,
540 id='tag:{host},{year}:collection.user-{user}.title-{title}'.format(
542 year
=datetime
.datetime
.today().strftime('%Y'),
543 user
=request
.matchdict
['user'],
544 title
=collection
.title
),
548 entry
= item
.get_media_entry
549 feed
.add(entry
.get('title'),
551 id=entry
.url_for_self(request
.urlgen
, qualified
=True),
554 'name': entry
.get_uploader
.username
,
555 'uri': request
.urlgen(
556 'mediagoblin.user_pages.user_home',
557 qualified
=True, user
=entry
.get_uploader
.username
)},
558 updated
=item
.get('added'),
560 'href': entry
.url_for_self(
564 'type': 'text/html'}])
566 return feed
.get_response()
569 @require_active_login
570 def processing_panel(request
):
572 Show to the user what media is still in conversion/processing...
573 and what failed, and why!
576 user
= request
.db
.User
.find_one(
577 {'username': request
.matchdict
['user'],
578 'status': u
'active'})
580 # Make sure the user exists and is active
582 return render_404(request
)
583 elif user
.status
!= u
'active':
584 return render_to_response(
586 'mediagoblin/user_pages/user.html',
589 # XXX: Should this be a decorator?
591 # Make sure we have permission to access this user's panel. Only
592 # admins and this user herself should be able to do so.
593 if not (user
.id == request
.user
.id
594 or request
.user
.is_admin
):
595 # No? Let's simply redirect to this user's homepage then.
597 request
, 'mediagoblin.user_pages.user_home',
598 user
=request
.matchdict
['user'])
600 # Get media entries which are in-processing
601 processing_entries
= request
.db
.MediaEntry
.find(
602 {'uploader': user
.id,
603 'state': u
'processing'}).sort('created', DESCENDING
)
605 # Get media entries which have failed to process
606 failed_entries
= request
.db
.MediaEntry
.find(
607 {'uploader': user
.id,
608 'state': u
'failed'}).sort('created', DESCENDING
)
610 processed_entries
= request
.db
.MediaEntry
.find(
611 {'uploader': user
.id,
612 'state': u
'processed'}).sort('created', DESCENDING
).limit(10)
615 return render_to_response(
617 'mediagoblin/user_pages/processing_panel.html',
619 'processing_entries': processing_entries
,
620 'failed_entries': failed_entries
,
621 'processed_entries': processed_entries
})