From aba81c9f20acd0fa3fd1a31db678fccfba8777d1 Mon Sep 17 00:00:00 2001 From: Elrond Date: Thu, 26 May 2011 23:09:33 +0200 Subject: [PATCH 01/11] Starting "edit" functionality. This adds a link to the "edit" form, the form, the view for displaying the form and that's about it. --- mediagoblin/decorators.py | 20 ++++++++++ mediagoblin/edit/__init__.py | 0 mediagoblin/edit/forms.py | 27 +++++++++++++ mediagoblin/edit/routing.py | 22 +++++++++++ mediagoblin/edit/views.py | 23 +++++++++++ mediagoblin/routing.py | 2 + .../templates/mediagoblin/edit/edit.html | 38 +++++++++++++++++++ .../mediagoblin/user_pages/media.html | 3 ++ 8 files changed, 135 insertions(+) create mode 100644 mediagoblin/edit/__init__.py create mode 100644 mediagoblin/edit/forms.py create mode 100644 mediagoblin/edit/routing.py create mode 100644 mediagoblin/edit/views.py create mode 100644 mediagoblin/templates/mediagoblin/edit/edit.html diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index ff3f0b5e..fe631112 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -99,3 +99,23 @@ def get_user_media_entry(controller): return controller(request, media=media, *args, **kwargs) return _make_safe(wrapper, controller) + +def get_media_entry_by_id(controller): + """ + Pass in a MediaEntry based off of a url component + """ + def wrapper(request, *args, **kwargs): + try: + media = request.db.MediaEntry.find_one( + {'_id': ObjectId(request.matchdict['media']), + 'state': 'processed'}) + except InvalidId: + return exc.HTTPNotFound() + + # Still no media? Okay, 404. + if not media: + return exc.HTTPNotFound() + + return controller(request, media=media, *args, **kwargs) + + return _make_safe(wrapper, controller) diff --git a/mediagoblin/edit/__init__.py b/mediagoblin/edit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py new file mode 100644 index 00000000..ea25141d --- /dev/null +++ b/mediagoblin/edit/forms.py @@ -0,0 +1,27 @@ +# 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 + + +class EditForm(wtforms.Form): + title = wtforms.TextField( + 'Title', + [wtforms.validators.Length(min=0, max=500)]) + slug = wtforms.TextField( + 'Slug') + description = wtforms.TextAreaField('Description of this work') diff --git a/mediagoblin/edit/routing.py b/mediagoblin/edit/routing.py new file mode 100644 index 00000000..d7396a60 --- /dev/null +++ b/mediagoblin/edit/routing.py @@ -0,0 +1,22 @@ +# 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 + +edit_routes = [ + Route('mediagoblin.edit.edit_media', "/{media}/", + controller="mediagoblin.edit.views:edit_media"), +] diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py new file mode 100644 index 00000000..11dd58be --- /dev/null +++ b/mediagoblin/edit/views.py @@ -0,0 +1,23 @@ + + +from webob import Response + +from mediagoblin.edit import forms +from mediagoblin.decorators import require_active_login, get_media_entry_by_id + +@get_media_entry_by_id +@require_active_login +def edit_media(request, media): + form = forms.EditForm(request.POST, + title = media['title'], + slug = media['slug'], + description = media['description']) + + # render + template = request.template_env.get_template( + 'mediagoblin/edit/edit.html') + return Response( + template.render( + {'request': request, + 'media': media, + 'form': form})) diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py index 356ef678..b854c85a 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -19,6 +19,7 @@ from routes import Mapper from mediagoblin.auth.routing import auth_routes from mediagoblin.submit.routing import submit_routes from mediagoblin.user_pages.routing import user_routes +from mediagoblin.edit.routing import edit_routes def get_mapper(): mapping = Mapper() @@ -31,5 +32,6 @@ def get_mapper(): mapping.extend(auth_routes, '/auth') mapping.extend(submit_routes, '/submit') mapping.extend(user_routes, '/u') + mapping.extend(edit_routes, '/edit') return mapping diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html new file mode 100644 index 00000000..72773cb5 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/edit.html @@ -0,0 +1,38 @@ +{# +# 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 %} +

Edit details for {{ media.title }}

+ +
+ + {{ wtforms_util.render_table(form) }} + + + + +
+
+ +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 036bf726..f13c32e3 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -41,6 +41,9 @@ {{- media.uploader().username }}
Description: {{ media.description }} +
+ Edit {% else %}

Sorry, no such media found.

