Merge remote branch 'remotes/gandaro/324-bad-media-types'
authorChristopher Allan Webber <cwebber@dustycloud.org>
Mon, 6 Jun 2011 12:51:17 +0000 (07:51 -0500)
committerChristopher Allan Webber <cwebber@dustycloud.org>
Mon, 6 Jun 2011 12:51:17 +0000 (07:51 -0500)
30 files changed:
docs/hackinghowto.rst
mediagoblin/app.py
mediagoblin/auth/forms.py
mediagoblin/auth/lib.py
mediagoblin/auth/routing.py
mediagoblin/auth/views.py
mediagoblin/celery_setup/__init__.py
mediagoblin/celery_setup/from_tests.py [new file with mode: 0644]
mediagoblin/decorators.py
mediagoblin/edit/__init__.py [new file with mode: 0644]
mediagoblin/edit/forms.py [new file with mode: 0644]
mediagoblin/edit/lib.py [new file with mode: 0644]
mediagoblin/edit/routing.py [new file with mode: 0644]
mediagoblin/edit/views.py [new file with mode: 0644]
mediagoblin/routing.py
mediagoblin/submit/routing.py
mediagoblin/submit/views.py
mediagoblin/templates/mediagoblin/auth/login.html
mediagoblin/templates/mediagoblin/edit/edit.html [new file with mode: 0644]
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/templates/mediagoblin/utils/pagination.html
mediagoblin/tests/mgoblin_test_app.ini [new file with mode: 0644]
mediagoblin/tests/test_auth.py
mediagoblin/tests/test_tests.py [new file with mode: 0644]
mediagoblin/tests/tools.py [new file with mode: 0644]
mediagoblin/user_pages/routing.py
mediagoblin/user_pages/views.py
mediagoblin/util.py
mediagoblin/views.py
setup.py

index a56498bb81d3b1a37d028d81666697e5cc208b17..a9aadb622676845c5f671ebd7344da1b05e8c68a 100644 (file)
@@ -152,7 +152,7 @@ Running the test suite
 
 Run::
 
-    ./bin/nosetests
+    CELERY_CONFIG_MODULE=mediagoblin.celery_setup.from_tests ./bin/nosetests
 
 
 Running a shell
index 714404deeef23d31b5fc6fdca3a41b2d29f7f6fc..e5949531bebcb7cda73fe9650d2a764169c42a60 100644 (file)
@@ -139,12 +139,13 @@ def paste_app_factory(global_config, **app_config):
         raise ImproperlyConfigured(
             "One of direct_remote_path or direct_remote_paths must be provided")
 
-    if asbool(os.environ.get('CELERY_ALWAYS_EAGER')):
-        setup_celery_from_config(
-            app_config, global_config,
-            force_celery_always_eager=True)
-    else:
-        setup_celery_from_config(app_config, global_config)
+    if not asbool(app_config.get('celery_setup_elsewhere')):
+        if asbool(os.environ.get('CELERY_ALWAYS_EAGER')):
+            setup_celery_from_config(
+                app_config, global_config,
+                force_celery_always_eager=True)
+        else:
+            setup_celery_from_config(app_config, global_config)
 
     mgoblin_app = MediaGoblinApp(
         connection, db,
index db8aaceb4351f5f54e15df2a266737a76f115adb..7bc0aeb1325bef118ca83b9107bad7ecb3b876b2 100644 (file)
@@ -27,7 +27,9 @@ class RegistrationForm(wtforms.Form):
         'Password',
         [wtforms.validators.Required(),
          wtforms.validators.Length(min=6, max=30),
-         wtforms.validators.EqualTo('confirm_password')])
+         wtforms.validators.EqualTo(
+                'confirm_password',
+                'Passwords must match.')])
     confirm_password = wtforms.PasswordField(
         'Confirm password',
         [wtforms.validators.Required()])
index 7cf021bc63e976f1103fafb26d2c53e8bcb9ef89..f40e560f759fe3f19678b223ece4ade391b37905 100644 (file)
@@ -19,6 +19,9 @@ import random
 
 import bcrypt
 
