diff options
Diffstat (limited to 'mediagoblin/tools')
-rw-r--r-- | mediagoblin/tools/licenses.py | 84 | ||||
-rw-r--r-- | mediagoblin/tools/pluginapi.py | 13 | ||||
-rw-r--r-- | mediagoblin/tools/response.py | 34 | ||||
-rw-r--r-- | mediagoblin/tools/template.py | 39 | ||||
-rw-r--r-- | mediagoblin/tools/text.py | 26 | ||||
-rw-r--r-- | mediagoblin/tools/translate.py | 64 | ||||
-rw-r--r-- | mediagoblin/tools/url.py | 2 |
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) |