From 8782001bf0002143f412e9612e97939f57d63ffe Mon Sep 17 00:00:00 2001 From: Elrond Date: Thu, 26 May 2011 23:17:41 +0200 Subject: [PATCH 02/11] Use new button style --- mediagoblin/templates/mediagoblin/edit/edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html index 72773cb5..bd85f361 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit.html +++ b/mediagoblin/templates/mediagoblin/edit/edit.html @@ -29,7 +29,7 @@ {{ wtforms_util.render_table(form) }} - + From 98857207ccb432117709f64137ca20f81635f288 Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 27 May 2011 00:17:30 +0200 Subject: [PATCH 03/11] "edit": Finally implement saving. Currently no checks. Probably not so good. And especially, every logged in user currently can edit the data for any other user's media. --- mediagoblin/edit/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 11dd58be..050ece4e 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -1,6 +1,6 @@ -from webob import Response +from webob import Response, exc from mediagoblin.edit import forms from mediagoblin.decorators import require_active_login, get_media_entry_by_id @@ -13,6 +13,17 @@ def edit_media(request, media): slug = media['slug'], description = media['description']) + if request.method == 'POST' and form.validate(): + media['title'] = request.POST['title'] + media['description'] = request.POST['description'] + media['slug'] = request.POST['slug'] + media.save() + + # redirect + return exc.HTTPFound( + location=request.urlgen("mediagoblin.user_pages.media_home", + user=media.uploader()['username'], media=media['_id'])) + # render template = request.template_env.get_template( 'mediagoblin/edit/edit.html') From c849e690925cb656b8c00ccbeda12aeab22c2fdd Mon Sep 17 00:00:00 2001 From: Elrond Date: Thu, 2 Jun 2011 14:25:31 +0200 Subject: [PATCH 04/11] Check for edit permission. You need to own the media, or be an admin to use the edit form. As simple as that, for now. --- mediagoblin/edit/views.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 050ece4e..e5dccc81 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -5,9 +5,22 @@ from webob import Response, exc from mediagoblin.edit import forms from mediagoblin.decorators import require_active_login, get_media_entry_by_id + +def may_edit_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 + + @get_media_entry_by_id @require_active_login def edit_media(request, media): + if not may_edit_media(request, media): + return exc.HTTPForbidden() + form = forms.EditForm(request.POST, title = media['title'], slug = media['slug'], From b897fdf91b2fe03c299880545fc6712d50cd0e97 Mon Sep 17 00:00:00 2001 From: Elrond Date: Thu, 2 Jun 2011 16:39:47 +0200 Subject: [PATCH 05/11] Change edit form to use divs instead of table --- mediagoblin/templates/mediagoblin/edit/edit.html | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html index bd85f361..d694ce25 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit.html +++ b/mediagoblin/templates/mediagoblin/edit/edit.html @@ -25,13 +25,12 @@

- - {{ wtforms_util.render_table(form) }} - - - - -
+
+ {{ wtforms_util.render_divs(form) }} +
+ +
+
From 8cd5d4f8c3de634905651b1d1e8cd1355b7f3a99 Mon Sep 17 00:00:00 2001 From: Elrond Date: Thu, 2 Jun 2011 16:48:15 +0200 Subject: [PATCH 06/11] Prepare for moving /edit/ under /u/.../edit/ To make moving the whole thing under /u/ easier, prepare to pass in the {user} needed for that. --- mediagoblin/edit/routing.py | 2 +- mediagoblin/edit/views.py | 4 ++-- mediagoblin/templates/mediagoblin/edit/edit.html | 1 + mediagoblin/templates/mediagoblin/user_pages/media.html | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mediagoblin/edit/routing.py b/mediagoblin/edit/routing.py index d7396a60..54f2661a 100644 --- a/mediagoblin/edit/routing.py +++ b/mediagoblin/edit/routing.py @@ -17,6 +17,6 @@ from routes.route import Route edit_routes = [ - Route('mediagoblin.edit.edit_media', "/{media}/", + Route('mediagoblin.edit.edit_media', "/{user}/{media}/", controller="mediagoblin.edit.views:edit_media"), ] diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index e5dccc81..a0afaa30 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -3,7 +3,7 @@ from webob import Response, exc from mediagoblin.edit import forms -from mediagoblin.decorators import require_active_login, get_media_entry_by_id +from mediagoblin.decorators import require_active_login, get_user_media_entry def may_edit_media(request, media): @@ -15,7 +15,7 @@ def may_edit_media(request, media): return False -@get_media_entry_by_id +@get_user_media_entry @require_active_login def edit_media(request, media): if not may_edit_media(request, media): diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html index d694ce25..295d57eb 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit.html +++ b/mediagoblin/templates/mediagoblin/edit/edit.html @@ -23,6 +23,7 @@

Edit details for {{ media.title }}

diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index b26e2514..406fd3f6 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -34,6 +34,7 @@ user= media.uploader().username) }}"> {{- media.uploader().username }}

Edit

{% else %}

Sorry, no such media found.

