From 08750772eaf140266d1be0aac5024f581664012b Mon Sep 17 00:00:00 2001 From: Mark Holmquist Date: Mon, 22 Aug 2011 02:57:40 -0700 Subject: [PATCH 1/7] + 'confirm' section for confirmation dialogues + implemented delete functionality * fixed several instances of 'must be an instance of unicode, not str' --- mediagoblin/auth/views.py | 11 ++++++----- mediagoblin/edit/views.py | 13 ++++++++----- mediagoblin/routing.py | 2 ++ mediagoblin/submit/views.py | 4 ++-- .../templates/mediagoblin/user_pages/media.html | 7 +++++-- mediagoblin/user_pages/routing.py | 2 ++ mediagoblin/user_pages/views.py | 2 +- 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py index 4c4a34fd..48c5937c 100644 --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@ -44,11 +44,12 @@ def register(request): if request.method == 'POST' and register_form.validate(): # TODO: Make sure the user doesn't exist already - + username = unicode(request.POST['username'].lower()) + email = unicode(request.POST['email'].lower()) users_with_username = request.db.User.find( - {'username': request.POST['username'].lower()}).count() + {'username': username}).count() users_with_email = request.db.User.find( - {'email': request.POST['email'].lower()}).count() + {'email': email}).count() extra_validation_passes = True @@ -64,8 +65,8 @@ def register(request): if extra_validation_passes: # Create the user user = request.db.User() - user['username'] = request.POST['username'].lower() - user['email'] = request.POST['email'].lower() + user['username'] = username + user['email'] = email user['pw_hash'] = auth_lib.bcrypt_gen_password_hash( request.POST['password']) user.save(validate=True) diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 0b1a98f1..2a835816 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import uuid from webob import exc from string import split @@ -53,15 +54,17 @@ def edit_media(request, media): form.slug.errors.append( _(u'An entry with that slug already exists for this user.')) else: - media['title'] = request.POST['title'] - media['description'] = request.POST.get('description') + media['title'] = unicode(request.POST['title']) + media['description'] = unicode(request.POST.get('description')) media['tags'] = convert_to_tag_list_of_dicts( request.POST.get('tags')) media['description_html'] = cleaned_markdown_conversion( media['description']) - media['slug'] = request.POST['slug'] + media['slug'] = unicode(request.POST['slug']) + task_id = unicode(uuid.uuid4()) + media['queued_task_id'] = task_id media.save() return redirect(request, "mediagoblin.user_pages.media_home", @@ -102,8 +105,8 @@ def edit_profile(request): bio = user.get('bio')) if request.method == 'POST' and form.validate(): - user['url'] = request.POST['url'] - user['bio'] = request.POST['bio'] + user['url'] = unicode(request.POST['url']) + user['bio'] = unicode(request.POST['bio']) user['bio_html'] = cleaned_markdown_conversion(user['bio']) diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py index 1340da60..125f7270 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -21,6 +21,7 @@ from mediagoblin.submit.routing import submit_routes from mediagoblin.user_pages.routing import user_routes from mediagoblin.edit.routing import edit_routes from mediagoblin.listings.routing import tag_routes +from mediagoblin.confirm.routing import confirm_routes def get_mapper(): @@ -36,5 +37,6 @@ def get_mapper(): mapping.extend(user_routes, '/u') mapping.extend(edit_routes, '/edit') mapping.extend(tag_routes, '/tag') + mapping.extend(confirm_routes, '/confirm') return mapping diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py index 1ba17954..5bcc5393 100644 --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@ -55,10 +55,10 @@ def submit_start(request): entry = request.db.MediaEntry() entry['_id'] = ObjectId() entry['title'] = ( - request.POST['title'] + unicode(request.POST['title']) or unicode(splitext(filename)[0])) - entry['description'] = request.POST.get('description') + entry['description'] = unicode(request.POST.get('description')) entry['description_html'] = cleaned_markdown_conversion( entry['description']) diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 6747fddc..c80144aa 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -127,8 +127,11 @@ class="media_icon" />edit

- {% trans %}delete{% endtrans %} + {% trans %}delete{% endtrans %}