+from mediagoblin.util import send_email, render_template
+from mediagoblin import globals as mgoblin_globals
+
 
 def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
     """
@@ -84,3 +87,37 @@ def fake_login_attempt():
     randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
 
     randplus_stored_hash == randplus_hashed_pass
+
+
+EMAIL_VERIFICATION_TEMPLATE = (
+    u"http://{host}{uri}?"
+    u"userid={userid}&token={verification_key}")
+
+def send_verification_email(user, request):
+    """
+    Send the verification email to users to activate their accounts.
+
+    Args:
+    - user: a user object
+    - request: the request 
+    """
+    rendered_email = render_template(
+        request, 'mediagoblin/auth/verification_email.txt',
+        {'username': user['username'],
+         'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
+                host=request.host,
+                uri=request.urlgen('mediagoblin.auth.verify_email'),
+                userid=unicode(user['_id']),
+                verification_key=user['verification_key'])})
+
+    # TODO: There is no error handling in place
+    send_email(
+        mgoblin_globals.email_sender_address,
+        [user['email']],
+        # TODO
+        # Due to the distributed nature of GNU MediaGoblin, we should
+        # find a way to send some additional information about the 
+        # specific GNU MediaGoblin instance in the subject line. For 
+        # example "GNU MediaGoblin @ Wandborg - [...]".   
+        'GNU MediaGoblin - Verify your email!',
+        rendered_email)
index a8909fbb615848a75de2a82cd9ca8e67cf4a3255..46c585d2c30944baa29c42ce5c22771af1571309 100644 (file)
@@ -20,7 +20,8 @@ auth_routes = [
     Route('mediagoblin.auth.register', '/register/',
           controller='mediagoblin.auth.views:register'),
     Route('mediagoblin.auth.register_success', '/register/success/',
-          controller='mediagoblin.auth.views:register_success'),
+          template='mediagoblin/auth/register_success.html',
+          controller='mediagoblin.views:simple_template_render'),
     Route('mediagoblin.auth.login', '/login/',
           controller='mediagoblin.auth.views:login'),
     Route('mediagoblin.auth.logout', '/logout/',
@@ -28,9 +29,11 @@ auth_routes = [
     Route('mediagoblin.auth.verify_email', '/verify_email/',
           controller='mediagoblin.auth.views:verify_email'),
     Route('mediagoblin.auth.verify_email_notice', '/verification_required/',
-          controller='mediagoblin.auth.views:verify_email_notice'),
+          template='mediagoblin/auth/verification_needed.html',
+          controller='mediagoblin.views:simple_template_render'),
     Route('mediagoblin.auth.resend_verification', '/resend_verification/',
           controller='mediagoblin.auth.views:resend_activation'),
     Route('mediagoblin.auth.resend_verification_success',
           '/resend_verification_success/',
-          controller='mediagoblin.auth.views:resend_activation_success')]
+          template='mediagoblin/auth/resent_verification_email.html',
+          controller='mediagoblin.views:simple_template_render')]
index e9d75f8bba75e5e1fbd005bb7b9f084a4fec829f..1d00f382448de00c8113a9366e71914792f3b95a 100644 (file)
 
 import uuid
 
-from webob import Response, exc
+from webob import exc
 
+from mediagoblin.util import render_to_response, redirect
 from mediagoblin.db.util import ObjectId
 from mediagoblin.auth import lib as auth_lib
 from mediagoblin.auth import forms as auth_forms
-from mediagoblin.util import send_email
-from mediagoblin import globals as mgoblin_globals
+from mediagoblin.auth.lib import send_verification_email
 
 
 def register(request):
@@ -52,46 +52,14 @@ def register(request):
                 request.POST['password'])
             entry.save(validate=True)
             
-            email_template = request.template_env.get_template(
-                'mediagoblin/auth/verification_email.txt')
-
-            # TODO: There is no error handling in place
-            send_email(
-                mgoblin_globals.email_sender_address,
-                [entry['email']],
-                # TODO
-                # Due to the distributed nature of GNU MediaGoblin, we should
-                # find a way to send some additional information about the 
-                # specific GNU MediaGoblin instance in the subject line. For 
-                # example "GNU MediaGoblin @ Wandborg - [...]".   
-                'GNU MediaGoblin - Verify email',
-                email_template.render(
-                    username=entry['username'],
-                    verification_url='http://{host}{uri}?userid={userid}&token={verification_key}'.format(
-                        host=request.host,
-                        uri=request.urlgen('mediagoblin.auth.verify_email'),
-                        userid=unicode(entry['_id']),
-                        verification_key=entry['verification_key'])))
-            
-            # Redirect to register_success
-            return exc.HTTPFound(
-                location=request.urlgen("mediagoblin.auth.register_success"))
-
-    # render
-    template = request.template_env.get_template(
-        'mediagoblin/auth/register.html')
-    return Response(
-        template.render(
-            {'request': request,
-             'register_form': register_form}))
+            send_verification_email(entry, request)
 
+            return redirect(request, "mediagoblin.auth.register_success")
 
-def register_success(request):
-    template = request.template_env.get_template(
-        'mediagoblin/auth/register_success.html')
-    return Response(
-        template.render(
-            {'request': request}))
+    return render_to_response(
+        request,
+        'mediagoblin/auth/register.html',
+        {'register_form': register_form})
 
 
 def login(request):
@@ -116,8 +84,7 @@ def login(request):
             if request.POST.get('next'):
                 return exc.HTTPFound(location=request.POST['next'])
             else:
-                return exc.HTTPFound(
-                    location=request.urlgen("index"))
+                return redirect(request, "index")
 
         else:
             # Prevent detecting who's on this system by testing login
@@ -125,23 +92,19 @@ def login(request):
             auth_lib.fake_login_attempt()
             login_failed = True
 
-    # render
-    template = request.template_env.get_template(
-        'mediagoblin/auth/login.html')
-    return Response(
-        template.render(
-            {'request': request,
-             'login_form': login_form,
-             'next': request.GET.get('next') or request.POST.get('next'),
-             'login_failed': login_failed}))
+    return render_to_response(
+        request,
+        'mediagoblin/auth/login.html',
+        {'login_form': login_form,
+         'next': request.GET.get('next') or request.POST.get('next'),
+         'login_failed': login_failed})
 
 
 def logout(request):
     # Maybe deleting the user_id parameter would be enough?
     request.session.delete()
     
-    return exc.HTTPFound(
-        location=request.urlgen("index"))
+    return redirect(request, "index")
 
 
 def verify_email(request):
@@ -158,8 +121,6 @@ def verify_email(request):
     user = request.db.User.find_one(
         {'_id': ObjectId(unicode(request.GET['userid']))})
 
-    verification_successful = bool
-
     if user and user['verification_key'] == unicode(request.GET['token']):
         user['status'] = u'active'
         user['email_verified'] = True
@@ -168,27 +129,11 @@ def verify_email(request):
     else:
         verification_successful = False
         
-    template = request.template_env.get_template(
-        'mediagoblin/auth/verify_email.html')
-    return Response(
-        template.render(
-            {'request': request,
-             'user': user,
-             'verification_successful': verification_successful}))
-
-def verify_email_notice(request):
-    """
-    Verify warning view.
-
-    When the user tries to do some action that requires their account
-    to be verified beforehand, this view is called upon!
-    """
-
-    template = request.template_env.get_template(
-        'mediagoblin/auth/verification_needed.html')
-    return Response(
-        template.render(
-            {'request': request}))
+    return render_to_response(
+        request,
+        'mediagoblin/auth/verify_email.html',
+        {'user': user,
+         'verification_successful': verification_successful})
 
 
 def resend_activation(request):
@@ -200,36 +145,6 @@ def resend_activation(request):
     request.user['verification_key'] = unicode(uuid.uuid4())
     request.user.save()
 
-    # Copied shamelessly from the register view above.
-
-    email_template = request.template_env.get_template(
-        'mediagoblin/auth/verification_email.txt')
-
-    # TODO: There is no error handling in place
-    send_email(
-        mgoblin_globals.email_sender_address,
-        [request.user['email']],
-        # TODO
-        # Due to the distributed nature of GNU MediaGoblin, we should
-        # find a way to send some additional information about the 
-        # specific GNU MediaGoblin instance in the subject line. For 
-        # example "GNU MediaGoblin @ Wandborg - [...]".   
-        'GNU MediaGoblin - Verify email',
-        email_template.render(
-            username=request.user['username'],
-            verification_url='http://{host}{uri}?userid={userid}&token={verification_key}'.format(
-                host=request.host,
-                uri=request.urlgen('mediagoblin.auth.verify_email'),
-                userid=unicode(request.user['_id']),
-                verification_key=request.user['verification_key'])))
-
-    return exc.HTTPFound(
-        location=request.urlgen('mediagoblin.auth.resend_verification_success'))
-
-
-def resend_activation_success(request):
-    template = request.template_env.get_template(
-        'mediagoblin/auth/resent_verification_email.html')
-    return Response(
-        template.render(
-            {'request': request}))
+    send_verification_email(request.user, request)
+
+    return redirect(request, 'mediagoblin.auth.resend_verification_success')
index 1a77cc6242ede3afe6cb97385467c55e62f83c34..d4f25b079ccfc916a9c58555151bd1108a974deb 100644 (file)
@@ -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]
diff --git a/mediagoblin/celery_setup/from_tests.py b/mediagoblin/celery_setup/from_tests.py
new file mode 100644 (file)
index 0000000..fe7d731
--- /dev/null
@@ -0,0 +1,43 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+import os
+
+from mediagoblin.tests.tools import TEST_APP_CONFIG
+from mediagoblin import util
+from mediagoblin.celery_setup import setup_celery_from_config
+from mediagoblin.globals import setup_globals
+
+
+OUR_MODULENAME = 'mediagoblin.celery_setup.from_tests'
+
+
+def setup_self(setup_globals_func=setup_globals):
+    """
+    Set up celery for testing's sake, which just needs to set up
+    celery and celery only.
+    """
+    mgoblin_conf = util.read_config_file(TEST_APP_CONFIG)
+    mgoblin_section = mgoblin_conf['app:mediagoblin']
+
+    setup_celery_from_config(
+        mgoblin_section, mgoblin_conf,
+        settings_module=OUR_MODULENAME,
+        set_environ=False)
+
+
+if os.environ.get('CELERY_CONFIG_MODULE') == OUR_MODULENAME:
+    setup_self()
index a5bede54c4439d23eaeec7bfbef2d366740e6cf8..c2fe3f9fdbf0bd7db962e197589b0f1c6e8aadfc 100644 (file)
@@ -18,6 +18,7 @@
 from bson.errors import InvalidId
 from webob import exc
 
+from mediagoblin.util import redirect
 from mediagoblin.db.util import ObjectId
 
 
@@ -38,9 +39,8 @@ def require_active_login(controller):
     def new_controller_func(request, *args, **kwargs):
         if request.user and \
                 request.user.get('status') == u'needs_email_verification':
-            return exc.HTTPFound(
-                location = request.urlgen(
-                    'mediagoblin.auth.verify_email_notice'))
+            return redirect(request,
+                    'mediagoblin.auth.verify_email_notice')
         elif not request.user or request.user.get('status') != u'active':
             return exc.HTTPFound(
                 location="%s?next=%s" % (
@@ -102,3 +102,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 (file)
index 0000000..e69de29
diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py
new file mode 100644 (file)
index 0000000..ea25141
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+
+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/lib.py b/mediagoblin/edit/lib.py
new file mode 100644 (file)
index 0000000..2a81034
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+
+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/routing.py b/mediagoblin/edit/routing.py
new file mode 100644 (file)
index 0000000..bf0b249
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+
+from routes.route import Route
+
+edit_routes = [
+    # Media editing view handled in user_pages/routing.py
+]
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
new file mode 100644 (file)
index 0000000..c5f0f43
--- /dev/null
@@ -0,0 +1,61 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+
+from webob import exc
+
+from mediagoblin.util import render_to_response, redirect
+from mediagoblin.edit import forms
+from mediagoblin.edit.lib import may_edit_media
+from mediagoblin.decorators import require_active_login, get_user_media_entry
+
+
+@get_user_media_entry
+@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'],
+        description = media['description'])
+
+    if request.method == 'POST' and form.validate():
+        # 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']
+            media.save()
+
+            return redirect(request, "mediagoblin.user_pages.media_home",
+                user=media.uploader()['username'], media=media['slug'])
+
+    return render_to_response(
+        request,
+        'mediagoblin/edit/edit.html',
+        {'media': media,
+         'form': form})
index 356ef678853f2ded12374793dfcfadb131669465..b854c85a5d3dcdf40393c095922011a257902f4d 100644 (file)
@@ -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
index cff28acb2f48b37cbb4cc7fc9954f08ffe178f24..3edbab707126304fcb611191eb6a16b68629d1c1 100644 (file)
@@ -20,5 +20,5 @@ submit_routes = [
     Route('mediagoblin.submit.start', '/',
           controller='mediagoblin.submit.views:submit_start'),
     Route('mediagoblin.submit.success', '/success/',
-          controller='mediagoblin.submit.views:submit_success'),
-    ]
+          template='mediagoblin/submit/success.html',
+          controller='mediagoblin.views:simple_template_render')]
index 5ddf992fd8ca6e0f438d8f3f7a346f639c1e8a37..e9b5c37e0c4b4af2168141e7430eeaee114309b2 100644 (file)
@@ -17,9 +17,9 @@
 from os.path import splitext
 from cgi import FieldStorage
 
-from webob import Response, exc
 from werkzeug.utils import secure_filename
 
+from mediagoblin.util import render_to_response, redirect
 from mediagoblin.decorators import require_active_login
 from mediagoblin.submit import forms as submit_forms, security
 from mediagoblin.process_media import process_media_initial
@@ -78,23 +78,14 @@ def submit_start(request):
             # queue it for processing
             process_media_initial.delay(unicode(entry['_id']))
 
-            # redirect
-            return exc.HTTPFound(
-                location=request.urlgen("mediagoblin.submit.success"))
+            return redirect(request, "mediagoblin.submit.success")
 
-    # render
-    template = request.template_env.get_template(
-        'mediagoblin/submit/start.html')
-    return Response(
-        template.render(
-            {'request': request,
-             'submit_form': submit_form}))
+    return render_to_response(
+        request,
+        'mediagoblin/submit/start.html',
+        {'submit_form': submit_form})
 
 
 def submit_success(request):
-    # render
-    template = request.template_env.get_template(
-        'mediagoblin/submit/success.html')
-    return Response(
-        template.render(
-            {'request': request}))
+    return render_to_response(
+        request, 'mediagoblin/submit/success.html', {})
index 22a57b70f94fa89489c59c59e876f311c2233895..c2e27c1593a47dd24812cdcafa8935a49cc416cb 100644 (file)
@@ -33,7 +33,8 @@
         <input type="submit" value="submit" class="button"/>
       </div>
       {% if next %}
-        <input type="hidden" name="next" value="{{ next }}" class="button" />
+        <input type="hidden" name="next" value="{{ next }}" class="button"
+               style="display: none;"/>
       {% endif %}
       <p>Don't have an account yet? <a href="{{ request.urlgen('mediagoblin.auth.register') }}">Create one here!</a></p>
     </div>
diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html
new file mode 100644 (file)
index 0000000..295d57e
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+  <h1>Edit details for {{ media.title }}</h1>
+
+  <form action="{{ request.urlgen('mediagoblin.edit.edit_media',
+                               user= media.uploader().username,
+                               media= media._id) }}"
+        method="POST" enctype="multipart/form-data">
+    <div class="submit_box form_box">
+      {{ wtforms_util.render_divs(form) }}
+      <div class="form_submit_buttons">
+        <input type="submit" value="submit" class="button" />
+      </div>
+    </div>
+  </form>
+  <img src="{{ request.app.public_store.file_url(
+         media['media_files']['thumb']) }}" />
+{% endblock %}    
index 886962d1f4bfbba5ece1fc6385f9ffdf82d54cd2..200f13cdda64a2de7cf590c3e85d4cc6a2081325 100644 (file)
 
   {# temporarily, an "image gallery" that isn't one really ;) #}
   {% if media %}
-    <img class="media_image" src="{{ request.app.public_store.file_url(
-                  media.media_files.main) }}" />
     <h1>
       {{media.title}}
     </h1>
+    <img class="media_image" src="{{ request.app.public_store.file_url(
+                  media.media_files.main) }}" />
     <p>{{ media.description }}</p>
     <p>Uploaded on
     {{ "%4d-%02d-%02d"|format(media.created.year,
@@ -33,6 +33,9 @@
     <a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
                                user= media.uploader().username) }}">
       {{- media.uploader().username }}</a></p>
+    <p><a href="{{ request.urlgen('mediagoblin.edit.edit_media',
+                               user= media.uploader().username,
+                               media= media._id) }}">Edit</a></p>
   {% else %}
   <p>Sorry, no such media found.<p/>
   {% endif %}
index 5ca5e09bc8f0d95fee3b54462ff1abb45492294d..62e8af911a63679f34289339579e672aae0d53bd 100644 (file)
@@ -32,7 +32,7 @@
             <strong>{{ page }}</strong>
           {% endif %}
         {% else %}
-          <span class=ellipsis>…</span>
+          <span class="ellipsis">…</span>
         {% endif %}
       {%- endfor %}
 
diff --git a/mediagoblin/tests/mgoblin_test_app.ini b/mediagoblin/tests/mgoblin_test_app.ini
new file mode 100644 (file)
index 0000000..abed261
--- /dev/null
@@ -0,0 +1,46 @@
+[DEFAULT]
+debug = true
+
+[composite:main]
+use = egg:Paste#urlmap
+/ = mediagoblin
+/mgoblin_media/ = publicstore_serve
+/mgoblin_static/ = mediagoblin_static
+
+[app:mediagoblin]
+use = egg:mediagoblin#app
+filter-with = beaker
+queuestore_base_dir = %(here)s/test_user_dev/media/queue
+publicstore_base_dir = %(here)s/test_user_dev/media/public
+publicstore_base_url = /mgoblin_media/
+direct_remote_path = /mgoblin_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+db_name = __mediagoblin_tests__
+# Celery shouldn't be set up by the paste app factory as it's set up
+# elsewhere
+celery_setup_elsewhere = true
+
+[app:publicstore_serve]
+use = egg:Paste#static
+document_root = %(here)s/user_dev/media/public
+
+[app:mediagoblin_static]
+use = egg:Paste#static
+document_root = %(here)s/mediagoblin/static/
+
+[filter:beaker]
+use = egg:Beaker#beaker_session
+cache_dir = %(here)s/test_user_dev/beaker
+beaker.session.key = mediagoblin
+# beaker.session.secret = somesupersecret
+beaker.session.data_dir = %(here)s/test_user_dev/beaker/sessions/data
+beaker.session.lock_dir = %(here)s/test_user_dev/beaker/sessions/lock
+
+[celery]
+celery_always_eager = true
+
+[server:main]
+use = egg:Paste#http
+host = 127.0.0.1
+port = 6543
index 94ce6bbacd0cc6f09d20aecf5d6f371cfa9a7883..cdfeccabbc9fa462c647844e1a28fdb82693baa6 100644 (file)
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import urlparse
+
+from nose.tools import assert_equal
 
 from mediagoblin.auth import lib as auth_lib
+from mediagoblin.tests.tools import setup_fresh_app
+from mediagoblin import globals as mgoblin_globals
+from mediagoblin import util
 
 
 ########################
@@ -57,3 +63,169 @@ def test_bcrypt_gen_password_hash():
         pw, hashed_pw, '3><7R45417')
     assert not auth_lib.bcrypt_check_password(
         'notthepassword', hashed_pw, '3><7R45417')
+
+
+@setup_fresh_app
+def test_register_views(test_app):
+    """
+    Massive test function that all our registration-related views all work.
+    """
+    # Test doing a simple GET on the page
+    # -----------------------------------
+
+    test_app.get('/auth/register/')
+    # Make sure it rendered with the appropriate template
+    assert util.TEMPLATE_TEST_CONTEXT.has_key(
+        'mediagoblin/auth/register.html')
+    
+    # Try to register without providing anything, should error
+    # --------------------------------------------------------
+
+    util.clear_test_template_context()
+    test_app.post(
+        '/auth/register/', {})
+    context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+    form = context['register_form']
+    assert form.username.errors == [u'This field is required.']
+    assert form.password.errors == [u'This field is required.']
+    assert form.confirm_password.errors == [u'This field is required.']
+    assert form.email.errors == [u'This field is required.']
+
+    # Try to register with fields that are known to be invalid
+    # --------------------------------------------------------
+
+    ## too short
+    util.clear_test_template_context()
+    test_app.post(
+        '/auth/register/', {
+            'username': 'l',
+            'password': 'o',
+            'confirm_password': 'o',
+            'email': 'l'})
+    context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+    form = context['register_form']
+
+    assert form.username.errors == [
+        u'Field must be between 3 and 30 characters long.']
+    assert form.password.errors == [
+        u'Field must be between 6 and 30 characters long.']
+
+    ## bad form
+    util.clear_test_template_context()
+    test_app.post(
+        '/auth/register/', {
+            'username': '@_@',
+            'email': 'lollerskates'})
+    context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+    form = context['register_form']
+
+    assert form.username.errors == [
+        u'Invalid input.']
+    assert form.email.errors == [
+        u'Invalid email address.']
+
+    ## mismatching passwords
+    util.clear_test_template_context()
+    test_app.post(
+        '/auth/register/', {
+            'password': 'herpderp',
+            'confirm_password': 'derpherp'})
+    context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+    form = context['register_form']
+
+    assert form.password.errors == [
+        u'Passwords must match.']
+
+    ## At this point there should be no users in the database ;)
+    assert not mgoblin_globals.database.User.find().count()
+
+    # Successful register
+    # -------------------
+    util.clear_test_template_context()
+    response = test_app.post(
+        '/auth/register/', {
+            'username': 'happygirl',
+            'password': 'iamsohappy',
+            'confirm_password': 'iamsohappy',
+            'email': 'happygrrl@example.org'})
+    response.follow()
+
+    ## Did we redirect to the proper page?  Use the right template?
+    assert_equal(
+        urlparse.urlsplit(response.location)[2],
+        '/auth/register/success/')
+    assert util.TEMPLATE_TEST_CONTEXT.has_key(
+        'mediagoblin/auth/register_success.html')
+
+    ## Make sure user is in place
+    new_user = mgoblin_globals.database.User.find_one(
+        {'username': 'happygirl'})
+    assert new_user
+    assert new_user['status'] == u'needs_email_verification'
+    assert new_user['email_verified'] == False
+
+    ## Make sure we get email confirmation, and try verifying
+    assert len(util.EMAIL_TEST_INBOX) == 1
+    message = util.EMAIL_TEST_INBOX.pop()
+    assert message['To'] == 'happygrrl@example.org'
+    email_context = util.TEMPLATE_TEST_CONTEXT[
+        'mediagoblin/auth/verification_email.txt']
+    assert email_context['verification_url'] in message.get_payload(decode=True)
+
+    path = urlparse.urlsplit(email_context['verification_url'])[2]
+    get_params = urlparse.urlsplit(email_context['verification_url'])[3]
+    assert path == u'/auth/verify_email/'
+    parsed_get_params = urlparse.parse_qs(get_params)
+
+    ### user should have these same parameters
+    assert parsed_get_params['userid'] == [
+        unicode(new_user['_id'])]
+    assert parsed_get_params['token'] == [
+        new_user['verification_key']]
+    
+    ## Try verifying with bs verification key, shouldn't work
+    util.clear_test_template_context()
+    test_app.get(
+        "/auth/verify_email/?userid=%s&token=total_bs" % unicode(
+            new_user['_id']))
+    context = util.TEMPLATE_TEST_CONTEXT[
+        'mediagoblin/auth/verify_email.html']
+    assert context['verification_successful'] == False
+    new_user = mgoblin_globals.database.User.find_one(
+        {'username': 'happygirl'})
+    assert new_user
+    assert new_user['status'] == u'needs_email_verification'
+    assert new_user['email_verified'] == False
+
+    ## Verify the email activation works
+    util.clear_test_template_context()
+    test_app.get("%s?%s" % (path, get_params))
+    context = util.TEMPLATE_TEST_CONTEXT[
+        'mediagoblin/auth/verify_email.html']
+    assert context['verification_successful'] == True
+    new_user = mgoblin_globals.database.User.find_one(
+        {'username': 'happygirl'})
+    assert new_user
+    assert new_user['status'] == u'active'
+    assert new_user['email_verified'] == True
+
+    ## TODO: Try logging in
+    
+    # Uniqueness checks
+    # -----------------
+    ## We shouldn't be able to register with that user twice
+    util.clear_test_template_context()
+    response = test_app.post(
+        '/auth/register/', {
+            'username': 'happygirl',
+            'password': 'iamsohappy2',
+            'confirm_password': 'iamsohappy2',
+            'email': 'happygrrl2@example.org'})
+    
+    context = util.TEMPLATE_TEST_CONTEXT[
+        'mediagoblin/auth/register.html']
+    form = context['register_form']
+    assert form.username.errors == [
+        u'Sorry, a user with that name already exists.']
+
+    ## TODO: Also check for double instances of an email address?
diff --git a/mediagoblin/tests/test_tests.py b/mediagoblin/tests/test_tests.py
new file mode 100644 (file)
index 0000000..3ecbfac
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tests.tools import get_test_app
+
+from mediagoblin import globals as mgoblin_globals
+
+
+def test_get_test_app_wipes_db():
+    """
+    Make sure we get a fresh database on every wipe :)
+    """
+    get_test_app()
+    assert mgoblin_globals.database.User.find().count() == 0
+
+    new_user = mgoblin_globals.database.User()
+    new_user['username'] = u'lolcat'
+    new_user['email'] = u'lol@cats.example.org'
+    new_user['pw_hash'] = u'pretend_this_is_a_hash'
+    new_user.save()
+    assert mgoblin_globals.database.User.find().count() == 1
+
+    get_test_app()
+
+    assert mgoblin_globals.database.User.find().count() == 0
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
new file mode 100644 (file)
index 0000000..342b54b
--- /dev/null
@@ -0,0 +1,109 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+
+import pkg_resources
+import os, shutil
+
+from paste.deploy import appconfig, loadapp
+from webtest import TestApp
+
+from mediagoblin import util
+from mediagoblin.decorators import _make_safe
+from mediagoblin.db.open import setup_connection_and_db_from_config
+
+
+MEDIAGOBLIN_TEST_DB_NAME = '__mediagoblinunittests__'
+TEST_APP_CONFIG = pkg_resources.resource_filename(
+    'mediagoblin.tests', 'mgoblin_test_app.ini')
+TEST_USER_DEV = pkg_resources.resource_filename(
+    'mediagoblin.tests', 'test_user_dev')
+MGOBLIN_APP = None
+
+USER_DEV_DIRECTORIES_TO_SETUP = [
+    'media/public', 'media/queue',
+    'beaker/sessions/data', 'beaker/sessions/lock']
+
+
+class BadCeleryEnviron(Exception): pass
+
+
+def get_test_app(dump_old_app=True):
+    if not os.environ.get('CELERY_CONFIG_MODULE') == \
+            'mediagoblin.celery_setup.from_tests':
+        raise BadCeleryEnviron(
+            u"Sorry, you *absolutely* must run nosetests with the\n"
+            u"mediagoblin.celery_setup.from_tests module.  Like so:\n"
+            u"$ CELERY_CONFIG_MODULE=mediagoblin.celery_setup.from_tests ./bin/nosetests")
+
+    # Just return the old app if that exists and it's okay to set up
+    # and return
+    if MGOBLIN_APP and not dump_old_app:
+        return MGOBLIN_APP
+
+    # Remove and reinstall user_dev directories
+    if os.path.exists(TEST_USER_DEV):
+        shutil.rmtree(TEST_USER_DEV)
+
+    for directory in USER_DEV_DIRECTORIES_TO_SETUP:
+        full_dir = os.path.join(TEST_USER_DEV, directory)
+        os.makedirs(full_dir)
+
+    # Get app config
+    config = appconfig(
+        'config:' + os.path.basename(TEST_APP_CONFIG),
+        relative_to=os.path.dirname(TEST_APP_CONFIG),
+        name='mediagoblin')
+
+    # Wipe database
+    # @@: For now we're dropping collections, but we could also just
+    # collection.remove() ?
+    connection, db = setup_connection_and_db_from_config(
+        config.local_conf)
+
+    collections_to_wipe = [
+        collection
+        for collection in db.collection_names()
+        if not collection.startswith('system.')]
+
+    for collection in collections_to_wipe:
+        db.drop_collection(collection)
+
+    # Don't need these anymore...
+    del(connection)
+    del(db)
+
+    # TODO: Drop and recreate indexes
+
+    # setup app and return
+    test_app = loadapp(
+        'config:' + TEST_APP_CONFIG)
+
+    return TestApp(test_app)
+
+
+def setup_fresh_app(func):
+    """
+    Decorator to setup a fresh test application for this function.
+
+    Cleans out test buckets and passes in a new, fresh test_app.
+    """
+    def wrapper(*args, **kwargs):
+        test_app = get_test_app()
+        util.clear_test_buckets()
+        return func(test_app, *args, **kwargs)
+
+    return _make_safe(wrapper, func)
index 96f97427273b1d495684685457b00b0830cbf446..c5e9a984b16b846d75004026525cb3e657d6e1e7 100644 (file)
@@ -22,5 +22,7 @@ user_routes = [
     Route('mediagoblin.user_pages.media_home', '/{user}/m/{media}/',
         requirements=dict(m_id="[0-9a-fA-F]{24}"),
         controller="mediagoblin.user_pages.views:media_home"),
+    Route('mediagoblin.edit.edit_media', "/{user}/m/{media}/edit/",
+        controller="mediagoblin.edit.views:edit_media"),
     Route('mediagoblin.user_pages.atom_feed', '/{user}/atom/',
         controller="mediagoblin.user_pages.views:atom_feed")]
index cc9c7b211c71dd55258d8a6beb39473009123713..323c3e548dab3ed17fef16cb18a56db985235485 100644 (file)
@@ -14,9 +14,9 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from webob import Response, exc
+from webob import exc
 from mediagoblin.db.util import DESCENDING
-from mediagoblin.util import Pagination
+from mediagoblin.util import Pagination, render_to_response
 
 from mediagoblin.decorators import uses_pagination, get_user_media_entry
 
@@ -42,26 +42,22 @@ def user_home(request, page):
     if media_entries == None:
         return exc.HTTPNotFound()
     
-    template = request.template_env.get_template(
-        'mediagoblin/user_pages/user.html')
-
-    return Response(
-        template.render(
-            {'request': request,
-             'user': user,
-             'media_entries': media_entries,
-             'pagination': pagination}))
+    return render_to_response(
+        request,
+        'mediagoblin/user_pages/user.html',
+        {'user': user,
+         'media_entries': media_entries,
+         'pagination': pagination})
 
 
 @get_user_media_entry
 def media_home(request, media):
     """'Homepage' of a MediaEntry()"""
-    template = request.template_env.get_template(
-        'mediagoblin/user_pages/media.html')
-    return Response(
-        template.render(
-            {'request': request,
-             'media': media}))
+    return render_to_response(
+        request,
+        'mediagoblin/user_pages/media.html',
+        {'media': media})
+
 
 ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 5
 
index 2865cf11504f39bec4271f88bca9fe0183fa3c00..64e21ca99e982c2d949bd0f7a8ab3b8e59ebfabe 100644 (file)
@@ -18,17 +18,22 @@ from email.MIMEText import MIMEText
 import gettext
 import pkg_resources
 import smtplib
+import os
 import sys
 import re
+import urllib
+from math import ceil
+import copy
+
+from babel.localedata import exists
 import jinja2
-from mediagoblin.db.util import ObjectId
 import translitcodec
+from paste.deploy.loadwsgi import NicerConfigParser
+from webob import Response, exc
 
 from mediagoblin import globals as mgoblin_globals
+from mediagoblin.db.util import ObjectId
 
-import urllib
-from math import ceil
-import copy
 
 TESTS_ENABLED = False
 def _activate_testing():
@@ -39,6 +44,26 @@ def _activate_testing():
     TESTS_ENABLED = True
 
 
+def clear_test_buckets():
+    """
+    We store some things for testing purposes that should be cleared
+    when we want a "clean slate" of information for our next round of
+    tests.  Call this function to wipe all that stuff clean.
+
+    Also wipes out some other things we might redefine during testing,
+    like the jinja envs.
+    """
+    global SETUP_JINJA_ENVS
+    SETUP_JINJA_ENVS = {}
+
+    global EMAIL_TEST_INBOX
+    global EMAIL_TEST_MBOX_INBOX
+    EMAIL_TEST_INBOX = []
+    EMAIL_TEST_MBOX_INBOX = []
+
+    clear_test_template_context()
+
+
 def get_jinja_loader(user_template_path=None):
     """
     Set up the Jinja template loaders, possibly allowing for user
