Commit | Line | Data |
---|---|---|
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 | 17 | import logging |
64712915 | 18 | import datetime |
5ab60299 | 19 | import json |
52359e91 | 20 | |
e49b7e02 BP |
21 | import six |
22 | ||
3a8c3a38 | 23 | from mediagoblin import messages, mg_globals |
64a456a4 JT |
24 | from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, Comment, |
25 | CollectionItem, LocalUser, Activity, \ | |
26 | GenericModelReference) | |
2a01c5e7 | 27 | from mediagoblin.plugins.api.tools import get_media_file_paths |
2e6ee596 E |
28 | from mediagoblin.tools.response import render_to_response, render_404, \ |
29 | redirect, redirect_obj | |
5ab60299 | 30 | from mediagoblin.tools.text import cleaned_markdown_conversion |
a789b713 | 31 | from mediagoblin.tools.translate import pass_to_ugettext as _ |
152a3bfa | 32 | from mediagoblin.tools.pagination import Pagination |
b9492011 | 33 | from mediagoblin.tools.federation import create_activity |
9074ee7c | 34 | from mediagoblin.user_pages import forms as user_forms |
52a355b2 | 35 | from mediagoblin.user_pages.lib import (send_comment_email, |
9d6e453f | 36 | add_media_to_collection, build_report_object) |
2d7b6bde JW |
37 | from mediagoblin.notifications import trigger_notification, \ |
38 | add_comment_subscription, mark_comment_notification_seen | |
2edd6b0b | 39 | from mediagoblin.tools.pluginapi import hook_transform |
f6249408 | 40 | |
50854db0 | 41 | from 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 | 47 | from werkzeug.contrib.atom import AtomFeed |
2d7b6bde | 48 | from werkzeug.exceptions import MethodNotAllowed |
5ab60299 | 49 | from 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 | 57 | def 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 | 93 | def 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 | 123 | MEDIA_COMMENTS_PER_PAGE = 50 |
434b3221 | 124 | |
6bba33d7 | 125 | @user_not_banned |
01674e10 | 126 | @get_user_media_entry |
9074ee7c | 127 | @uses_pagination |
6f59a3a3 | 128 | def 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 | 175 | def 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 | |
221 | def 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 |
235 | def 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 |
337 | def 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 | 394 | def 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 | 423 | def 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 | |
438 | def 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 | |
485 | def 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 | 538 | ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15 |
00c39256 | 539 | |
243c3843 | 540 | |
00c39256 BK |
541 | def 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 |
611 | def 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 | 683 | def 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 |
726 | def 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 | |
766 | def 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 | ) |