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
):
176 form
= user_forms
.MediaCollectForm(request
.form
)
177 filt
= (request
.db
.Collection
.creator
== request
.user
.id)
178 form
.collection
.query
= request
.db
.Collection
.query
.filter(
179 filt
).order_by(request
.db
.Collection
.title
)
181 if request
.method
== 'POST':
185 collection_item
= request
.db
.CollectionItem()
187 # If the user is adding a new collection, use that
188 if request
.form
['collection_title']:
189 collection
= request
.db
.Collection()
190 collection
.id = ObjectId()
193 unicode(request
.form
['collection_title']))
195 collection
.description
= unicode(
196 request
.form
.get('collection_description'))
197 collection
.creator
= request
.user
.id
198 collection
.generate_slug()
200 # Make sure this user isn't duplicating an existing collection
201 existing_collection
= request
.db
.Collection
.find_one({
202 'creator': request
.user
.id,
203 'title': collection
.title
})
205 if existing_collection
:
206 messages
.add_message(
207 request
, messages
.ERROR
,
208 _('You already have a collection called "%s"!'
211 return redirect(request
, "mediagoblin.user_pages.media_home",
212 user
=request
.user
.username
,
215 collection
.save(validate
=True)
217 collection_item
.collection
= collection
.id
218 # Otherwise, use the collection selected from the drop-down
220 collection
= request
.db
.Collection
.find_one({
221 'id': request
.form
.get('collection')})
222 collection_item
.collection
= collection
.id
224 # Make sure the user actually selected a collection
226 messages
.add_message(
227 request
, messages
.ERROR
,
228 _('You have to select or add a collection'))
229 # Check whether media already exists in collection
230 elif request
.db
.CollectionItem
.find_one({
231 'media_entry': media
.id,
232 'collection': collection_item
.collection
}):
233 messages
.add_message(
234 request
, messages
.ERROR
,
235 _('"%s" already in collection "%s"'
236 % (media
.title
, collection
.title
)))
238 collection_item
.media_entry
= media
.id
239 collection_item
.author
= request
.user
.id
240 collection_item
.note
= unicode(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(
250 request
, messages
.SUCCESS
, _('"%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
,
257 messages
.add_message(
258 request
, messages
.ERROR
,
259 _('Please check your entries and try again.'))
261 return render_to_response(
263 'mediagoblin/user_pages/media_collect.html',
268 @get_user_media_entry
269 @require_active_login
270 @user_may_delete_media
271 def media_confirm_delete(request
, media
):
273 form
= user_forms
.ConfirmDeleteForm(request
.form
)
275 if request
.method
== 'POST' and form
.validate():
276 if form
.confirm
.data
is True:
277 username
= media
.get_uploader
.username
279 # Delete all the associated comments
280 for comment
in media
.get_comments():
283 # Delete all files on the public storage
285 delete_media_files(media
)
286 except OSError, error
:
287 _log
.error('No such files from the user "{1}"'
288 ' to delete: {0}'.format(str(error
), username
))
289 messages
.add_message(request
, messages
.ERROR
,
290 _('Some of the files with this entry seem'
291 ' to be missing. Deleting anyway.'))
294 messages
.add_message(
295 request
, messages
.SUCCESS
, _('You deleted the media.'))
297 return redirect(request
, "mediagoblin.user_pages.user_home",
300 messages
.add_message(
301 request
, messages
.ERROR
,
302 _("The media was not deleted because you didn't check that you were sure."))
303 return redirect(request
,
304 location
=media
.url_for_self(request
.urlgen
))
306 if ((request
.user
.is_admin
and
307 request
.user
.id != media
.uploader
)):
308 messages
.add_message(
309 request
, messages
.WARNING
,
310 _("You are about to delete another user's media. "
311 "Proceed with caution."))
313 return render_to_response(
315 'mediagoblin/user_pages/media_confirm_delete.html',
320 @active_user_from_url
322 def user_collection(request
, page
, url_user
=None):
323 """A User-defined Collection"""
324 collection
= Collection
.query
.filter_by(
325 get_creator
=url_user
,
326 slug
=request
.matchdict
['collection']).first()
328 cursor
= collection
.get_collection_items()
330 pagination
= Pagination(page
, cursor
)
331 collection_items
= pagination()
333 # if no data is available, return NotFound
334 # TODO: Should an empty collection really also return 404?
335 if collection_items
== None:
336 return render_404(request
)
338 return render_to_response(
340 'mediagoblin/user_pages/collection.html',
342 'collection': collection
,
343 'collection_items': collection_items
,
344 'pagination': pagination
})
347 @get_user_collection_item
348 @require_active_login
349 @user_may_alter_collection
350 def collection_item_confirm_remove(request
, collection_item
):
352 form
= user_forms
.ConfirmCollectionItemRemoveForm(request
.form
)
354 if request
.method
== 'POST' and form
.validate():
355 username
= collection_item
.in_collection
.get_creator
.username
356 collection
= collection_item
.in_collection
358 if form
.confirm
.data
is True:
359 entry
= collection_item
.get_media_entry
360 entry
.collected
= entry
.collected
- 1
363 collection_item
.delete()
364 collection
.items
= collection
.items
- 1
367 messages
.add_message(
368 request
, messages
.SUCCESS
, _('You deleted the item from the collection.'))
370 messages
.add_message(
371 request
, messages
.ERROR
,
372 _("The item was not removed because you didn't check that you were sure."))
374 return redirect(request
, "mediagoblin.user_pages.user_collection",
376 collection
=collection
.slug
)
378 if ((request
.user
.is_admin
and
379 request
.user
.id != collection_item
.in_collection
.creator
)):
380 messages
.add_message(
381 request
, messages
.WARNING
,
382 _("You are about to delete an item from another user's collection. "
383 "Proceed with caution."))
385 return render_to_response(
387 'mediagoblin/user_pages/collection_item_confirm_remove.html',
388 {'collection_item': collection_item
,
393 @require_active_login
394 @user_may_alter_collection
395 def collection_confirm_delete(request
, collection
):
397 form
= user_forms
.ConfirmDeleteForm(request
.form
)
399 if request
.method
== 'POST' and form
.validate():
401 username
= collection
.get_creator
.username
403 if form
.confirm
.data
is True:
404 collection_title
= collection
.title
406 # Delete all the associated collection items
407 for item
in collection
.get_collection_items():
408 entry
= item
.get_media_entry
409 entry
.collected
= entry
.collected
- 1
414 messages
.add_message(
415 request
, messages
.SUCCESS
, _('You deleted the collection "%s"' % collection_title
))
417 return redirect(request
, "mediagoblin.user_pages.user_home",
420 messages
.add_message(
421 request
, messages
.ERROR
,
422 _("The collection was not deleted because you didn't check that you were sure."))
424 return redirect(request
, "mediagoblin.user_pages.user_collection",
426 collection
=collection
.slug
)
428 if ((request
.user
.is_admin
and
429 request
.user
.id != collection
.creator
)):
430 messages
.add_message(
431 request
, messages
.WARNING
,
432 _("You are about to delete another user's collection. "
433 "Proceed with caution."))
435 return render_to_response(
437 'mediagoblin/user_pages/collection_confirm_delete.html',
438 {'collection': collection
,
442 ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
= 15
445 def atom_feed(request
):
447 generates the atom feed with the newest images
450 user
= request
.db
.User
.find_one({
451 'username': request
.matchdict
['user'],
452 'status': u
'active'})
454 return render_404(request
)
456 cursor
= request
.db
.MediaEntry
.find({
458 'state': u
'processed'}) \
459 .sort('created', DESCENDING
) \
460 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
)
463 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
466 'href': request
.urlgen(
467 'mediagoblin.user_pages.user_home',
468 qualified
=True, user
=request
.matchdict
['user']),
473 if mg_globals
.app_config
["push_urls"]:
474 for push_url
in mg_globals
.app_config
["push_urls"]:
480 "MediaGoblin: Feed for user '%s'" % request
.matchdict
['user'],
481 feed_url
=request
.url
,
482 id='tag:{host},{year}:gallery.user-{user}'.format(
484 year
=datetime
.datetime
.today().strftime('%Y'),
485 user
=request
.matchdict
['user']),
489 feed
.add(entry
.get('title'),
490 entry
.description_html
,
491 id=entry
.url_for_self(request
.urlgen
, qualified
=True),
494 'name': entry
.get_uploader
.username
,
495 'uri': request
.urlgen(
496 'mediagoblin.user_pages.user_home',
497 qualified
=True, user
=entry
.get_uploader
.username
)},
498 updated
=entry
.get('created'),
500 'href': entry
.url_for_self(
504 'type': 'text/html'}])
506 return feed
.get_response()
509 def collection_atom_feed(request
):
511 generates the atom feed with the newest images from a collection
514 user
= request
.db
.User
.find_one({
515 'username': request
.matchdict
['user'],
516 'status': u
'active'})
518 return render_404(request
)
520 collection
= request
.db
.Collection
.find_one({
522 'slug': request
.matchdict
['collection']})
524 cursor
= request
.db
.CollectionItem
.find({
525 'collection': collection
.id}) \
526 .sort('added', DESCENDING
) \
527 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
)
530 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
533 'href': request
.urlgen(
534 'mediagoblin.user_pages.user_collection',
535 qualified
=True, user
=request
.matchdict
['user'], collection
=collection
.slug
),
540 if mg_globals
.app_config
["push_urls"]:
541 for push_url
in mg_globals
.app_config
["push_urls"]:
547 "MediaGoblin: Feed for %s's collection %s" % (request
.matchdict
['user'], collection
.title
),
548 feed_url
=request
.url
,
549 id='tag:{host},{year}:collection.user-{user}.title-{title}'.format(
551 year
=datetime
.datetime
.today().strftime('%Y'),
552 user
=request
.matchdict
['user'],
553 title
=collection
.title
),
557 entry
= item
.get_media_entry
558 feed
.add(entry
.get('title'),
560 id=entry
.url_for_self(request
.urlgen
, qualified
=True),
563 'name': entry
.get_uploader
.username
,
564 'uri': request
.urlgen(
565 'mediagoblin.user_pages.user_home',
566 qualified
=True, user
=entry
.get_uploader
.username
)},
567 updated
=item
.get('added'),
569 'href': entry
.url_for_self(
573 'type': 'text/html'}])
575 return feed
.get_response()
578 @require_active_login
579 def processing_panel(request
):
581 Show to the user what media is still in conversion/processing...
582 and what failed, and why!
585 user
= request
.db
.User
.find_one(
586 {'username': request
.matchdict
['user'],
587 'status': u
'active'})
589 # Make sure the user exists and is active
591 return render_404(request
)
592 elif user
.status
!= u
'active':
593 return render_to_response(
595 'mediagoblin/user_pages/user.html',
598 # XXX: Should this be a decorator?
600 # Make sure we have permission to access this user's panel. Only
601 # admins and this user herself should be able to do so.
602 if not (user
.id == request
.user
.id
603 or request
.user
.is_admin
):
604 # No? Let's simply redirect to this user's homepage then.
606 request
, 'mediagoblin.user_pages.user_home',
607 user
=request
.matchdict
['user'])
609 # Get media entries which are in-processing
610 processing_entries
= request
.db
.MediaEntry
.find(
611 {'uploader': user
.id,
612 'state': u
'processing'}).sort('created', DESCENDING
)
614 # Get media entries which have failed to process
615 failed_entries
= request
.db
.MediaEntry
.find(
616 {'uploader': user
.id,
617 'state': u
'failed'}).sort('created', DESCENDING
)
619 processed_entries
= request
.db
.MediaEntry
.find(
620 {'uploader': user
.id,
621 'state': u
'processed'}).sort('created', DESCENDING
).limit(10)
624 return render_to_response(
626 'mediagoblin/user_pages/processing_panel.html',
628 'processing_entries': processing_entries
,
629 'failed_entries': failed_entries
,
630 'processed_entries': processed_entries
})