@@ -55,6 +80,9 @@ def get_jinja_loader(user_template_path=None):
         return jinja2.PackageLoader('mediagoblin', 'templates')
 
 
+SETUP_JINJA_ENVS = {}
+
+
 def get_jinja_env(template_loader, locale):
     """
     Set up the Jinja environment, 
@@ -64,6 +92,11 @@ def get_jinja_env(template_loader, locale):
     """
     setup_gettext(locale)
 
+    # If we have a jinja environment set up with this locale, just
+    # return that one.
+    if SETUP_JINJA_ENVS.has_key(locale):
+        return SETUP_JINJA_ENVS[locale]
+
     template_env = jinja2.Environment(
         loader=template_loader, autoescape=True,
         extensions=['jinja2.ext.i18n'])
@@ -72,9 +105,49 @@ def get_jinja_env(template_loader, locale):
         mgoblin_globals.translations.gettext,
         mgoblin_globals.translations.ngettext)
 
+    if exists(locale):
+        SETUP_JINJA_ENVS[locale] = template_env
+
     return template_env
 
 
+# We'll store context information here when doing unit tests
+TEMPLATE_TEST_CONTEXT = {}
+
+
+def render_template(request, template_path, context):
+    """
+    Render a template with context.
+
+    Always inserts the request into the context, so you don't have to.
+    Also stores the context if we're doing unit tests.  Helpful!
+    """
+    template = request.template_env.get_template(
+        template_path)
+    context['request'] = request
+    rendered = template.render(context)
+
+    if TESTS_ENABLED:
+        TEMPLATE_TEST_CONTEXT[template_path] = context
+
+    return rendered
+
+
+def clear_test_template_context():
+    global TEMPLATE_TEST_CONTEXT
+    TEMPLATE_TEST_CONTEXT = {}
+
+
+def render_to_response(request, template, context):
+    """Much like Django's shortcut.render()"""
+    return Response(render_template(request, template, context))
+
+
+def redirect(request, *args, **kwargs):
+    """Returns a HTTPFound(), takes a request and then urlgen params"""
+    return exc.HTTPFound(location=request.urlgen(*args, **kwargs))
+
+
 def setup_user_in_request(request):
     """
     Examine a request and tack on a request.user parameter if that's
@@ -278,6 +351,30 @@ def get_locale_from_request(request):
     return locale_to_lower_upper(target_lang)
 
 
+def read_config_file(conf_file):
+    """
+    Read a paste deploy style config file and process it.
+    """
+    if not os.path.exists(conf_file):
+        raise IOError(
+            "MEDIAGOBLIN_CONFIG not set or file does not exist")
+
+    parser = NicerConfigParser(conf_file)
+    parser.read(conf_file)
+    parser._defaults.setdefault(
+        'here', os.path.dirname(os.path.abspath(conf_file)))
+    parser._defaults.setdefault(
+        '__file__', os.path.abspath(conf_file))
+
+    mgoblin_conf = dict(
+        [(section_name, dict(parser.items(section_name)))
+         for section_name in parser.sections()])
+
+    return mgoblin_conf
+
+
+SETUP_GETTEXTS = {}
+
 def setup_gettext(locale):
     """
     Setup the gettext instance based on this locale
