aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mediagoblin/auth/forms.py51
-rw-r--r--mediagoblin/auth/views.py146
-rw-r--r--mediagoblin/db/open.py13
-rw-r--r--mediagoblin/edit/views.py13
-rw-r--r--mediagoblin/init/__init__.py2
-rw-r--r--mediagoblin/media_types/video/processing.py62
-rw-r--r--mediagoblin/plugins/api/views.py5
-rw-r--r--mediagoblin/processing/task.py49
-rw-r--r--mediagoblin/submit/lib.py45
-rw-r--r--mediagoblin/submit/views.py11
-rw-r--r--mediagoblin/templates/mediagoblin/edit/delete_account.html9
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit_account.html5
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media.html30
-rw-r--r--mediagoblin/tests/test_api.py4
-rw-r--r--mediagoblin/tests/test_auth.py24
-rw-r--r--mediagoblin/tests/test_collections.py37
-rw-r--r--mediagoblin/tests/test_csrf_middleware.py8
-rw-r--r--mediagoblin/tests/test_edit.py4
-rw-r--r--mediagoblin/tests/test_http_callback.py4
-rw-r--r--mediagoblin/tests/test_messages.py4
-rw-r--r--mediagoblin/tests/test_misc.py4
-rw-r--r--mediagoblin/tests/test_oauth.py4
-rw-r--r--mediagoblin/tests/test_submission.py12
-rw-r--r--mediagoblin/tests/test_tags.py4
-rw-r--r--mediagoblin/tests/test_tests.py10
-rw-r--r--mediagoblin/tests/test_workbench.py2
-rw-r--r--mediagoblin/tests/tools.py27
-rw-r--r--mediagoblin/tools/mail.py13
-rw-r--r--mediagoblin/tools/workbench.py (renamed from mediagoblin/workbench.py)4
-rw-r--r--mediagoblin/user_pages/forms.py7
-rw-r--r--mediagoblin/user_pages/routing.py2
-rw-r--r--mediagoblin/user_pages/views.py9
32 files changed, 373 insertions, 251 deletions
diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py
index 0b2bf959..7cae951a 100644
--- a/mediagoblin/auth/forms.py
+++ b/mediagoblin/auth/forms.py
@@ -17,52 +17,75 @@
import wtforms
import re
+from mediagoblin.tools.mail import normalize_email
from mediagoblin.tools.translate import fake_ugettext_passthrough as _
+def normalize_user_or_email_field(allow_email=True, allow_user=True):
+ """Check if we were passed a field that matches a username and/or email pattern
+
+ This is useful for fields that can take either a username or email
+ address. Use the parameters if you want to only allow a username for
+ instance"""
+ message = _(u'Invalid User name or email address.')
+ nomail_msg = _(u"This field does not take email addresses.")
+ nouser_msg = _(u"This field requires an email address.")
+
+ def _normalize_field(form, field):
+ email = u'@' in field.data
+ if email: # normalize email address casing
+ if not allow_email:
+ raise wtforms.ValidationError(nomail_msg)
+ wtforms.validators.Email()(form, field)
+ field.data = normalize_email(field.data)
+ else: # lower case user names
+ if not allow_user:
+ raise wtforms.ValidationError(nouser_msg)
+ wtforms.validators.Length(min=3, max=30)(form, field)
+ wtforms.validators.Regexp(r'^\w+$')(form, field)
+ field.data = field.data.lower()
+ if field.data is None: # should not happen, but be cautious anyway
+ raise wtforms.ValidationError(message)
+ return _normalize_field
+
class RegistrationForm(wtforms.Form):
username = wtforms.TextField(
_('Username'),
[wtforms.validators.Required(),
- wtforms.validators.Length(min=3, max=30),
- wtforms.validators.Regexp(r'^\w+$')])
+ normalize_user_or_email_field(allow_email=False)])
password = wtforms.PasswordField(
_('Password'),
[wtforms.validators.Required(),
- wtforms.validators.Length(min=6, max=30)])
+ wtforms.validators.Length(min=5, max=1024)])
email = wtforms.TextField(
_('Email address'),
[wtforms.validators.Required(),
- wtforms.validators.Email()])
+ normalize_user_or_email_field(allow_user=False)])
class LoginForm(wtforms.Form):
username = wtforms.TextField(
_('Username'),
[wtforms.validators.Required(),
- wtforms.validators.Regexp(r'^\w+$')])
+ normalize_user_or_email_field(allow_email=False)])
password = wtforms.PasswordField(
_('Password'),
- [wtforms.validators.Required()])
+ [wtforms.validators.Required(),
+ wtforms.validators.Length(min=5, max=1024)])
class ForgotPassForm(wtforms.Form):
username = wtforms.TextField(
_('Username or email'),
- [wtforms.validators.Required()])
-
- def validate_username(form, field):
- if not (re.match(r'^\w+$', field.data) or
- re.match(r'^.+@[^.].*\.[a-z]{2,10}$', field.data,
- re.IGNORECASE)):
- raise wtforms.ValidationError(_(u'Incorrect input'))
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field()])
class ChangePassForm(wtforms.Form):
password = wtforms.PasswordField(
'Password',
[wtforms.validators.Required(),
- wtforms.validators.Length(min=6, max=30)])
+ wtforms.validators.Length(min=5, max=1024)])
userid = wtforms.HiddenField(
'',
[wtforms.validators.Required()])
diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py
index 43354135..d8ad7b51 100644
--- a/mediagoblin/auth/views.py
+++ b/mediagoblin/auth/views.py
@@ -41,8 +41,10 @@ def email_debug_message(request):
def register(request):
- """
- Your classic registration view!
+ """The registration view.
+
+ Note that usernames will always be lowercased. Email domains are lowercased while
+ the first part remains case-sensitive.
"""
# Redirects to indexpage if registrations are disabled
if not mg_globals.app_config["allow_registration"]:
@@ -56,12 +58,8 @@ def register(request):
if request.method == 'POST' and register_form.validate():
# TODO: Make sure the user doesn't exist already
- username = unicode(request.form['username'].lower())
- em_user, em_dom = unicode(request.form['email']).split("@", 1)
- em_dom = em_dom.lower()
- email = em_user + "@" + em_dom
- users_with_username = User.query.filter_by(username=username).count()
- users_with_email = User.query.filter_by(email=email).count()
+ users_with_username = User.query.filter_by(username=register_form.data['username']).count()
+ users_with_email = User.query.filter_by(email=register_form.data['email']).count()
extra_validation_passes = True
@@ -77,8 +75,8 @@ def register(request):
if extra_validation_passes:
# Create the user
user = User()
- user.username = username
- user.email = email
+ user.username = register_form.data['username']
+ user.email = register_form.data['email']
user.pw_hash = auth_lib.bcrypt_gen_password_hash(
request.form['password'])
user.verification_key = unicode(uuid.uuid4())
@@ -114,20 +112,21 @@ def login(request):
login_failed = False
- if request.method == 'POST' and login_form.validate():
- user = User.query.filter_by(username=request.form['username'].lower()).first()
+ if request.method == 'POST':
+ if login_form.validate():
+ user = User.query.filter_by(username=login_form.data['username']).first()
- if user and user.check_login(request.form['password']):
- # set up login in session
- request.session['user_id'] = unicode(user.id)
- request.session.save()
+ if user and user.check_login(request.form['password']):
+ # set up login in session
+ request.session['user_id'] = unicode(user.id)
+ request.session.save()
- if request.form.get('next'):
- return redirect(request, location=request.form['next'])
- else:
- return redirect(request, "index")
+ if request.form.get('next'):
+ return redirect(request, location=request.form['next'])
+ else:
+ return redirect(request, "index")
- else:
+ # Some failure during login occured if we are here!
# Prevent detecting who's on this system by testing login
# attempt timings
auth_lib.fake_login_attempt()
@@ -227,59 +226,66 @@ def forgot_password(request):
"""
Forgot password view
- Sends an email with an url to renew forgotten password
+ Sends an email with an url to renew forgotten password.
+ Use GET querystring parameter 'username' to pre-populate the input field
"""
fp_form = auth_forms.ForgotPassForm(request.form,
- username=request.GET.get('username'))
-
- if request.method == 'POST' and fp_form.validate():
-
- # '$or' not available till mongodb 1.5.3
- user = User.query.filter_by(username=request.form['username']).first()
- if not user:
- user = User.query.filter_by(email=request.form['username']).first()
-
- if user:
- if user.email_verified and user.status == 'active':
- user.fp_verification_key = unicode(uuid.uuid4())
- user.fp_token_expire = datetime.datetime.now() + \
- datetime.timedelta(days=10)
- user.save()
-
- send_fp_verification_email(user, request)
-
- messages.add_message(
- request,
- messages.INFO,
- _("An email has been sent with instructions on how to "
- "change your password."))
- email_debug_message(request)
-
- else:
- # special case... we can't send the email because the
- # username is inactive / hasn't verified their email
- messages.add_message(
- request,
- messages.WARNING,
- _("Could not send password recovery email as "
- "your username is inactive or your account's "
- "email address has not been verified."))
-
- return redirect(
- request, 'mediagoblin.user_pages.user_home',
- user=user.username)
- return redirect(request, 'mediagoblin.auth.login')
- else:
- messages.add_message(
- request,
- messages.WARNING,
- _("Couldn't find someone with that username or email."))
+ username=request.args.get('username'))
+
+ if not (request.method == 'POST' and fp_form.validate()):
+ # Either GET request, or invalid form submitted. Display the template
+ return render_to_response(request,
+ 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form})
+
+ # If we are here: method == POST and form is valid. username casing
+ # has been sanitized. Store if a user was found by email. We should
+ # not reveal if the operation was successful then as we don't want to
+ # leak if an email address exists in the system.
+ found_by_email = '@' in request.form['username']
+
+ if found_by_email:
+ user = User.query.filter_by(
+ email = request.form['username']).first()
+ # Don't reveal success in case the lookup happened by email address.
+ success_message=_("If that email address (case sensitive!) is "
+ "registered an email has been sent with instructions "
+ "on how to change your password.")
+
+ else: # found by username
+ user = User.query.filter_by(
+ username = request.form['username']).first()
+
+ if user is None:
+ messages.add_message(request,
+ messages.WARNING,
+ _("Couldn't find someone with that username."))
return redirect(request, 'mediagoblin.auth.forgot_password')
- return render_to_response(
- request,
- 'mediagoblin/auth/forgot_password.html',
- {'fp_form': fp_form})
+ success_message=_("An email has been sent with instructions "
+ "on how to change your password.")
+
+ if user and not(user.email_verified and user.status == 'active'):
+ # Don't send reminder because user is inactive or has no verified email
+ messages.add_message(request,
+ messages.WARNING,
+ _("Could not send password recovery email as your username is in"
+ "active or your account's email address has not been verified."))
+
+ return redirect(request, 'mediagoblin.user_pages.user_home',
+ user=user.username)
+
+ # SUCCESS. Send reminder and return to login page
+ if user:
+ user.fp_verification_key = unicode(uuid.uuid4())
+ user.fp_token_expire = datetime.datetime.now() + \
+ datetime.timedelta(days=10)
+ user.save()
+
+ email_debug_message(request)
+ send_fp_verification_email(user, request)
+
+ messages.add_message(request, messages.INFO, success_message)
+ return redirect(request, 'mediagoblin.auth.login')
def verify_forgot_password(request):
diff --git a/mediagoblin/db/open.py b/mediagoblin/db/open.py
index d976acd8..5fd5ed03 100644
--- a/mediagoblin/db/open.py
+++ b/mediagoblin/db/open.py
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from sqlalchemy import create_engine
+from sqlalchemy import create_engine, event
import logging
from mediagoblin.db.base import Base, Session
@@ -66,9 +66,20 @@ def load_models(app_config):
exc))
+def _sqlite_fk_pragma_on_connect(dbapi_con, con_record):
+ """Enable foreign key checking on each new sqlite connection"""
+ dbapi_con.execute('pragma foreign_keys=on')
+
+
def setup_connection_and_db_from_config(app_config):
engine = create_engine(app_config['sql_engine'])
+
+ # Enable foreign key checking for sqlite
+ if app_config['sql_engine'].startswith('sqlite://'):
+ event.listen(engine, 'connect', _sqlite_fk_pragma_on_connect)
+
# logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
+
Session.configure(bind=engine)
return DatabaseMaster(engine)
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index 88af22d8..24b0b69b 100644
--- a/mediagoblin/edit/views.py
+++ b/mediagoblin/edit/views.py
@@ -33,6 +33,7 @@ from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.text import (
convert_to_tag_list_of_dicts, media_tags_as_string)
+from mediagoblin.tools.url import slugify
from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used
import mimetypes
@@ -58,22 +59,20 @@ def edit_media(request, media):
if request.method == 'POST' and form.validate():
# Make sure there isn't already a MediaEntry with such a slug
# and userid.
- slug_used = check_media_slug_used(media.uploader, request.form['slug'],
- media.id)
+ slug = slugify(request.form['slug'])
+ slug_used = check_media_slug_used(media.uploader, slug, media.id)
if slug_used:
form.slug.errors.append(
_(u'An entry with that slug already exists for this user.'))
else:
- media.title = unicode(request.form['title'])
- media.description = unicode(request.form.get('description'))
+ media.title = request.form['title']
+ media.description = request.form.get('description')
media.tags = convert_to_tag_list_of_dicts(
request.form.get('tags'))
media.license = unicode(request.form.get('license', '')) or None
-
- media.slug = unicode(request.form['slug'])
-
+ media.slug = slug
media.save()
return redirect(request,
diff --git a/mediagoblin/init/__init__.py b/mediagoblin/init/__init__.py
index ab6e6399..7c832442 100644
--- a/mediagoblin/init/__init__.py
+++ b/mediagoblin/init/__init__.py
@@ -26,7 +26,7 @@ from mediagoblin import mg_globals
from mediagoblin.mg_globals import setup_globals
from mediagoblin.db.open import setup_connection_and_db_from_config, \
check_db_migrations_current, load_models
-from mediagoblin.workbench import WorkbenchManager
+from mediagoblin.tools.workbench import WorkbenchManager
from mediagoblin.storage import storage_system_from_config
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
index 22c4355d..4c9f0131 100644
--- a/mediagoblin/media_types/video/processing.py
+++ b/mediagoblin/media_types/video/processing.py
@@ -75,9 +75,8 @@ def process_video(entry, workbench=None):
thumbnail_filepath = create_pub_filepath(
entry, name_builder.fill('{basename}.thumbnail.jpg'))
- # Create a temporary file for the video destination
- tmp_dst = NamedTemporaryFile(dir=workbench.dir)
-
+ # Create a temporary file for the video destination (cleaned up with workbench)
+ tmp_dst = NamedTemporaryFile(dir=workbench.dir, delete=False)
with tmp_dst:
# Transcode queued file to a VP8/vorbis file that fits in a 640x640 square
progress_callback = ProgressCallback(entry)
@@ -88,22 +87,20 @@ def process_video(entry, workbench=None):
vorbis_quality=video_config['vorbis_quality'],
progress_callback=progress_callback)
- # Push transcoded video to public storage
- _log.debug('Saving medium...')
- # TODO (#419, we read everything in RAM here!)
- mgg.public_store.get_file(medium_filepath, 'wb').write(
- tmp_dst.read())
- _log.debug('Saved medium')
+ # Push transcoded video to public storage
+ _log.debug('Saving medium...')
+ mgg.public_store.copy_local_to_storage(tmp_dst.name, medium_filepath)
+ _log.debug('Saved medium')
- entry.media_files['webm_640'] = medium_filepath
+ entry.media_files['webm_640'] = medium_filepath
- # Save the width and height of the transcoded video
- entry.media_data_init(
- width=transcoder.dst_data.videowidth,
- height=transcoder.dst_data.videoheight)
+ # Save the width and height of the transcoded video
+ entry.media_data_init(
+ width=transcoder.dst_data.videowidth,
+ height=transcoder.dst_data.videoheight)
- # Create a temporary file for the video thumbnail
- tmp_thumb = NamedTemporaryFile(dir=workbench.dir, suffix='.jpg')
+ # Temporary file for the video thumbnail (cleaned up with workbench)
+ tmp_thumb = NamedTemporaryFile(dir=workbench.dir, suffix='.jpg', delete=False)
with tmp_thumb:
# Create a thumbnail.jpg that fits in a 180x180 square
@@ -112,33 +109,16 @@ def process_video(entry, workbench=None):
tmp_thumb.name,
180)
- # Push the thumbnail to public storage
- _log.debug('Saving thumbnail...')
- mgg.public_store.get_file(thumbnail_filepath, 'wb').write(
- tmp_thumb.read())
- _log.debug('Saved thumbnail')
-
- entry.media_files['thumb'] = thumbnail_filepath
+ # Push the thumbnail to public storage
+ _log.debug('Saving thumbnail...')
+ mgg.public_store.copy_local_to_storage(tmp_thumb.name, thumbnail_filepath)
+ entry.media_files['thumb'] = thumbnail_filepath
if video_config['keep_original']:
# Push original file to public storage
- queued_file = file(queued_filename, 'rb')
-
- with queued_file:
- original_filepath = create_pub_filepath(
- entry,
- queued_filepath[-1])
-
- with mgg.public_store.get_file(original_filepath, 'wb') as \
- original_file:
- _log.debug('Saving original...')
- # TODO (#419, we read everything in RAM here!)
- original_file.write(queued_file.read())
- _log.debug('Saved original')
-
- entry.media_files['original'] = original_filepath
+ _log.debug('Saving original...')
+ original_filepath = create_pub_filepath(entry, queued_filepath[-1])
+ mgg.public_store.copy_local_to_storage(queued_filename, original_filepath)
+ entry.media_files['original'] = original_filepath
mgg.queue_store.delete_file(queued_filepath)
-
- # clean up workbench
- workbench.destroy_self()
diff --git a/mediagoblin/plugins/api/views.py b/mediagoblin/plugins/api/views.py
index 6aa4ef9f..2055a663 100644
--- a/mediagoblin/plugins/api/views.py
+++ b/mediagoblin/plugins/api/views.py
@@ -86,7 +86,10 @@ def post_entry(request):
#
# (... don't change entry after this point to avoid race
# conditions with changes to the document via processing code)
- run_process_media(entry)
+ feed_url = request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ qualified=True, user=request.user.username)
+ run_process_media(entry, feed_url)
return json_response(get_entry_serializable(entry, request.urlgen))
diff --git a/mediagoblin/processing/task.py b/mediagoblin/processing/task.py
index b29de9bd..e9bbe084 100644
--- a/mediagoblin/processing/task.py
+++ b/mediagoblin/processing/task.py
@@ -15,8 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
+import urllib
+import urllib2
-from celery.task import Task
+from celery import registry, task
from mediagoblin import mg_globals as mgg
from mediagoblin.db.models import MediaEntry
@@ -28,18 +30,51 @@ logging.basicConfig()
_log.setLevel(logging.DEBUG)
+@task.task(default_retry_delay=2 * 60)
+def handle_push_urls(feed_url):
+ """Subtask, notifying the PuSH servers of new content
+
+ Retry 3 times every 2 minutes if run in separate process before failing."""
+ if not mgg.app_config["push_urls"]:
+ return # Nothing to do
+ _log.debug('Notifying Push servers for feed {0}'.format(feed_url))
+ hubparameters = {
+ 'hub.mode': 'publish',
+ 'hub.url': feed_url}
+ hubdata = urllib.urlencode(hubparameters)
+ hubheaders = {
+ "Content-type": "application/x-www-form-urlencoded",
+ "Connection": "close"}
+ for huburl in mgg.app_config["push_urls"]:
+ hubrequest = urllib2.Request(huburl, hubdata, hubheaders)
+ try:
+ hubresponse = urllib2.urlopen(hubrequest)
+ except (urllib2.HTTPError, urllib2.URLError) as exc:
+ # We retry by default 3 times before failing
+ _log.info("PuSH url %r gave error %r", huburl, exc)
+ try:
+ return handle_push_urls.retry(exc=exc, throw=False)
+ except Exception as e:
+ # All retries failed, Failure is no tragedy here, probably.
+ _log.warn('Failed to notify PuSH server for feed {0}. '
+ 'Giving up.'.format(feed_url))
+ return False
+
################################
# Media processing initial steps
################################
-class ProcessMedia(Task):
+class ProcessMedia(task.Task):
"""
Pass this entry off for processing.
"""
- def run(self, media_id):
+ def run(self, media_id, feed_url):
"""
Pass the media entry off to the appropriate processing function
(for now just process_image...)
+
+ :param feed_url: The feed URL that the PuSH server needs to be
+ updated for.
"""
entry = MediaEntry.query.get(media_id)
@@ -58,6 +93,10 @@ class ProcessMedia(Task):
entry.state = u'processed'
entry.save()
+ # Notify the PuSH servers as async task
+ if mgg.app_config["push_urls"] and feed_url:
+ handle_push_urls.subtask().delay(feed_url)
+
json_processing_callback(entry)
except BaseProcessingFail as exc:
mark_entry_failed(entry.id, exc)
@@ -97,3 +136,7 @@ class ProcessMedia(Task):
entry = mgg.database.MediaEntry.query.filter_by(id=entry_id).first()
json_processing_callback(entry)
+
+# Register the task
+process_media = registry.tasks[ProcessMedia.name]
+
diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py
index db5dfe53..679fc543 100644
--- a/mediagoblin/submit/lib.py
+++ b/mediagoblin/submit/lib.py
@@ -14,16 +14,12 @@
# 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 urllib
-import urllib2
import logging
import uuid
-from celery import registry
from werkzeug.utils import secure_filename
-from mediagoblin import mg_globals
from mediagoblin.processing import mark_entry_failed
-from mediagoblin.processing.task import ProcessMedia
+from mediagoblin.processing.task import process_media
_log = logging.getLogger(__name__)
@@ -58,11 +54,17 @@ def prepare_queue_task(app, entry, filename):
return queue_file
-def run_process_media(entry):
- process_media = registry.tasks[ProcessMedia.name]
+def run_process_media(entry, feed_url=None):
+ """Process the media asynchronously
+
+ :param entry: MediaEntry() instance to be processed.
+ :param feed_url: A string indicating the feed_url that the PuSH servers
+ should be notified of. This will be sth like: `request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',qualified=True,
+ user=request.user.username)`"""
try:
process_media.apply_async(
- [unicode(entry.id)], {},
+ [entry.id, feed_url], {},
task_id=entry.queued_task_id)
except BaseException as exc:
# The purpose of this section is because when running in "lazy"
@@ -76,30 +78,3 @@ def run_process_media(entry):
mark_entry_failed(entry.id, exc)
# re-raise the exception
raise
-
-
-def handle_push_urls(request):
- if mg_globals.app_config["push_urls"]:
- feed_url = request.urlgen(
- 'mediagoblin.user_pages.atom_feed',
- qualified=True,
- user=request.user.username)
- hubparameters = {
- 'hub.mode': 'publish',
- 'hub.url': feed_url}
- hubdata = urllib.urlencode(hubparameters)
- hubheaders = {
- "Content-type": "application/x-www-form-urlencoded",
- "Connection": "close"}
- for huburl in mg_globals.app_config["push_urls"]:
- hubrequest = urllib2.Request(huburl, hubdata, hubheaders)
- try:
- hubresponse = urllib2.urlopen(hubrequest)
- except urllib2.HTTPError as exc:
- # This is not a big issue, the item will be fetched
- # by the PuSH server next time we hit it
- _log.warning(
- "push url %r gave error %r", huburl, exc.code)
- except urllib2.URLError as exc:
- _log.warning(
- "push url %r is unreachable %r", huburl, exc.reason)
diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py
index 38adf85f..4055d394 100644
--- a/mediagoblin/submit/views.py
+++ b/mediagoblin/submit/views.py
@@ -32,8 +32,7 @@ from mediagoblin.submit import forms as submit_forms
from mediagoblin.messages import add_message, SUCCESS
from mediagoblin.media_types import sniff_media, \
InvalidFileType, FileTypeNotSupported
-from mediagoblin.submit.lib import handle_push_urls, run_process_media, \
- prepare_queue_task
+from mediagoblin.submit.lib import run_process_media, prepare_queue_task
@require_active_login
@@ -91,10 +90,10 @@ def submit_start(request):
#
# (... don't change entry after this point to avoid race
# conditions with changes to the document via processing code)
- run_process_media(entry)
-
- handle_push_urls(request)
-
+ feed_url = request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ qualified=True, user=request.user.username)
+ run_process_media(entry, feed_url)
add_message(request, SUCCESS, _('Woohoo! Submitted!'))
return redirect(request, "mediagoblin.user_pages.user_home",
diff --git a/mediagoblin/templates/mediagoblin/edit/delete_account.html b/mediagoblin/templates/mediagoblin/edit/delete_account.html
index 6d56d77c..84d0b580 100644
--- a/mediagoblin/templates/mediagoblin/edit/delete_account.html
+++ b/mediagoblin/templates/mediagoblin/edit/delete_account.html
@@ -24,11 +24,16 @@
<form action="{{ request.urlgen('mediagoblin.edit.delete_account') }}"
method="POST" enctype="multipart/form-data">
<div class="form_box">
- <h1>Really delete user '{{ user.username }}' and all related media/comments?
+ <h1>
+ {%- trans user_name=user.username -%}
+ Really delete user '{{ user_name }}' and all related media/comments?
+ {%- endtrans -%}
</h1>
<p class="delete_checkbox_box">
<input type="checkbox" name="confirmed"/>
- <label for="confirmed">Yes, really delete my account</label>
+ <label for="confirmed">
+ {%- trans %}Yes, really delete my account{% endtrans -%}
+ </label>
</p>
<div class="form_submit_buttons">
diff --git a/mediagoblin/templates/mediagoblin/edit/edit_account.html b/mediagoblin/templates/mediagoblin/edit/edit_account.html
index 89f559af..4b980301 100644
--- a/mediagoblin/templates/mediagoblin/edit/edit_account.html
+++ b/mediagoblin/templates/mediagoblin/edit/edit_account.html
@@ -58,7 +58,8 @@
</div>
</form>
<div class="delete">
- <a href="{{request.urlgen('mediagoblin.edit.delete_account')
- }}">Delete my account</a>
+ <a href="{{ request.urlgen('mediagoblin.edit.delete_account') }}">
+ {%- trans %}Delete my account{% endtrans -%}
+ </a>
</div>
{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html
index 10b48296..7e184257 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/media.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/media.html
@@ -105,9 +105,6 @@
<form action="{{ request.urlgen('mediagoblin.user_pages.media_post_comment',
user= media.get_uploader.username,
media_id=media.id) }}" method="POST" id="form_comment">
- <p>
- {% trans %}You can use <a href="http://daringfireball.net/projects/markdown/basics">Markdown</a> for formatting.{% endtrans %}
- </p>
{{ wtforms_util.render_divs(comment_form) }}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Add this comment{% endtrans %}" class="button_action" />
@@ -115,35 +112,38 @@
</div>
</form>
{% endif %}
+ <ul style="list-style:none">
{% for comment in comments %}
{% set comment_author = comment.get_author %}
- {% if pagination.active_id == comment.id %}
- <div class="comment_wrapper comment_active" id="comment-{{ comment.id }}">
- <a name="comment" id="comment"></a>
- {% else %}
- <div class="comment_wrapper" id="comment-{{ comment.id }}">
- {% endif %}
- <div class="comment_author">
+ <li id="comment-{{ comment.id }}"
+ {%- if pagination.active_id == comment.id %}
+ class="comment_wrapper comment_active">
+ <a name="comment" id="comment"></a>
+ {%- else %}
+ class="comment_wrapper">
+ {%- endif %}
+ <div class="comment_author">
<img src="{{ request.staticdirect('/images/icon_comment.png') }}" />
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
user = comment_author.username) }}">
- {{ comment_author.username -}}
+ {{- comment_author.username -}}
</a>
{% trans %}at{% endtrans %}
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home.view_comment',
comment = comment.id,
user = media.get_uploader.username,
media = media.slug_or_id) }}#comment">
- {{ comment.created.strftime("%I:%M%p %Y-%m-%d") }}
+ {{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}
</a>:
</div>
<div class="comment_content">
- {% autoescape False %}
+ {% autoescape False -%}
{{ comment.content_html }}
- {% endautoescape %}
+ {%- endautoescape %}
</div>
- </div>
+ </li>
{% endfor %}
+ </ul>
{{ render_pagination(request, pagination,
media.url_for_self(request.urlgen)) }}
{% endif %}
diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py
index 4b784da3..82b1c1b4 100644
--- a/mediagoblin/tests/test_api.py
+++ b/mediagoblin/tests/test_api.py
@@ -22,7 +22,7 @@ from pkg_resources import resource_filename
from mediagoblin import mg_globals
from mediagoblin.tools import template, pluginapi
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
_log = logging.getLogger(__name__)
@@ -44,7 +44,7 @@ BIG_BLUE = resource('bigblue.png')
class TestAPI(object):
def setUp(self):
- self.app = get_test_app(dump_old_app=False)
+ self.app = get_app(dump_old_app=False)
self.db = mg_globals.database
self.user_password = u'4cc355_70k3N'
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index a40c9cbc..f4409121 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -22,7 +22,7 @@ from nose.tools import assert_equal
from mediagoblin import mg_globals
from mediagoblin.auth import lib as auth_lib
from mediagoblin.db.models import User
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import setup_fresh_app, get_app, fixture_add_user
from mediagoblin.tools import template, mail
@@ -67,11 +67,11 @@ def test_bcrypt_gen_password_hash():
'notthepassword', hashed_pw, '3><7R45417')
-def test_register_views():
+@setup_fresh_app
+def test_register_views(test_app):
"""
Massive test function that all our registration-related views all work.
"""
- test_app = get_test_app(dump_old_app=False)
# Test doing a simple GET on the page
# -----------------------------------
@@ -105,10 +105,8 @@ def test_register_views():
context = template.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.']
+ assert_equal (form.username.errors, [u'Field must be between 3 and 30 characters long.'])
+ assert_equal (form.password.errors, [u'Field must be between 5 and 1024 characters long.'])
## bad form
template.clear_test_template_context()
@@ -119,13 +117,11 @@ def test_register_views():
context = template.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.']
+ assert_equal (form.username.errors, [u'This field does not take email addresses.'])
+ assert_equal (form.email.errors, [u'This field requires an email address.'])
## At this point there should be no users in the database ;)
- assert not User.query.count()
+ assert_equal(User.query.count(), 0)
# Successful register
# -------------------
@@ -315,7 +311,7 @@ def test_authentication_views():
"""
Test logging in and logging out
"""
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
# Make a new user
test_user = fixture_add_user(active_user=False)
@@ -370,7 +366,7 @@ def test_authentication_views():
response = test_app.post(
'/auth/login/', {
'username': u'chris',
- 'password': 'jam'})
+ 'password': 'jam_and_ham'})
context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
assert context['login_failed']
diff --git a/mediagoblin/tests/test_collections.py b/mediagoblin/tests/test_collections.py
new file mode 100644
index 00000000..b19f6362
--- /dev/null
+++ b/mediagoblin/tests/test_collections.py
@@ -0,0 +1,37 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# 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 fixture_add_collection, fixture_add_user, \
+ get_app
+from mediagoblin.db.models import Collection, User
+from mediagoblin.db.base import Session
+from nose.tools import assert_equal
+
+
+def test_user_deletes_collection():
+ # Setup db.
+ get_app(dump_old_app=False)
+
+ user = fixture_add_user()
+ coll = fixture_add_collection(user=user)
+ # Reload into session:
+ user = User.query.get(user.id)
+
+ cnt1 = Collection.query.count()
+ user.delete()
+ cnt2 = Collection.query.count()
+
+ assert_equal(cnt1, cnt2 + 1)
diff --git a/mediagoblin/tests/test_csrf_middleware.py b/mediagoblin/tests/test_csrf_middleware.py
index 22a0eb04..e720264c 100644
--- a/mediagoblin/tests/test_csrf_middleware.py
+++ b/mediagoblin/tests/test_csrf_middleware.py
@@ -14,12 +14,12 @@
# 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.tests.tools import get_app
from mediagoblin import mg_globals
def test_csrf_cookie_set():
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
cookie_name = mg_globals.app_config['csrf_cookie_name']
# get login page
@@ -37,7 +37,7 @@ def test_csrf_token_must_match():
# We need a fresh app for this test on webtest < 1.3.6.
# We do not understand why, but it fixes the tests.
# If we require webtest >= 1.3.6, we can switch to a non fresh app here.
- test_app = get_test_app(dump_old_app=True)
+ test_app = get_app(dump_old_app=True)
# construct a request with no cookie or form token
assert test_app.post('/auth/login/',
@@ -68,7 +68,7 @@ def test_csrf_token_must_match():
status_int == 200
def test_csrf_exempt():
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
# monkey with the views to decorate a known endpoint
import mediagoblin.auth.views
from mediagoblin.meddleware.csrf import csrf_exempt
diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py
index 4bea9243..7db6eaea 100644
--- a/mediagoblin/tests/test_edit.py
+++ b/mediagoblin/tests/test_edit.py
@@ -18,13 +18,13 @@ from nose.tools import assert_equal
from mediagoblin import mg_globals
from mediagoblin.db.models import User
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
from mediagoblin.tools import template
from mediagoblin.auth.lib import bcrypt_check_password
class TestUserEdit(object):
def setUp(self):
- self.app = get_test_app(dump_old_app=False)
+ self.app = get_app(dump_old_app=False)
# set up new user
self.user_password = u'toast'
self.user = fixture_add_user(password = self.user_password)
diff --git a/mediagoblin/tests/test_http_callback.py b/mediagoblin/tests/test_http_callback.py
index 0f6e489f..8bee7045 100644
--- a/mediagoblin/tests/test_http_callback.py
+++ b/mediagoblin/tests/test_http_callback.py
@@ -20,14 +20,14 @@ from urlparse import urlparse, parse_qs
from mediagoblin import mg_globals
from mediagoblin.tools import processing
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
from mediagoblin.tests.test_submission import GOOD_PNG
from mediagoblin.tests import test_oauth as oauth
class TestHTTPCallback(object):
def setUp(self):
- self.app = get_test_app(dump_old_app=False)
+ self.app = get_app(dump_old_app=False)
self.db = mg_globals.database
self.user_password = u'secret'
diff --git a/mediagoblin/tests/test_messages.py b/mediagoblin/tests/test_messages.py
index c587e599..4c0f3e2e 100644
--- a/mediagoblin/tests/test_messages.py
+++ b/mediagoblin/tests/test_messages.py
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.messages import fetch_messages, add_message
-from mediagoblin.tests.tools import get_test_app
+from mediagoblin.tests.tools import get_app
from mediagoblin.tools import template
@@ -26,7 +26,7 @@ def test_messages():
fetched messages should be the same as the added ones,
and fetching should clear the message list.
"""
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
# Aquire a request object
test_app.get('/')
context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
diff --git a/mediagoblin/tests/test_misc.py b/mediagoblin/tests/test_misc.py
index 8a96e7d0..ae5d7e50 100644
--- a/mediagoblin/tests/test_misc.py
+++ b/mediagoblin/tests/test_misc.py
@@ -16,9 +16,9 @@
from nose.tools import assert_equal
-from mediagoblin.tests.tools import get_test_app
+from mediagoblin.tests.tools import get_app
def test_404_for_non_existent():
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
res = test_app.get('/does-not-exist/', expect_errors=True)
assert_equal(res.status_int, 404)
diff --git a/mediagoblin/tests/test_oauth.py b/mediagoblin/tests/test_oauth.py
index a72f766e..94ba5dab 100644
--- a/mediagoblin/tests/test_oauth.py
+++ b/mediagoblin/tests/test_oauth.py
@@ -21,7 +21,7 @@ from urlparse import parse_qs, urlparse
from mediagoblin import mg_globals
from mediagoblin.tools import template, pluginapi
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
_log = logging.getLogger(__name__)
@@ -29,7 +29,7 @@ _log = logging.getLogger(__name__)
class TestOAuth(object):
def setUp(self):
- self.app = get_test_app()
+ self.app = get_app()
self.db = mg_globals.database
self.pman = pluginapi.PluginManager()
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
index 53330c48..00f1ed3d 100644
--- a/mediagoblin/tests/test_submission.py
+++ b/mediagoblin/tests/test_submission.py
@@ -24,7 +24,7 @@ import os
from nose.tools import assert_equal, assert_true
from pkg_resources import resource_filename
-from mediagoblin.tests.tools import get_test_app, \
+from mediagoblin.tests.tools import get_app, \
fixture_add_user
from mediagoblin import mg_globals
from mediagoblin.tools import template
@@ -50,7 +50,7 @@ REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request']
class TestSubmission:
def setUp(self):
- self.test_app = get_test_app(dump_old_app=False)
+ self.test_app = get_app(dump_old_app=False)
# TODO: Possibly abstract into a decorator like:
# @as_authenticated_user('chris')
@@ -161,11 +161,17 @@ class TestSubmission:
media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
media_id = media.id
- # At least render the edit page
+ # render and post to the edit page.
edit_url = request.urlgen(
'mediagoblin.edit.edit_media',
user=self.test_user.username, media_id=media_id)
self.test_app.get(edit_url)
+ self.test_app.post(edit_url,
+ {'title': u'Balanced Goblin',
+ 'slug': u"Balanced=Goblin",
+ 'tags': u''})
+ media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+ assert_equal(media.slug, u"balanced-goblin")
# Add a comment, so we can test for its deletion later.
self.check_comments(request, media_id, 0)
diff --git a/mediagoblin/tests/test_tags.py b/mediagoblin/tests/test_tags.py
index 73af2eea..ccb93085 100644
--- a/mediagoblin/tests/test_tags.py
+++ b/mediagoblin/tests/test_tags.py
@@ -14,7 +14,7 @@
# 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.tests.tools import get_app
from mediagoblin.tools import text
def test_list_of_dicts_conversion():
@@ -24,7 +24,7 @@ def test_list_of_dicts_conversion():
as a dict. Each tag dict should contain the tag's name and slug. Another
function performs the reverse operation when populating a form to edit tags.
"""
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
# Leading, trailing, and internal whitespace should be removed and slugified
assert text.convert_to_tag_list_of_dicts('sleep , 6 AM, chainsaw! ') == [
{'name': u'sleep', 'slug': u'sleep'},
diff --git a/mediagoblin/tests/test_tests.py b/mediagoblin/tests/test_tests.py
index d09e8f28..d539f1e0 100644
--- a/mediagoblin/tests/test_tests.py
+++ b/mediagoblin/tests/test_tests.py
@@ -15,22 +15,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin import mg_globals
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
from mediagoblin.db.models import User
-def test_get_test_app_wipes_db():
+def test_get_app_wipes_db():
"""
Make sure we get a fresh database on every wipe :)
"""
- get_test_app(dump_old_app=True)
+ get_app(dump_old_app=True)
assert User.query.count() == 0
fixture_add_user()
assert User.query.count() == 1
- get_test_app(dump_old_app=False)
+ get_app(dump_old_app=False)
assert User.query.count() == 1
- get_test_app(dump_old_app=True)
+ get_app(dump_old_app=True)
assert User.query.count() == 0
diff --git a/mediagoblin/tests/test_workbench.py b/mediagoblin/tests/test_workbench.py
index 9da8eea0..636c8689 100644
--- a/mediagoblin/tests/test_workbench.py
+++ b/mediagoblin/tests/test_workbench.py
@@ -18,7 +18,7 @@ import os
import tempfile
-from mediagoblin import workbench
+from mediagoblin.tools import workbench
from mediagoblin.mg_globals import setup_globals
from mediagoblin.decorators import get_workbench
from mediagoblin.tests.test_storage import get_tmp_filestorage
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
index 3e78b2e3..18d4ec0c 100644
--- a/mediagoblin/tests/tools.py
+++ b/mediagoblin/tests/tools.py
@@ -25,7 +25,7 @@ from paste.deploy import loadapp
from webtest import TestApp
from mediagoblin import mg_globals
-from mediagoblin.db.models import User
+from mediagoblin.db.models import User, Collection
from mediagoblin.tools import testing
from mediagoblin.init.config import read_mediagoblin_config
from mediagoblin.db.open import setup_connection_and_db_from_config
@@ -103,7 +103,7 @@ def suicide_if_bad_celery_environ():
raise BadCeleryEnviron(BAD_CELERY_MESSAGE)
-def get_test_app(dump_old_app=True):
+def get_app(dump_old_app=True):
suicide_if_bad_celery_environ()
# Make sure we've turned on testing
@@ -164,7 +164,7 @@ def setup_fresh_app(func):
"""
@wraps(func)
def wrapper(*args, **kwargs):
- test_app = get_test_app()
+ test_app = get_app()
testing.clear_test_buckets()
return func(test_app, *args, **kwargs)
@@ -226,3 +226,24 @@ def fixture_add_user(username=u'chris', password=u'toast',
Session.expunge(test_user)
return test_user
+
+
+def fixture_add_collection(name=u"My first Collection", user=None):
+ if user is None:
+ user = fixture_add_user()
+ coll = Collection.query.filter_by(creator=user.id, title=name).first()
+ if coll is not None:
+ return coll
+ coll = Collection()
+ coll.creator = user.id
+ coll.title = name
+ coll.generate_slug()
+ coll.save()
+
+ # Reload
+ Session.refresh(coll)
+
+ # ... and detach from session:
+ Session.expunge(coll)
+
+ return coll
diff --git a/mediagoblin/tools/mail.py b/mediagoblin/tools/mail.py
index 8639ba0c..4fa02ce5 100644
--- a/mediagoblin/tools/mail.py
+++ b/mediagoblin/tools/mail.py
@@ -122,3 +122,16 @@ def send_email(from_addr, to_addrs, subject, message_body):
print message.get_payload(decode=True)
return mhost.sendmail(from_addr, to_addrs, message.as_string())
+
+
+def normalize_email(email):
+ """return case sensitive part, lower case domain name
+
+ :returns: None in case of broken email addresses"""
+ try:
+ em_user, em_dom = email.split('@', 1)
+ except ValueError:
+ # email contained no '@'
+ return None
+ email = "@".join((em_user, em_dom.lower()))
+ return email
diff --git a/mediagoblin/workbench.py b/mediagoblin/tools/workbench.py
index 0d4db52b..0bd4096b 100644
--- a/mediagoblin/workbench.py
+++ b/mediagoblin/tools/workbench.py
@@ -19,10 +19,6 @@ import shutil
import tempfile
-DEFAULT_WORKBENCH_DIR = os.path.join(
- tempfile.gettempdir(), u'mgoblin_workbench')
-
-
# Actual workbench stuff
# ----------------------
diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py
index 9e8ccf01..c7398d84 100644
--- a/mediagoblin/user_pages/forms.py
+++ b/mediagoblin/user_pages/forms.py
@@ -20,8 +20,11 @@ from mediagoblin.tools.translate import fake_ugettext_passthrough as _
class MediaCommentForm(wtforms.Form):
comment_content = wtforms.TextAreaField(
- '',
- [wtforms.validators.Required()])
+ _('Comment'),
+ [wtforms.validators.Required()],
+ description=_(u'You can use '
+ u'<a href="http://daringfireball.net/projects/markdown/basics">'
+ u'Markdown</a> for formatting.'))
class ConfirmDeleteForm(wtforms.Form):
confirm = wtforms.BooleanField(
diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py
index a9431405..2b228355 100644
--- a/mediagoblin/user_pages/routing.py
+++ b/mediagoblin/user_pages/routing.py
@@ -37,7 +37,7 @@ add_route('mediagoblin.user_pages.user_gallery',
'mediagoblin.user_pages.views:user_gallery')
add_route('mediagoblin.user_pages.media_home.view_comment',
- '/u/<string:user>/m/<string:media>/c/<string:comment>/',
+ '/u/<string:user>/m/<string:media>/c/<int:comment>/',
'mediagoblin.user_pages.views:media_home')
add_route('mediagoblin.user_pages.atom_feed',
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
index 30c78a38..dea47fbf 100644
--- a/mediagoblin/user_pages/views.py
+++ b/mediagoblin/user_pages/views.py
@@ -110,12 +110,13 @@ def media_home(request, media, page, **kwargs):
"""
'Homepage' of a MediaEntry()
"""
- if request.matchdict.get('comment', None):
+ comment_id = request.matchdict.get('comment', None)
+ if comment_id:
pagination = Pagination(
page, media.get_comments(
mg_globals.app_config['comments_ascending']),
MEDIA_COMMENTS_PER_PAGE,
- request.matchdict.get('comment'))
+ comment_id)
else:
pagination = Pagination(
page, media.get_comments(
@@ -226,6 +227,10 @@ def media_collect(request, media):
messages.add_message(
request, messages.ERROR,
_('You have to select or add a collection'))
+ return redirect(request, "mediagoblin.user_pages.media_collect",
+ user=media.get_uploader.username,
+ media=media.id)
+
# Check whether media already exists in collection
elif CollectionItem.query.filter_by(