aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/tools
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin/tools')
-rw-r--r--mediagoblin/tools/licenses.py84
-rw-r--r--mediagoblin/tools/pluginapi.py13
-rw-r--r--mediagoblin/tools/response.py34
-rw-r--r--mediagoblin/tools/template.py39
-rw-r--r--mediagoblin/tools/text.py26
-rw-r--r--mediagoblin/tools/translate.py64
-rw-r--r--mediagoblin/tools/url.py2
7 files changed, 139 insertions, 123 deletions
diff --git a/mediagoblin/tools/licenses.py b/mediagoblin/tools/licenses.py
index 48cb442b..a964980e 100644
--- a/mediagoblin/tools/licenses.py
+++ b/mediagoblin/tools/licenses.py
@@ -14,42 +14,56 @@
# 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/>.
-SORTED_SUPPORTED_LICENSES = [
- ("",
- {"name": "No license specified",
- "abbreviation": "All rights reserved"}),
- ("http://creativecommons.org/licenses/by/3.0/",
- {"name": "Creative Commons Attribution Unported 3.0",
- "abbreviation": "CC BY 3.0"}),
- ("http://creativecommons.org/licenses/by-sa/3.0/",
- {"name": "Creative Commons Attribution-ShareAlike Unported 3.0",
- "abbreviation": "CC BY-SA 3.0"}),
- ("http://creativecommons.org/licenses/by-nd/3.0/",
- {"name": "Creative Commons Attribution-NoDerivs 3.0 Unported",
- "abbreviation": "CC BY-ND 3.0"}),
- ("http://creativecommons.org/licenses/by-nc/3.0/",
- {"name": "Creative Commons Attribution-NonCommercial Unported 3.0",
- "abbreviation": "CC BY-NC 3.0"}),
- ("http://creativecommons.org/licenses/by-nc-sa/3.0/",
- {"name": "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported",
- "abbreviation": "CC BY-NC-SA 3.0"}),
- ("http://creativecommons.org/licenses/by-nc-nd/3.0/",
- {"name": "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported",
- "abbreviation": "CC BY-NC-ND 3.0"}),
- ("http://creativecommons.org/publicdomain/zero/1.0/",
- {"name": "Creative Commons CC0 1.0 Universal",
- "abbreviation": "CC0 1.0"}),
- ("http://creativecommons.org/publicdomain/mark/1.0/",
- {"name": "Public Domain",
- "abbreviation": "Public Domain"})]
-
-SUPPORTED_LICENSES = dict(SORTED_SUPPORTED_LICENSES)
+from collections import namedtuple
+# Give a License attribute names: uri, name, abbreviation
+License = namedtuple("License", ["abbreviation", "name", "uri"])
+SORTED_LICENSES = [
+ License("All rights reserved", "No license specified", ""),
+ License("CC BY 3.0", "Creative Commons Attribution Unported 3.0",
+ "http://creativecommons.org/licenses/by/3.0/"),
+ License("CC BY-SA 3.0",
+ "Creative Commons Attribution-ShareAlike Unported 3.0",
+ "http://creativecommons.org/licenses/by-sa/3.0/"),
+ License("CC BY-ND 3.0",
+ "Creative Commons Attribution-NoDerivs 3.0 Unported",
+ "http://creativecommons.org/licenses/by-nd/3.0/"),
+ License("CC BY-NC 3.0",
+ "Creative Commons Attribution-NonCommercial Unported 3.0",
+ "http://creativecommons.org/licenses/by-nc/3.0/"),
+ License("CC BY-NC-SA 3.0",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported",
+ "http://creativecommons.org/licenses/by-nc-sa/3.0/"),
+ License("CC BY-NC-ND 3.0",
+ "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported",
+ "http://creativecommons.org/licenses/by-nc-nd/3.0/"),
+ License("CC0 1.0",
+ "Creative Commons CC0 1.0 Universal",
+ "http://creativecommons.org/publicdomain/zero/1.0/"),
+ License("Public Domain","Public Domain",
+ "http://creativecommons.org/publicdomain/mark/1.0/"),
+ ]
-def licenses_as_choices():
- license_list = []
+# dict {uri: License,...} to enable fast license lookup by uri. Ideally,
+# we'd want to use an OrderedDict (python 2.7+) here to avoid having the
+# same data in two structures
+SUPPORTED_LICENSES = dict(((l.uri, l) for l in SORTED_LICENSES))
+
+
+def get_license_by_url(url):
+ """Look up a license by its url and return the License object"""
+ try:
+ return SUPPORTED_LICENSES[url]
+ except KeyError:
+ # in case of an unknown License, just display the url given
+ # rather than exploding in the user's face.
+ return License(url, url, url)
- for uri, data in SORTED_SUPPORTED_LICENSES:
- license_list.append((uri, data["abbreviation"]))
- return license_list
+def licenses_as_choices():
+ """List of (uri, abbreviation) tuples for HTML choice field population
+
+ The data seems to be consumed/deleted during usage, so hand over a
+ throwaway list, rather than just a generator.
+ """
+ return [(lic.uri, lic.abbreviation) for lic in SORTED_LICENSES]
diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py
index 7c1e108b..38ab631b 100644
--- a/mediagoblin/tools/pluginapi.py
+++ b/mediagoblin/tools/pluginapi.py
@@ -125,6 +125,7 @@ class PluginManager(object):
def register_route(self, route):
"""Registers a single route"""
+ _log.debug('registering route: {0}'.format(route))
self.routes.append(route)
def get_routes(self):
@@ -144,16 +145,14 @@ def register_routes(routes):
Example passing in a single route:
- >>> from routes import Route
- >>> register_routes(Route('about-view', '/about',
- ... controller=about_view_handler))
+ >>> register_routes(('about-view', '/about',
+ ... 'mediagoblin.views:about_view_handler'))
Example passing in a list of routes:
- >>> from routes import Route
>>> register_routes([
- ... Route('contact-view', '/contact', controller=contact_handler),
- ... Route('about-view', '/about', controller=about_handler)
+ ... ('contact-view', '/contact', 'mediagoblin.views:contact_handler'),
+ ... ('about-view', '/about', 'mediagoblin.views:about_handler')
... ])
@@ -162,7 +161,7 @@ def register_routes(routes):
Be careful when designing your route urls. If they clash with
core urls, then it could result in DISASTER!
"""
- if isinstance(routes, (tuple, list)):
+ if isinstance(routes, list):
for route in routes:
PluginManager().register_route(route)
else:
diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py
index a54c32fb..81939a77 100644
--- a/mediagoblin/tools/response.py
+++ b/mediagoblin/tools/response.py
@@ -16,6 +16,8 @@
from webob import Response, exc
from mediagoblin.tools.template import render_template
+from mediagoblin.tools.translate import (lazy_pass_to_ugettext as _,
+ pass_to_ugettext)
def render_to_response(request, template, context, status=200):
@@ -25,14 +27,36 @@ def render_to_response(request, template, context, status=200):
status=status)
-def render_404(request):
- """
- Render a 404.
+def render_error(request, status=500, title=_('Oops!'),
+ err_msg=_('An error occured')):
+ """Render any error page with a given error code, title and text body
+
+ Title and description are passed through as-is to allow html. Make
+ sure no user input is contained therein for security reasons. The
+ description will be wrapped in <p></p> tags.
"""
- return render_to_response(
- request, 'mediagoblin/404.html', {}, status=404)
+ return Response(render_template(request, 'mediagoblin/error.html',
+ {'err_code': status, 'title': title, 'err_msg': err_msg}),
+ status=status)
+def render_403(request):
+ """Render a standard 403 page"""
+ _ = pass_to_ugettext
+ title = _('Operation not allowed')
+ err_msg = _("Sorry Dave, I can't let you do that!</p><p>You have tried "
+ " to perform a function that you are not allowed to. Have you "
+ "been trying to delete all user accounts again?")
+ return render_error(request, 403, title, err_msg)
+
+def render_404(request):
+ """Render a standard 404 page."""
+ _ = pass_to_ugettext
+ err_msg = _("There doesn't seem to be a page at this address. Sorry!</p>"
+ "<p>If you're sure the address is correct, maybe the page "
+ "you're looking for has been moved or deleted.")
+ return render_error(request, 404, err_msg=err_msg)
+
def redirect(request, *args, **kwargs):
"""Returns a HTTPFound(), takes a request and then urlgen params"""
diff --git a/mediagoblin/tools/template.py b/mediagoblin/tools/template.py
index 158d5321..d9c6e654 100644
--- a/mediagoblin/tools/template.py
+++ b/mediagoblin/tools/template.py
@@ -17,10 +17,12 @@
from math import ceil
import jinja2
from babel.localedata import exists
+from werkzeug.urls import url_quote_plus
+
from mediagoblin import mg_globals
from mediagoblin import messages
from mediagoblin.tools import common
-from mediagoblin.tools.translate import setup_gettext
+from mediagoblin.tools.translate import get_gettext_translation
from mediagoblin.meddleware.csrf import render_csrf_form_token
@@ -34,7 +36,7 @@ def get_jinja_env(template_loader, locale):
(In the future we may have another system for providing theming;
for now this is good enough.)
"""
- setup_gettext(locale)
+ mg_globals.thread_scope.translations = get_gettext_translation(locale)
# If we have a jinja environment set up with this locale, just
# return that one.
@@ -57,11 +59,11 @@ def get_jinja_env(template_loader, locale):
# ... construct a grid of thumbnails or other media
# ... have access to the global and app config
template_env.globals['fetch_messages'] = messages.fetch_messages
- template_env.globals['gridify_list'] = gridify_list
- template_env.globals['gridify_cursor'] = gridify_cursor
template_env.globals['app_config'] = mg_globals.app_config
template_env.globals['global_config'] = mg_globals.global_config
+ template_env.filters['urlencode'] = url_quote_plus
+
if exists(locale):
SETUP_JINJA_ENVS[locale] = template_env
@@ -96,32 +98,3 @@ def render_template(request, template_path, context):
def clear_test_template_context():
global TEMPLATE_TEST_CONTEXT
TEMPLATE_TEST_CONTEXT = {}
-
-
-def gridify_list(this_list, num_cols=5):
- """
- Generates a list of lists where each sub-list's length depends on
- the number of columns in the list
- """
- grid = []
-
- # Figure out how many rows we should have
- num_rows = int(ceil(float(len(this_list)) / num_cols))
-
- for row_num in range(num_rows):
- slice_min = row_num * num_cols
- slice_max = (row_num + 1) * num_cols
-
- row = this_list[slice_min:slice_max]
-
- grid.append(row)
-
- return grid
-
-
-def gridify_cursor(this_cursor, num_cols=5):
- """
- Generates a list of lists where each sub-list's length depends on
- the number of columns in the list
- """
- return gridify_list(list(this_cursor), num_cols)
diff --git a/mediagoblin/tools/text.py b/mediagoblin/tools/text.py
index ea231244..96df49d2 100644
--- a/mediagoblin/tools/text.py
+++ b/mediagoblin/tools/text.py
@@ -38,13 +38,12 @@ HTML_CLEANER = Cleaner(
allow_tags=[
'div', 'b', 'i', 'em', 'strong', 'p', 'ul', 'ol', 'li', 'a', 'br',
'pre', 'code'],
- remove_unknown_tags=False, # can't be used with allow_tags
+ remove_unknown_tags=False, # can't be used with allow_tags
safe_attrs_only=True,
- add_nofollow=True, # for now
+ add_nofollow=True, # for now
host_whitelist=(),
whitelist_tags=set([]))
-TAGS_DELIMITER=',';
def clean_html(html):
# clean_html barfs on an empty string
@@ -68,14 +67,12 @@ def convert_to_tag_list_of_dicts(tag_string):
stripped_tag_string = u' '.join(tag_string.strip().split())
# Split the tag string into a list of tags
- for tag in stripped_tag_string.split(
- TAGS_DELIMITER):
-
+ for tag in stripped_tag_string.split(','):
+ tag = tag.strip()
# Ignore empty or duplicate tags
- if tag.strip() and tag.strip() not in [t['name'] for t in taglist]:
-
- taglist.append({'name': tag.strip(),
- 'slug': url.slugify(tag.strip())})
+ if tag and tag not in [t['name'] for t in taglist]:
+ taglist.append({'name': tag,
+ 'slug': url.slugify(tag)})
return taglist
@@ -85,11 +82,10 @@ def media_tags_as_string(media_entry_tags):
This is the opposite of convert_to_tag_list_of_dicts
"""
- media_tag_string = ''
+ tags_string = ''
if media_entry_tags:
- media_tag_string = (TAGS_DELIMITER+u' ').join(
- [tag['name'] for tag in media_entry_tags])
- return media_tag_string
+ tags_string = u', '.join([tag['name'] for tag in media_entry_tags])
+ return tags_string
TOO_LONG_TAG_WARNING = \
@@ -107,7 +103,7 @@ def tag_length_validator(form, field):
if too_long_tags:
raise wtforms.ValidationError(
- TOO_LONG_TAG_WARNING % (mg_globals.app_config['tags_max_length'], \
+ TOO_LONG_TAG_WARNING % (mg_globals.app_config['tags_max_length'],
', '.join(too_long_tags)))
diff --git a/mediagoblin/tools/translate.py b/mediagoblin/tools/translate.py
index cdc827a0..96144538 100644
--- a/mediagoblin/tools/translate.py
+++ b/mediagoblin/tools/translate.py
@@ -16,7 +16,9 @@
import gettext
import pkg_resources
-from babel.localedata import exists
+
+
+from babel import localedata
from babel.support import LazyProxy
from mediagoblin import mg_globals
@@ -25,14 +27,24 @@ from mediagoblin import mg_globals
# Translation tools
###################
-
+AVAILABLE_LOCALES = None
TRANSLATIONS_PATH = pkg_resources.resource_filename(
'mediagoblin', 'i18n')
+def set_available_locales():
+ """Set available locales for which we have translations"""
+ global AVAILABLE_LOCALES
+ locales=['en', 'en_US'] # these are available without translations
+ for locale in localedata.list():
+ if gettext.find('mediagoblin', TRANSLATIONS_PATH, [locale]):
+ locales.append(locale)
+ AVAILABLE_LOCALES = locales
+
+
def locale_to_lower_upper(locale):
"""
- Take a locale, regardless of style, and format it like "en-US"
+ Take a locale, regardless of style, and format it like "en_US"
"""
if '-' in locale:
lang, country = locale.split('-', 1)
@@ -57,33 +69,32 @@ def locale_to_lower_lower(locale):
def get_locale_from_request(request):
"""
- Figure out what target language is most appropriate based on the
- request
+ Return most appropriate language based on prefs/request request
"""
- request_form = request.GET or request.form
-
- if request_form.has_key('lang'):
- return locale_to_lower_upper(request_form['lang'])
+ request_args = (request.args, request.form)[request.method=='POST']
- # Your routing can explicitly specify a target language
- matchdict = request.matchdict or {}
+ if request_args.has_key('lang'):
+ # User explicitely demanded a language, normalize lower_uppercase
+ target_lang = locale_to_lower_upper(request_args['lang'])
- if 'locale' in matchdict:
- target_lang = matchdict['locale']
elif 'target_lang' in request.session:
+ # TODO: Uh, ohh, this is never ever set anywhere?
target_lang = request.session['target_lang']
- # Pull the first acceptable language or English
else:
- # TODO: Internationalization broken
- target_lang = 'en'
+ # Pull the most acceptable language based on browser preferences
+ # This returns one of AVAILABLE_LOCALES which is aready case-normalized.
+ # Note: in our tests request.accept_languages is None, so we need
+ # to explicitely fallback to en here.
+ target_lang = request.accept_languages.best_match(AVAILABLE_LOCALES) \
+ or "en_US"
- return locale_to_lower_upper(target_lang)
+ return target_lang
SETUP_GETTEXTS = {}
-def setup_gettext(locale):
+def get_gettext_translation(locale):
"""
- Setup the gettext instance based on this locale
+ Return the gettext instance based on this locale
"""
# Later on when we have plugins we may want to enable the
# multi-translations system they have so we can handle plugin
@@ -96,15 +107,9 @@ def setup_gettext(locale):
else:
this_gettext = gettext.translation(
'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
- if exists(locale):
+ if localedata.exists(locale):
SETUP_GETTEXTS[locale] = this_gettext
-
- mg_globals.thread_scope.translations = this_gettext
-
-
-# Force en to be setup before anything else so that
-# mg_globals.translations is never None
-setup_gettext('en')
+ return this_gettext
def pass_to_ugettext(*args, **kwargs):
@@ -124,7 +129,10 @@ def lazy_pass_to_ugettext(*args, **kwargs):
This is useful if you have to define a translation on a module
level but you need it to not translate until the time that it's
- used as a string.
+ used as a string. For example, in:
+ def func(self, message=_('Hello boys and girls'))
+
+ you would want to use the lazy version for _.
"""
return LazyProxy(pass_to_ugettext, *args, **kwargs)
diff --git a/mediagoblin/tools/url.py b/mediagoblin/tools/url.py
index 7477173a..de16536c 100644
--- a/mediagoblin/tools/url.py
+++ b/mediagoblin/tools/url.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
+# This import *is* used; see word.encode('tranlit/long') below.
import translitcodec
@@ -27,6 +28,7 @@ def slugify(text, delim=u'-'):
"""
result = []
for word in _punct_re.split(text.lower()):
+ # Uses translitcodec!
word = word.encode('translit/long')
if word:
result.append(word)