@@ -288,8 +385,13 @@ def setup_gettext(locale):
 
     # TODO: fallback nicely on translations from pt_PT to pt if not
     # available, etc.
-    this_gettext = gettext.translation(
-        'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
+    if SETUP_GETTEXTS.has_key(locale):
+        this_gettext = SETUP_GETTEXTS[locale]
+    else:
+        this_gettext = gettext.translation(
+            'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
+        if exists(locale):
+            SETUP_GETTEXTS[locale] = this_gettext
 
     mgoblin_globals.setup_globals(
         translations=this_gettext)
index dd722c63c4f16d6c105cfefdb96ece5ce244ebdd..5b6d9773d200e3adca58d95fb63a2cc0fc52db4c 100644 (file)
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from webob import Response
+from mediagoblin.util import render_to_response
 from mediagoblin.db.util import DESCENDING
 
 def root_view(request):
     media_entries = request.db.MediaEntry.find(
         {u'state': u'processed'}).sort('created', DESCENDING)
     
-    template = request.template_env.get_template(
-        'mediagoblin/root.html')
-    return Response(
-        template.render(
-            {'request': request,
-             'media_entries': media_entries}))
+    return render_to_response(
+        request, 'mediagoblin/root.html',
+        {'media_entries': media_entries})
+
+
+def simple_template_render(request):
+    """
+    A view for absolutely simple template rendering.
+    Just make sure 'template' is in the matchdict!
+    """
+    template_name = request.matchdict['template']
+    return render_to_response(
+        request, template_name, {})
index 097dd7f29e83b0fd39853b39e727705798d8efbd..46da7276b2e177cfa6666bfaf0eed2030f880628 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -41,6 +41,7 @@ setup(
         'Babel',
         'translitcodec',
         'argparse',
+        'webtest',
         ],
     test_suite='nose.collector',