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/>.
21 from mediagoblin
import messages
, mg_globals
22 from mediagoblin
.db
.util
import DESCENDING
, ObjectId
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
)
34 from werkzeug
.contrib
.atom
import AtomFeed
36 from mediagoblin
.media_types
import get_media_manager
37 from sqlalchemy
.exc
import IntegrityError
39 _log
= logging
.getLogger(__name__
)
40 _log
.setLevel(logging
.DEBUG
)
44 def user_home(request
, page
):
45 """'Homepage' of a User()"""
46 user
= request
.db
.User
.find_one({
47 'username': request
.matchdict
['user']})
49 return render_404(request
)
50 elif user
.status
!= u
'active':
51 return render_to_response(
53 'mediagoblin/user_pages/user.html',
56 cursor
= request
.db
.MediaEntry
.find(
57 {'uploader': user
._id
,
58 'state': u
'processed'}).sort('created', DESCENDING
)
60 pagination
= Pagination(page
, cursor
)
61 media_entries
= pagination()
63 #if no data is available, return NotFound
64 if media_entries
== None:
65 return render_404(request
)
67 user_gallery_url
= request
.urlgen(
68 'mediagoblin.user_pages.user_gallery',
71 return render_to_response(
73 'mediagoblin/user_pages/user.html',
75 'user_gallery_url': user_gallery_url
,
76 'media_entries': media_entries
,
77 'pagination': pagination
})
81 def user_gallery(request
, page
):
82 """'Gallery' of a User()"""
83 user
= request
.db
.User
.find_one({
84 'username': request
.matchdict
['user'],
87 return render_404(request
)
89 cursor
= request
.db
.MediaEntry
.find(
90 {'uploader': user
._id
,
91 'state': u
'processed'}).sort('created', DESCENDING
)
93 pagination
= Pagination(page
, cursor
)
94 media_entries
= pagination()
96 #if no data is available, return NotFound
97 if media_entries
== None:
98 return render_404(request
)
100 return render_to_response(
102 'mediagoblin/user_pages/gallery.html',
104 'media_entries': media_entries
,
105 'pagination': pagination
})
107 MEDIA_COMMENTS_PER_PAGE
= 50
110 @get_user_media_entry
112 def media_home(request
, media
, page
, **kwargs
):
114 'Homepage' of a MediaEntry()
116 if 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
,
121 ObjectId(request
.matchdict
.get('comment')))
123 pagination
= Pagination(
124 page
, media
.get_comments(
125 mg_globals
.app_config
['comments_ascending']),
126 MEDIA_COMMENTS_PER_PAGE
)
128 comments
= pagination()
130 comment_form
= user_forms
.MediaCommentForm(request
.POST
)
132 media_template_name
= get_media_manager(media
.media_type
)['display_template']
134 return render_to_response(
138 'comments': comments
,
139 'pagination': pagination
,
140 'comment_form': comment_form
,
141 'app_config': mg_globals
.app_config
})
144 @get_user_media_entry
145 @require_active_login
146 def media_post_comment(request
, media
):
148 recieves POST from a MediaEntry() comment form, saves the comment.
150 assert request
.method
== 'POST'
152 comment
= request
.db
.MediaComment()
153 comment
.media_entry
= media
.id
154 comment
.author
= request
.user
.id
155 comment
.content
= unicode(request
.POST
['comment_content'])
157 if not comment
.content
.strip():
158 messages
.add_message(
161 _("Oops, your comment was empty."))
165 messages
.add_message(
166 request
, messages
.SUCCESS
,
167 _('Your comment has been posted!'))
169 media_uploader
= media
.get_uploader
170 #don't send email if you comment on your own post
171 if (comment
.author
!= media_uploader
and
172 media_uploader
.wants_comment_notification
):
173 send_comment_email(media_uploader
, comment
, media
, request
)
175 return exc
.HTTPFound(
176 location
=media
.url_for_self(request
.urlgen
))
179 @get_user_media_entry
180 @require_active_login
181 def media_collect(request
, media
):
183 form
= user_forms
.MediaCollectForm(request
.POST
)
184 filt
= (request
.db
.Collection
.creator
== request
.user
.id)
185 form
.collection
.query
= request
.db
.Collection
.query
.filter(filt
).order_by(request
.db
.Collection
.title
)
187 if request
.method
== 'POST':
191 collection_item
= request
.db
.CollectionItem()
193 # If the user is adding a new collection, use that
194 if request
.POST
['collection_title']:
195 collection
= request
.db
.Collection()
196 collection
.id = ObjectId()
199 unicode(request
.POST
['collection_title'])
200 or unicode(splitext(filename
)[0]))
202 collection
.description
= unicode(request
.POST
.get('collection_description'))
203 collection
.creator
= request
.user
._id
204 collection
.generate_slug()
206 # Make sure this user isn't duplicating an existing collection
207 existing_collection
= request
.db
.Collection
.find_one({
208 'creator': request
.user
._id
,
209 'title':collection
.title
})
211 if existing_collection
:
212 messages
.add_message(
213 request
, messages
.ERROR
, _('You already have a collection called "%s"!' % collection
.title
))
215 return redirect(request
, "mediagoblin.user_pages.media_home",
216 user
=request
.user
.username
,
219 collection
.save(validate
=True)
221 collection_item
.collection
= collection
.id
222 # Otherwise, use the collection selected from the drop-down
224 collection
= request
.db
.Collection
.find_one({'_id': request
.POST
.get('collection')})
225 collection_item
.collection
= collection
.id
227 # Make sure the user actually selected a collection
229 messages
.add_message(
230 request
, messages
.ERROR
, _('You have to select or add a collection'))
231 # Check whether media already exists in collection
232 elif request
.db
.CollectionItem
.find_one({'media_entry': media
.id, 'collection': collection_item
.collection
}):
233 messages
.add_message(
234 request
, messages
.ERROR
, _('"%s" already in collection "%s"' % (media
.title
, collection
.title
)))
236 collection_item
.media_entry
= media
.id
237 collection_item
.author
= request
.user
.id
238 collection_item
.note
= unicode(request
.POST
['note'])
239 collection_item
.save(validate
=True)
241 collection
.items
= collection
.items
+ 1
242 collection
.save(validate
=True)
244 media
.collected
= media
.collected
+ 1
247 messages
.add_message(
248 request
, messages
.SUCCESS
, _('"%s" added to collection "%s"' % (media
.title
, collection
.title
)))
250 return redirect(request
, "mediagoblin.user_pages.media_home",
251 user
=request
.user
.username
,
254 messages
.add_message(
255 request
, messages
.ERROR
, _('Please check your entries and try again.'))
257 return render_to_response(
259 'mediagoblin/user_pages/media_collect.html',
264 @get_user_media_entry
265 @require_active_login
266 @user_may_delete_media
267 def media_confirm_delete(request
, media
):
269 form
= user_forms
.ConfirmDeleteForm(request
.POST
)
271 if request
.method
== 'POST' and form
.validate():
272 if form
.confirm
.data
is True:
273 username
= media
.get_uploader
.username
275 # Delete all the associated comments
276 for comment
in media
.get_comments():
279 # Delete all files on the public storage
281 delete_media_files(media
)
282 except OSError, error
:
283 _log
.error('No such files from the user "{1}"'
284 ' to delete: {0}'.format(str(error
), username
))
285 messages
.add_message(request
, messages
.ERROR
,
286 _('Some of the files with this entry seem'
287 ' to be missing. Deleting anyway.'))
290 messages
.add_message(
291 request
, messages
.SUCCESS
, _('You deleted the media.'))
293 return redirect(request
, "mediagoblin.user_pages.user_home",
296 messages
.add_message(
297 request
, messages
.ERROR
,
298 _("The media was not deleted because you didn't check that you were sure."))
299 return exc
.HTTPFound(
300 location
=media
.url_for_self(request
.urlgen
))
302 if ((request
.user
.is_admin
and
303 request
.user
._id
!= media
.uploader
)):
304 messages
.add_message(
305 request
, messages
.WARNING
,
306 _("You are about to delete another user's media. "
307 "Proceed with caution."))
309 return render_to_response(
311 'mediagoblin/user_pages/media_confirm_delete.html',
317 def user_collection(request
, page
):
318 """A User-defined Collection"""
319 user
= request
.db
.User
.find_one({
320 'username': request
.matchdict
['user'],
321 'status': u
'active'})
323 return render_404(request
)
325 collection
= request
.db
.Collection
.find_one(
326 {'slug': request
.matchdict
['collection'] })
328 cursor
= request
.db
.CollectionItem
.find(
329 {'collection': collection
.id })
331 pagination
= Pagination(page
, cursor
)
332 collection_items
= pagination()
334 #if no data is available, return NotFound
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
.POST
)
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
.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
.POST
)
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({
457 'uploader': user
._id
,
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()
508 def collection_atom_feed(request
):
510 generates the atom feed with the newest images from a collection
513 user
= request
.db
.User
.find_one({
514 'username': request
.matchdict
['user'],
515 'status': u
'active'})
517 return render_404(request
)
519 collection
= request
.db
.Collection
.find_one({
521 'slug': request
.matchdict
['collection']})
523 cursor
= request
.db
.CollectionItem
.find({
524 'collection': collection
._id
}) \
525 .sort('added', DESCENDING
) \
526 .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS
)
529 ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
532 'href': request
.urlgen(
533 'mediagoblin.user_pages.user_collection',
534 qualified
=True, user
=request
.matchdict
['user'], collection
=collection
.slug
),
539 if mg_globals
.app_config
["push_urls"]:
540 for push_url
in mg_globals
.app_config
["push_urls"]:
546 "MediaGoblin: Feed for %s's collection %s" % (request
.matchdict
['user'], collection
.title
),
547 feed_url
=request
.url
,
548 id='tag:{host},{year}:collection.user-{user}.title-{title}'.format(
550 year
=datetime
.datetime
.today().strftime('%Y'),
551 user
=request
.matchdict
['user'],
552 title
=collection
.title
),
556 entry
= item
.get_media_entry
557 feed
.add(entry
.get('title'),
559 id=entry
.url_for_self(request
.urlgen
, qualified
=True),
562 'name': entry
.get_uploader
.username
,
563 'uri': request
.urlgen(
564 'mediagoblin.user_pages.user_home',
565 qualified
=True, user
=entry
.get_uploader
.username
)},
566 updated
=item
.get('added'),
568 'href': entry
.url_for_self(
572 'type': 'text/html'}])
574 return feed
.get_response()
577 @require_active_login
578 def processing_panel(request
):
580 Show to the user what media is still in conversion/processing...
581 and what failed, and why!
584 user
= request
.db
.User
.find_one(
585 {'username': request
.matchdict
['user'],
586 'status': u
'active'})
588 # Make sure the user exists and is active
590 return render_404(request
)
591 elif user
.status
!= u
'active':
592 return render_to_response(
594 'mediagoblin/user_pages/user.html',
597 # XXX: Should this be a decorator?
599 # Make sure we have permission to access this user's panel. Only
600 # admins and this user herself should be able to do so.
601 if not (user
._id
== request
.user
._id
602 or request
.user
.is_admin
):
603 # No? Let's simply redirect to this user's homepage then.
605 request
, 'mediagoblin.user_pages.user_home',
606 user
=request
.matchdict
['user'])
608 # Get media entries which are in-processing
609 processing_entries
= request
.db
.MediaEntry
.find(
610 {'uploader': user
._id
,
611 'state': u
'processing'}).sort('created', DESCENDING
)
613 # Get media entries which have failed to process
614 failed_entries
= request
.db
.MediaEntry
.find(
615 {'uploader': user
._id
,
616 'state': u
'failed'}).sort('created', DESCENDING
)
618 processed_entries
= request
.db
.MediaEntry
.find(
619 {'uploader': user
._id
,
620 'state': u
'processed'}).sort('created', DESCENDING
).limit(10)
623 return render_to_response(
625 'mediagoblin/user_pages/processing_panel.html',
627 'processing_entries': processing_entries
,
628 'failed_entries': failed_entries
,
629 'processed_entries': processed_entries
})