From 0732236e9c816dd2b04f5a9e97632a861de225ad Mon Sep 17 00:00:00 2001 From: Elrond Date: Thu, 2 Jun 2011 17:43:54 +0200 Subject: [PATCH 07/11] Handle Exceptions from save(); Move may_edit_media Turn .save() excpetions into a HTTPConflict. Not nice, but at least the user gets the error. Until there is a proper way to validate things and get nice errors. Move may_edit_media() to lib.py, as it's not a view. --- mediagoblin/edit/lib.py | 8 ++++++++ mediagoblin/edit/views.py | 15 +++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 mediagoblin/edit/lib.py diff --git a/mediagoblin/edit/lib.py b/mediagoblin/edit/lib.py new file mode 100644 index 00000000..293a0547 --- /dev/null +++ b/mediagoblin/edit/lib.py @@ -0,0 +1,8 @@ + +def may_edit_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/edit/views.py b/mediagoblin/edit/views.py index a0afaa30..b8e28e29 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -3,18 +3,10 @@ from webob import Response, exc from mediagoblin.edit import forms +from mediagoblin.edit.lib import may_edit_media from mediagoblin.decorators import require_active_login, get_user_media_entry -def may_edit_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 - - @get_user_media_entry @require_active_login def edit_media(request, media): @@ -30,7 +22,10 @@ def edit_media(request, media): media['title'] = request.POST['title'] media['description'] = request.POST['description'] media['slug'] = request.POST['slug'] - media.save() + try: + media.save() + except Exception as e: + return exc.HTTPConflict(detail = str(e)) # redirect return exc.HTTPFound( From 9bfe1d8e12c77d7a47049bffaa694441abd71489 Mon Sep 17 00:00:00 2001 From: Elrond Date: Thu, 2 Jun 2011 18:28:26 +0200 Subject: [PATCH 08/11] Add GNU headers to new *.py --- mediagoblin/edit/lib.py | 16 ++++++++++++++++ mediagoblin/edit/views.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/mediagoblin/edit/lib.py b/mediagoblin/edit/lib.py index 293a0547..2a810349 100644 --- a/mediagoblin/edit/lib.py +++ b/mediagoblin/edit/lib.py @@ -1,3 +1,19 @@ +# 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_edit_media(request, media): """Check, if the request's user may edit the media details""" diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index b8e28e29..7df47b18 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -1,3 +1,18 @@ +# 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 webob import Response, exc From 55c74e1e4b36d56c1f3c8e69a4cbea43843093c8 Mon Sep 17 00:00:00 2001 From: Elrond Date: Fri, 3 Jun 2011 23:54:33 +0200 Subject: [PATCH 09/11] Propagate Exceptions up in Celery-Eager-Mode When running in celery-eager mode, the celery machinery hides all exceptions inside "celery tasks" (you can find out about them on the task handle somehow). Currently much better to propagate them straight up, so they're visible on the console. --- mediagoblin/celery_setup/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mediagoblin/celery_setup/__init__.py b/mediagoblin/celery_setup/__init__.py index 1a77cc62..d4f25b07 100644 --- a/mediagoblin/celery_setup/__init__.py +++ b/mediagoblin/celery_setup/__init__.py @@ -140,6 +140,7 @@ def setup_celery_from_config(app_config, global_config, if force_celery_always_eager: celery_settings['CELERY_ALWAYS_EAGER'] = True + celery_settings['CELERY_EAGER_PROPAGATES_EXCEPTIONS'] = True __import__(settings_module) this_module = sys.modules[settings_module] From d5e90fe4b487e86261331fab0f7c08f12462625d Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Fri, 3 Jun 2011 18:43:08 -0500 Subject: [PATCH 10/11] Find out if such a slug exists via a query instead of via a .save() call --- mediagoblin/edit/views.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 7df47b18..5cfb2297 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -34,18 +34,25 @@ def edit_media(request, media): description = media['description']) if request.method == 'POST' and form.validate(): - media['title'] = request.POST['title'] - media['description'] = request.POST['description'] - media['slug'] = request.POST['slug'] - try: - media.save() - except Exception as e: - return exc.HTTPConflict(detail = str(e)) + # Make sure there isn't already a MediaEntry with such a slug + # and userid. + existing_user_slug_entries = request.db.MediaEntry.find( + {'slug': request.POST['slug'], + 'uploader': media['uploader'], + '_id': {'$ne': media['_id']}}).count() + + if existing_user_slug_entries: + 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['description'] + media['slug'] = request.POST['slug'] - # redirect - return exc.HTTPFound( - location=request.urlgen("mediagoblin.user_pages.media_home", - user=media.uploader()['username'], media=media['_id'])) + # redirect + return exc.HTTPFound( + location=request.urlgen("mediagoblin.user_pages.media_home", + user=media.uploader()['username'], media=media['_id'])) # render template = request.template_env.get_template( From c042bc6e218e99f93c1e642b1d77839ca0f4b1f9 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Fri, 3 Jun 2011 18:47:22 -0500 Subject: [PATCH 11/11] Title first and foremost... anything else looks weird to me. --- mediagoblin/templates/mediagoblin/user_pages/media.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 406fd3f6..200f13cd 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -20,11 +20,11 @@ {# temporarily, an "image gallery" that isn't one really ;) #} {% if media %} -

{{media.title}}

+

{{ media.description }}

Uploaded on {{ "%4d-%02d-%02d"|format(media.created.year,