{% endif %} diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py index bf9f12ab..1283355a 100644 --- a/mediagoblin/user_pages/routing.py +++ b/mediagoblin/user_pages/routing.py @@ -29,6 +29,8 @@ user_routes = [ controller="mediagoblin.user_pages.views:media_home"), Route('mediagoblin.edit.edit_media', "/{user}/m/{media}/edit/", controller="mediagoblin.edit.views:edit_media"), + Route('mediagoblin.confirm.confirm_delete', "/{user}/m/{media}/confirm/", + controller="mediagoblin.confirm.views:confirm_delete"), Route('mediagoblin.user_pages.atom_feed', '/{user}/atom/', controller="mediagoblin.user_pages.views:atom_feed"), Route('mediagoblin.user_pages.media_post_comment', diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 3677c134..bb789f42 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -129,7 +129,7 @@ def media_post_comment(request): comment = request.db.MediaComment() comment['media_entry'] = ObjectId(request.matchdict['media']) comment['author'] = request.user['_id'] - comment['content'] = request.POST['comment_content'] + comment['content'] = unicode(request.POST['comment_content']) comment['content_html'] = cleaned_markdown_conversion(comment['content']) From ae4feecfc20b6c7daf54e7822dc85dba6ac0f2eb Mon Sep 17 00:00:00 2001 From: Mark Holmquist Date: Mon, 22 Aug 2011 03:35:44 -0700 Subject: [PATCH 2/7] * Fix bad commit (oops!) * Delete works now --- mediagoblin/confirm/__init__.py | 17 ++++++ mediagoblin/confirm/forms.py | 26 +++++++++ mediagoblin/confirm/lib.py | 24 ++++++++ mediagoblin/confirm/routing.py | 21 +++++++ mediagoblin/confirm/views.py | 56 +++++++++++++++++++ .../mediagoblin/confirm/confirm_delete.html | 40 +++++++++++++ 6 files changed, 184 insertions(+) create mode 100644 mediagoblin/confirm/__init__.py create mode 100644 mediagoblin/confirm/forms.py create mode 100644 mediagoblin/confirm/lib.py create mode 100644 mediagoblin/confirm/routing.py create mode 100644 mediagoblin/confirm/views.py create mode 100644 mediagoblin/templates/mediagoblin/confirm/confirm_delete.html diff --git a/mediagoblin/confirm/__init__.py b/mediagoblin/confirm/__init__.py new file mode 100644 index 00000000..a8eeb5ed --- /dev/null +++ b/mediagoblin/confirm/__init__.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + diff --git a/mediagoblin/confirm/forms.py b/mediagoblin/confirm/forms.py new file mode 100644 index 00000000..4529528b --- /dev/null +++ b/mediagoblin/confirm/forms.py @@ -0,0 +1,26 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import wtforms + +from mediagoblin.util import fake_ugettext_passthrough as _ + +class ConfirmDeleteForm(wtforms.Form): + confirm = wtforms.RadioField('Confirm', + default='False', + choices=[('False', 'No, I made a mistake!'), + ('True', 'Yes, delete it!')]) diff --git a/mediagoblin/confirm/lib.py b/mediagoblin/confirm/lib.py new file mode 100644 index 00000000..2efc3735 --- /dev/null +++ b/mediagoblin/confirm/lib.py @@ -0,0 +1,24 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +def may_delete_media(request, media): + """Check, if the request's user may edit the media details""" + if media['uploader'] == request.user['_id']: + return True + if request.user['is_admin']: + return True + return False diff --git a/mediagoblin/confirm/routing.py b/mediagoblin/confirm/routing.py new file mode 100644 index 00000000..d8c1ef22 --- /dev/null +++ b/mediagoblin/confirm/routing.py @@ -0,0 +1,21 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from routes.route import Route + +confirm_routes = [ +] diff --git a/mediagoblin/confirm/views.py b/mediagoblin/confirm/views.py new file mode 100644 index 00000000..a4a63582 --- /dev/null +++ b/mediagoblin/confirm/views.py @@ -0,0 +1,56 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import uuid + +from webob import exc +from string import split + +from mediagoblin import messages +from mediagoblin import mg_globals +from mediagoblin.util import ( + render_to_response, redirect, clean_html, convert_to_tag_list_of_dicts, + media_tags_as_string, cleaned_markdown_conversion) +from mediagoblin.util import pass_to_ugettext as _ +from mediagoblin.confirm import forms +from mediagoblin.confirm.lib import may_delete_media +from mediagoblin.decorators import require_active_login, get_user_media_entry + + +@get_user_media_entry +@require_active_login +def confirm_delete(request, media): + if not may_delete_media(request, media): + return exc.HTTPForbidden() + + form = forms.ConfirmDeleteForm(request.POST) + + if request.method == 'POST' and form.validate(): + if request.POST.get('confirm') == 'True': + username = media.uploader()['username'] + media.delete() + return redirect(request, "mediagoblin.user_pages.user_home", + user=username) + else: + return redirect(request, "mediagoblin.user_pages.media_home", + user=media.uploader()['username'], + media=media['slug']) + + return render_to_response( + request, + 'mediagoblin/confirm/confirm_delete.html', + {'media': media, + 'form': form}) diff --git a/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html b/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html new file mode 100644 index 00000000..ada89d5d --- /dev/null +++ b/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html @@ -0,0 +1,40 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + +
+
+

+ {%- trans title=media['title'] -%} + Really delete {{ title }}? + {%- endtrans %} +

+ {{ wtforms_util.render_divs(form) }} +
+ +
+
+
+{% endblock %} From 46df0297e029c075a2c28b0c31490c560c99158c Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Mon, 29 Aug 2011 00:42:55 +0200 Subject: [PATCH 3/7] Added notice that deletion of a media entry is *permanent* --- .../templates/mediagoblin/confirm/confirm_delete.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html b/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html index ada89d5d..67d45811 100644 --- a/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html +++ b/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html @@ -31,6 +31,14 @@ Really delete {{ title }}? {%- endtrans %} +

+ + {%- trans -%} + If you choose yes, the media entry will be deleted permanently. + {%- endtrans %} + +

+ {{ wtforms_util.render_divs(form) }}
From 39b45e203507b6cdb25491b719bfa152cb0241f7 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 29 Aug 2011 08:05:35 -0500 Subject: [PATCH 4/7] I think confirm_delete deserves a url like /confirm-delete/ rather than /confirm/ Adjusting appropriately. --- mediagoblin/user_pages/routing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py index 55ee8e4d..2c83593f 100644 --- a/mediagoblin/user_pages/routing.py +++ b/mediagoblin/user_pages/routing.py @@ -32,8 +32,9 @@ user_routes = [ Route('mediagoblin.edit.attachments', '/{user}/m/{media}/attachments/', controller="mediagoblin.edit.views:edit_attachments"), - Route('mediagoblin.confirm.confirm_delete', "/{user}/m/{media}/confirm/", - controller="mediagoblin.confirm.views:confirm_delete"), + Route('mediagoblin.confirm.confirm_delete', + "/{user}/m/{media}/confirm-delete/", + controller="mediagoblin.confirm.views:confirm_delete"), Route('mediagoblin.user_pages.atom_feed', '/{user}/atom/', controller="mediagoblin.user_pages.views:atom_feed"), Route('mediagoblin.user_pages.media_post_comment', From afe4e513f7d1ac827bbf9b1d827c8e2a604e16a3 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Tue, 30 Aug 2011 21:46:47 +0200 Subject: [PATCH 5/7] Added tests for delete/confirm_delete --- mediagoblin/tests/test_submission.py | 61 +++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index 9ae129cd..b2599d22 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -17,7 +17,7 @@ import urlparse import pkg_resources -from nose.tools import assert_equal +from nose.tools import assert_equal, assert_true, assert_false from mediagoblin.auth import lib as auth_lib from mediagoblin.tests.tools import setup_fresh_app, get_test_app @@ -53,6 +53,8 @@ class TestSubmission: test_user['pw_hash'] = auth_lib.bcrypt_gen_password_hash('toast') test_user.save() + self.test_user = test_user + self.test_app.post( '/auth/login/', { 'username': u'chris', @@ -150,6 +152,63 @@ class TestSubmission: u'Tags must be shorter than 50 characters. Tags that are too long'\ ': ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu'] + def test_delete(self): + util.clear_test_template_context() + response = self.test_app.post( + '/submit/', { + 'title': 'Balanced Goblin', + }, upload_files=[( + 'file', GOOD_JPG)]) + + # Post image + response.follow() + + request = util.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/user_pages/user.html']['request'] + + media = request.db.MediaEntry.find({'title': 'Balanced Goblin'})[0] + + # Does media entry exist? + assert_true(media) + + # Do not confirm deletion + # --------------------------------------------------- + response = self.test_app.post( + request.urlgen('mediagoblin.confirm.confirm_delete', + # No work: user=media.uploader().username, + user=self.test_user['username'], + media=media['_id']), + {'confirm': 'False'}) + + response.follow() + + request = util.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/user_pages/user.html']['request'] + + media = request.db.MediaEntry.find({'title': 'Balanced Goblin'})[0] + + # Does media entry still exist? + assert_true(media) + + # Confirm deletion + # --------------------------------------------------- + response = self.test_app.post( + request.urlgen('mediagoblin.confirm.confirm_delete', + # No work: user=media.uploader().username, + user=self.test_user['username'], + media=media['_id']), + {'confirm': 'True'}) + + response.follow() + + request = util.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/user_pages/user.html']['request'] + + # Does media entry still exist? + assert_false( + request.db.MediaEntry.find( + {'_id': media['_id']}).count()) + def test_malicious_uploads(self): # Test non-suppoerted file with non-supported extension # ----------------------------------------------------- From 7d0efbae2955cf58eed86daa30cd3507c9088269 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Tue, 30 Aug 2011 23:11:49 +0200 Subject: [PATCH 6/7] Deleted old `mediagoblin.confirm` files --- mediagoblin/confirm/__init__.py | 17 ------ mediagoblin/confirm/forms.py | 26 --------- mediagoblin/confirm/lib.py | 24 -------- mediagoblin/confirm/routing.py | 21 ------- mediagoblin/confirm/views.py | 56 ------------------- .../mediagoblin/confirm/confirm_delete.html | 48 ---------------- 6 files changed, 192 deletions(-) delete mode 100644 mediagoblin/confirm/__init__.py delete mode 100644 mediagoblin/confirm/forms.py delete mode 100644 mediagoblin/confirm/lib.py delete mode 100644 mediagoblin/confirm/routing.py delete mode 100644 mediagoblin/confirm/views.py delete mode 100644 mediagoblin/templates/mediagoblin/confirm/confirm_delete.html diff --git a/mediagoblin/confirm/__init__.py b/mediagoblin/confirm/__init__.py deleted file mode 100644 index a8eeb5ed..00000000 --- a/mediagoblin/confirm/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 Free Software Foundation, Inc -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - - diff --git a/mediagoblin/confirm/forms.py b/mediagoblin/confirm/forms.py deleted file mode 100644 index 4529528b..00000000 --- a/mediagoblin/confirm/forms.py +++ /dev/null @@ -1,26 +0,0 @@ -# GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 Free Software Foundation, Inc -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - - -import wtforms - -from mediagoblin.util import fake_ugettext_passthrough as _ - -class ConfirmDeleteForm(wtforms.Form): - confirm = wtforms.RadioField('Confirm', - default='False', - choices=[('False', 'No, I made a mistake!'), - ('True', 'Yes, delete it!')]) diff --git a/mediagoblin/confirm/lib.py b/mediagoblin/confirm/lib.py deleted file mode 100644 index 2efc3735..00000000 --- a/mediagoblin/confirm/lib.py +++ /dev/null @@ -1,24 +0,0 @@ -# GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 Free Software Foundation, Inc -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - - -def may_delete_media(request, media): - """Check, if the request's user may edit the media details""" - if media['uploader'] == request.user['_id']: - return True - if request.user['is_admin']: - return True - return False diff --git a/mediagoblin/confirm/routing.py b/mediagoblin/confirm/routing.py deleted file mode 100644 index d8c1ef22..00000000 --- a/mediagoblin/confirm/routing.py +++ /dev/null @@ -1,21 +0,0 @@ -# GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 Free Software Foundation, Inc -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - - -from routes.route import Route - -confirm_routes = [ -] diff --git a/mediagoblin/confirm/views.py b/mediagoblin/confirm/views.py deleted file mode 100644 index a4a63582..00000000 --- a/mediagoblin/confirm/views.py +++ /dev/null @@ -1,56 +0,0 @@ -# GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 Free Software Foundation, Inc -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import uuid - -from webob import exc -from string import split - -from mediagoblin import messages -from mediagoblin import mg_globals -from mediagoblin.util import ( - render_to_response, redirect, clean_html, convert_to_tag_list_of_dicts, - media_tags_as_string, cleaned_markdown_conversion) -from mediagoblin.util import pass_to_ugettext as _ -from mediagoblin.confirm import forms -from mediagoblin.confirm.lib import may_delete_media -from mediagoblin.decorators import require_active_login, get_user_media_entry - - -@get_user_media_entry -@require_active_login -def confirm_delete(request, media): - if not may_delete_media(request, media): - return exc.HTTPForbidden() - - form = forms.ConfirmDeleteForm(request.POST) - - if request.method == 'POST' and form.validate(): - if request.POST.get('confirm') == 'True': - username = media.uploader()['username'] - media.delete() - return redirect(request, "mediagoblin.user_pages.user_home", - user=username) - else: - return redirect(request, "mediagoblin.user_pages.media_home", - user=media.uploader()['username'], - media=media['slug']) - - return render_to_response( - request, - 'mediagoblin/confirm/confirm_delete.html', - {'media': media, - 'form': form}) diff --git a/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html b/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html deleted file mode 100644 index 67d45811..00000000 --- a/mediagoblin/templates/mediagoblin/confirm/confirm_delete.html +++ /dev/null @@ -1,48 +0,0 @@ -{# -# GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011 Free Software Foundation, Inc -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -#} -{% extends "mediagoblin/base.html" %} - -{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} - -{% block mediagoblin_content %} - -
-
-

- {%- trans title=media['title'] -%} - Really delete {{ title }}? - {%- endtrans %} -

-

- - {%- trans -%} - If you choose yes, the media entry will be deleted permanently. - {%- endtrans %} - -

- - {{ wtforms_util.render_divs(form) }} -
- -
-
-
-{% endblock %} From 502073f2bf65380be18b349a678ac075777889a4 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Tue, 30 Aug 2011 23:16:46 +0200 Subject: [PATCH 7/7] Feature #403 - Ability to delete media entries - Fixes according to feedback * Moved `mediagoblin.confirm` stuff to `mediagoblin.user_pages`, templates too. * Removed route extension for `mediagoblin.confirm` * Created `delete_media_files` which deletes all media files on the public_store when the entry is deleted * Created a new decorator to check if a user has the permission to delete an entry. --- mediagoblin/decorators.py | 26 ++++++++++ mediagoblin/routing.py | 1 - mediagoblin/storage.py | 3 +- .../mediagoblin/user_pages/media.html | 2 +- .../user_pages/media_confirm_delete.html | 48 +++++++++++++++++++ mediagoblin/tests/test_submission.py | 4 +- mediagoblin/user_pages/forms.py | 7 +++ mediagoblin/user_pages/routing.py | 4 +- mediagoblin/user_pages/views.py | 34 ++++++++++++- mediagoblin/util.py | 15 ++++++ 10 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index c66049ca..c3d64327 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -51,6 +51,31 @@ def require_active_login(controller): return _make_safe(new_controller_func, controller) +def user_may_delete_media(controller): + """ + Require user ownership of the MediaEntry + + Originally: +def may_delete_media(request, media): + \"\"\" + Check, if the request's user may edit the media details + \"\"\" + if media['uploader'] == request.user['_id']: + return True + if request.user['is_admin']: + return True + return False + """ + def wrapper(request, *args, **kwargs): + if not request.user['_id'] == request.db.MediaEntry.find_one( + {'_id': ObjectId( + request.matchdict['media'])}).uploader()['_id']: + return exc.HTTPForbidden() + + return controller(request, *args, **kwargs) + + return _make_safe(wrapper, controller) + def uses_pagination(controller): """ @@ -122,3 +147,4 @@ def get_media_entry_by_id(controller): return controller(request, media=media, *args, **kwargs) return _make_safe(wrapper, controller) + diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py index 125f7270..f78658c5 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -37,6 +37,5 @@ def get_mapper(): mapping.extend(user_routes, '/u') mapping.extend(edit_routes, '/edit') mapping.extend(tag_routes, '/tag') - mapping.extend(confirm_routes, '/confirm') return mapping diff --git a/mediagoblin/storage.py b/mediagoblin/storage.py index 7ada95e1..82b7a5ff 100644 --- a/mediagoblin/storage.py +++ b/mediagoblin/storage.py @@ -281,7 +281,8 @@ class CloudFilesStorage(StorageInterface): def delete_file(self, filepath): # TODO: Also delete unused directories if empty (safely, with # checks to avoid race conditions). - self.container.delete_object(filepath) + self.container.delete_object( + self._resolve_filepath(filepath)) def file_url(self, filepath): return '/'.join([ diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index fe953e77..171ea21d 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -128,7 +128,7 @@ class="media_icon" />edit

- . +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + +

+
+

+ {%- trans title=media['title'] -%} + Really delete {{ title }}? + {%- endtrans %} +

+

+ + {%- trans -%} + If you choose yes, the media entry will be deleted permanently. + {%- endtrans %} + +

+ + {{ wtforms_util.render_divs(form) }} +
+ +
+
+ +{% endblock %} diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index b2599d22..43a81f02 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -174,7 +174,7 @@ class TestSubmission: # Do not confirm deletion # --------------------------------------------------- response = self.test_app.post( - request.urlgen('mediagoblin.confirm.confirm_delete', + request.urlgen('mediagoblin.user_pages.media_confirm_delete', # No work: user=media.uploader().username, user=self.test_user['username'], media=media['_id']), @@ -193,7 +193,7 @@ class TestSubmission: # Confirm deletion # --------------------------------------------------- response = self.test_app.post( - request.urlgen('mediagoblin.confirm.confirm_delete', + request.urlgen('mediagoblin.user_pages.media_confirm_delete', # No work: user=media.uploader().username, user=self.test_user['username'], media=media['_id']), diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py index 25001019..4a79bedd 100644 --- a/mediagoblin/user_pages/forms.py +++ b/mediagoblin/user_pages/forms.py @@ -23,3 +23,10 @@ class MediaCommentForm(wtforms.Form): comment_content = wtforms.TextAreaField( _('Comment'), [wtforms.validators.Required()]) + + +class ConfirmDeleteForm(wtforms.Form): + confirm = wtforms.RadioField('Confirm', + default='False', + choices=[('False', 'No, I made a mistake!'), + ('True', 'Yes, delete it!')]) diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py index 2c83593f..ffa6f969 100644 --- a/mediagoblin/user_pages/routing.py +++ b/mediagoblin/user_pages/routing.py @@ -32,9 +32,9 @@ user_routes = [ Route('mediagoblin.edit.attachments', '/{user}/m/{media}/attachments/', controller="mediagoblin.edit.views:edit_attachments"), - Route('mediagoblin.confirm.confirm_delete', + Route('mediagoblin.user_pages.media_confirm_delete', "/{user}/m/{media}/confirm-delete/", - controller="mediagoblin.confirm.views:confirm_delete"), + controller="mediagoblin.user_pages.views:media_confirm_delete"), Route('mediagoblin.user_pages.atom_feed', '/{user}/atom/', controller="mediagoblin.user_pages.views:atom_feed"), Route('mediagoblin.user_pages.media_post_comment', diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 080d98d7..2163acf7 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -20,11 +20,11 @@ from mediagoblin import messages, mg_globals from mediagoblin.db.util import DESCENDING, ObjectId from mediagoblin.util import ( Pagination, render_to_response, redirect, cleaned_markdown_conversion, - render_404) + render_404, delete_media_files) from mediagoblin.user_pages import forms as user_forms from mediagoblin.decorators import (uses_pagination, get_user_media_entry, - require_active_login) + require_active_login, user_may_delete_media) from werkzeug.contrib.atom import AtomFeed @@ -145,6 +145,36 @@ def media_post_comment(request): user = request.matchdict['user']) +@get_user_media_entry +@require_active_login +@user_may_delete_media +def media_confirm_delete(request, media): + + form = user_forms.ConfirmDeleteForm(request.POST) + + if request.method == 'POST' and form.validate(): + if request.POST.get('confirm') == 'True': + username = media.uploader()['username'] + + # Delete all files on the public storage + delete_media_files(media) + + media.delete() + + return redirect(request, "mediagoblin.user_pages.user_home", + user=username) + else: + return redirect(request, "mediagoblin.user_pages.media_home", + user=media.uploader()['username'], + media=media['slug']) + + return render_to_response( + request, + 'mediagoblin/user_pages/media_confirm_delete.html', + {'media': media, + 'form': form}) + + ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15 def atom_feed(request): diff --git a/mediagoblin/util.py b/mediagoblin/util.py index ba4ac01e..27c81f3a 100644 --- a/mediagoblin/util.py +++ b/mediagoblin/util.py @@ -681,3 +681,18 @@ def render_404(request): """ return render_to_response( request, 'mediagoblin/404.html', {}, status=400) + +def delete_media_files(media): + """ + Delete all files associated with a MediaEntry + + Arguments: + - media: A MediaEntry document + """ + for handle, listpath in media['media_files'].items(): + mg_globals.public_store.delete_file( + listpath) + + for attachment in media['attachment_files']: + mg_globals.public_store.delete_file( + attachment['filepath'])