diff options
| author | Malcolm Tredinnick <malcolm.tredinnick@gmail.com> | 2007-05-22 08:06:57 +0000 |
|---|---|---|
| committer | Malcolm Tredinnick <malcolm.tredinnick@gmail.com> | 2007-05-22 08:06:57 +0000 |
| commit | dd52eed2fb8dd93f08f12731330fa30126f800da (patch) | |
| tree | 81cebcc8bfae2a4569d386190cba1aff99cddb21 /django/utils | |
| parent | c2c585f0f0785b71d2efbe6de8b95ff994e59601 (diff) | |
unicode: Made lazy translation objects work properly. Fixed #4295, #4320.
git-svn-id: http://code.djangoproject.com/svn/django/branches/unicode@5314 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/utils')
| -rw-r--r-- | django/utils/dateformat.py | 12 | ||||
| -rw-r--r-- | django/utils/dates.py | 4 | ||||
| -rw-r--r-- | django/utils/encoding.py | 19 | ||||
| -rw-r--r-- | django/utils/functional.py | 18 | ||||
| -rw-r--r-- | django/utils/html.py | 29 | ||||
| -rw-r--r-- | django/utils/text.py | 35 | ||||
| -rw-r--r-- | django/utils/translation/trans_null.py | 2 | ||||
| -rw-r--r-- | django/utils/translation/trans_real.py | 9 |
8 files changed, 85 insertions, 43 deletions
diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index b36e4503cc..8a90e21cb1 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -11,10 +11,10 @@ Usage: >>> """ -from django.utils.dates import MONTHS, MONTHS_3, MONTHS_AP, WEEKDAYS +from django.utils.dates import MONTHS, MONTHS_3, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR from django.utils.tzinfo import LocalTimezone -from django.utils.translation import ugettext as _ -from django.utils.encoding import smart_unicode +from django.utils.translation import string_concat, ugettext as _ +from django.utils.encoding import force_unicode from calendar import isleap, monthrange import re, time @@ -24,9 +24,9 @@ re_escaped = re.compile(r'\\(.)') class Formatter(object): def format(self, formatstr): pieces = [] - for i, piece in enumerate(re_formatchars.split(formatstr)): + for i, piece in enumerate(re_formatchars.split(force_unicode(formatstr))): if i % 2: - pieces.append(smart_unicode(getattr(self, piece)())) + pieces.append(force_unicode(getattr(self, piece)())) elif piece: pieces.append(re_escaped.sub(r'\1', piece)) return u''.join(pieces) @@ -123,7 +123,7 @@ class DateFormat(TimeFormat): def D(self): "Day of the week, textual, 3 letters; e.g. 'Fri'" - return WEEKDAYS[self.data.weekday()][0:3] + return WEEKDAYS_ABBR[self.data.weekday()] def F(self): "Month, textual, long; e.g. 'January'" diff --git a/django/utils/dates.py b/django/utils/dates.py index b2dc48843c..4427af8ca9 100644 --- a/django/utils/dates.py +++ b/django/utils/dates.py @@ -6,6 +6,10 @@ WEEKDAYS = { 0:_('Monday'), 1:_('Tuesday'), 2:_('Wednesday'), 3:_('Thursday'), 4:_('Friday'), 5:_('Saturday'), 6:_('Sunday') } +WEEKDAYS_ABBR = { + 0:_('Mon'), 1:_('Tue'), 2:_('Wed'), 3:_('Thu'), 4:_('Fri'), + 5:_('Sat'), 6:_('Sun') +} WEEKDAYS_REV = { 'monday':0, 'tuesday':1, 'wednesday':2, 'thursday':3, 'friday':4, 'saturday':5, 'sunday':6 diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 5bb23a28bf..b1eaa9b805 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -17,15 +17,16 @@ def smart_unicode(s, encoding='utf-8', errors='strict'): Returns a unicode object representing 's'. Treats bytestrings using the 'encoding' codec. """ - #if isinstance(s, Promise): - # # The input is the result of a gettext_lazy() call, or similar. It will - # # already be encoded in DEFAULT_CHARSET on evaluation and we don't want - # # to evaluate it until render time. - # # FIXME: This isn't totally consistent, because it eventually returns a - # # bytestring rather than a unicode object. It works wherever we use - # # smart_unicode() at the moment. Fixing this requires work in the - # # i18n internals. - # return s + if isinstance(s, Promise): + # The input is the result of a gettext_lazy() call. + return s + return force_unicode(s, encoding, errors) + +def force_unicode(s, encoding='utf-8', errors='strict'): + """ + Similar to smart_unicode, except that lazy instances are resolved to + strings, rather than kept as lazy objects. + """ if not isinstance(s, basestring,): if hasattr(s, '__unicode__'): s = unicode(s) diff --git a/django/utils/functional.py b/django/utils/functional.py index 8127ad8407..e413c5363c 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -3,7 +3,7 @@ def curry(_curried_func, *args, **kwargs): return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs)) return _curried -class Promise: +class Promise(object): """ This is just a base class for the proxy class created in the closure of the lazy function. It can be used to recognize @@ -57,3 +57,19 @@ def lazy(func, *resultclasses): return __proxy__(args, kw) return __wrapper__ + +def allow_lazy(func, *resultclasses): + """ + A decorator that allows a function to be called with one or more lazy + arguments. If none of the args are lazy, the function is evaluated + immediately, otherwise a __proxy__ is returned that will evaluate the + function when needed. + """ + def wrapper(*args, **kwargs): + for arg in list(args) + kwargs.values(): + if isinstance(arg, Promise): + break + else: + return func(*args, **kwargs) + return lazy(func, *resultclasses)(*args, **kwargs) + return wrapper diff --git a/django/utils/html.py b/django/utils/html.py index 44c2809b42..311a4d5c35 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -1,7 +1,8 @@ "HTML utilities suitable for global use." import re, string -from django.utils.encoding import smart_unicode +from django.utils.encoding import force_unicode +from django.utils.functional import allow_lazy # Configuration for urlize() function LEADING_PUNCTUATION = ['(', '<', '<'] @@ -25,31 +26,37 @@ del x # Temporary variable def escape(html): "Returns the given HTML with ampersands, quotes and carets encoded" if not isinstance(html, basestring): - html = smart_unicode(html) - return html.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''') + html = force_unicode(html) + return force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''') +escape = allow_lazy(escape, unicode) def linebreaks(value): "Converts newlines into <p> and <br />s" - value = re.sub(r'\r\n|\r|\n', '\n', value) # normalize newlines + value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines paras = re.split('\n{2,}', value) paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras] return u'\n\n'.join(paras) +linebreaks = allow_lazy(linebreaks, unicode) def strip_tags(value): "Returns the given HTML with all tags stripped" - return re.sub(r'<[^>]*?>', '', value) + return re.sub(r'<[^>]*?>', '', force_unicode(value)) +strip_tags = allow_lazy(strip_tags) def strip_spaces_between_tags(value): "Returns the given HTML with spaces between tags removed" - return re.sub(r'>\s+<', '><', value) + return re.sub(r'>\s+<', '><', force_unicode(value)) +strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode) def strip_entities(value): "Returns the given HTML with all entities (&something;) stripped" - return re.sub(r'&(?:\w+|#\d);', '', value) + return re.sub(r'&(?:\w+|#\d);', '', force_unicode(value)) +strip_entities = allow_lazy(strip_entities, unicode) def fix_ampersands(value): "Returns the given HTML with all unencoded ampersands encoded correctly" - return unencoded_ampersands_re.sub('&', value) + return unencoded_ampersands_re.sub('&', force_unicode(value)) +fix_ampersands = allow_lazy(fix_ampersands, unicode) def urlize(text, trim_url_limit=None, nofollow=False): """ @@ -65,7 +72,7 @@ def urlize(text, trim_url_limit=None, nofollow=False): attribute. """ trim_url = lambda x, limit=trim_url_limit: limit is not None and (x[:limit] + (len(x) >=limit and '...' or '')) or x - words = word_split_re.split(text) + words = word_split_re.split(force_unicode(text)) nofollow_attr = nofollow and ' rel="nofollow"' or '' for i, word in enumerate(words): match = punctuation_re.match(word) @@ -83,6 +90,7 @@ def urlize(text, trim_url_limit=None, nofollow=False): if lead + middle + trail != word: words[i] = lead + middle + trail return u''.join(words) +urlize = allow_lazy(urlize, unicode) def clean_html(text): """ @@ -97,7 +105,7 @@ def clean_html(text): bottom of the text. """ from django.utils.text import normalize_newlines - text = normalize_newlines(text) + text = normalize_newlines(force_unicode(text)) text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text) text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text) text = fix_ampersands(text) @@ -115,4 +123,5 @@ def clean_html(text): # Remove stuff like "<p> </p>", but only if it's at the bottom of the text. text = trailing_empty_content_re.sub('', text) return text +clean_html = allow_lazy(clean_html, unicode) diff --git a/django/utils/text.py b/django/utils/text.py index a355744743..979775be77 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -1,16 +1,18 @@ import re from django.conf import settings -from django.utils.encoding import smart_unicode +from django.utils.encoding import force_unicode +from django.utils.functional import allow_lazy # Capitalizes the first letter of a string. -capfirst = lambda x: x and x[0].upper() + x[1:] +capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:] +capfirst = allow_lazy(capfirst, unicode) def wrap(text, width): """ A word-wrap function that preserves existing line breaks and most spaces in the text. Expects that existing line breaks are posix newlines. """ - text = smart_unicode(text) + text = force_unicode(text) def _generator(): it = iter(text.split(' ')) word = it.next() @@ -31,10 +33,11 @@ def wrap(text, width): pos = len(lines[-1]) yield word return u''.join(_generator()) +wrap = allow_lazy(wrap, unicode) def truncate_words(s, num): "Truncates a string after a certain number of words." - s = smart_unicode(s) + s = force_unicode(s) length = int(num) words = s.split() if len(words) > length: @@ -42,6 +45,7 @@ def truncate_words(s, num): if not words[-1].endswith('...'): words.append('...') return u' '.join(words) +truncate_words = allow_lazy(truncate_words, unicode) def truncate_html_words(s, num): """ @@ -49,7 +53,7 @@ def truncate_html_words(s, num): comments). Closes opened tags if they were correctly closed in the given html. """ - s = smart_unicode(s) + s = force_unicode(s) length = int(num) if length <= 0: return u'' @@ -104,6 +108,7 @@ def truncate_html_words(s, num): out += '</%s>' % tag # Return string return out +truncate_html_words = allow_lazy(truncate_html_words, unicode) def get_valid_filename(s): """ @@ -114,8 +119,9 @@ def get_valid_filename(s): >>> get_valid_filename("john's portrait in 2004.jpg") 'johns_portrait_in_2004.jpg' """ - s = smart_unicode(s).strip().replace(' ', '_') + s = force_unicode(s).strip().replace(' ', '_') return re.sub(r'[^-A-Za-z0-9_.]', '', s) +get_valid_filename = allow_lazy(get_valid_filename, unicode) def get_text_list(list_, last_word=u'or'): """ @@ -131,18 +137,21 @@ def get_text_list(list_, last_word=u'or'): '' """ if len(list_) == 0: return u'' - if len(list_) == 1: return smart_unicode(list_[0]) - return u'%s %s %s' % (', '.join([smart_unicode(i) for i in list_][:-1]), smart_unicode(last_word), smart_unicode(list_[-1])) + if len(list_) == 1: return force_unicode(list_[0]) + return u'%s %s %s' % (', '.join([force_unicode(i) for i in list_][:-1]), force_unicode(last_word), force_unicode(list_[-1])) +get_text_list = allow_lazy(get_text_list, unicode) def normalize_newlines(text): - return smart_unicode(re.sub(r'\r\n|\r|\n', '\n', text)) + return force_unicode(re.sub(r'\r\n|\r|\n', '\n', text)) +normalize_newlines = allow_lazy(normalize_newlines, unicode) def recapitalize(text): "Recapitalizes text, placing caps after end-of-sentence punctuation." - text = smart_unicode(text).lower() + text = force_unicode(text).lower() capsRE = re.compile(r'(?:^|(?<=[\.\?\!] ))([a-z])') text = capsRE.sub(lambda x: x.group(1).upper(), text) return text +recapitalize = allow_lazy(recapitalize) def phone2numeric(phone): "Converts a phone number with letters into its numeric equivalent." @@ -153,6 +162,7 @@ def phone2numeric(phone): 's': '7', 'r': '7', 'u': '8', 't': '8', 'w': '9', 'v': '8', 'y': '9', 'x': '9'}.get(m.group(0).lower()) return letters.sub(char2number, phone) +phone2numeric = allow_lazy(phone2numeric) # From http://www.xhaus.com/alan/python/httpcomp.html#gzip # Used with permission. @@ -183,6 +193,7 @@ def javascript_quote(s, quote_double_quotes=False): if quote_double_quotes: s = s.replace('"', '"') return str(ustring_re.sub(fix, s)) +javascript_quote = allow_lazy(javascript_quote, unicode) smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)') def smart_split(text): @@ -195,7 +206,7 @@ def smart_split(text): >>> list(smart_split('This is "a person\'s" test.')) ['This', 'is', '"a person\'s"', 'test.'] """ - text = smart_unicode(text) + text = force_unicode(text) for bit in smart_split_re.finditer(text): bit = bit.group(0) if bit[0] == '"' and bit[-1] == '"': @@ -204,3 +215,5 @@ def smart_split(text): yield "'" + bit[1:-1].replace("\\'", "'").replace("\\\\", "\\") + "'" else: yield bit +smart_split = allow_lazy(smart_split, unicode) + diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py index a09f5ce8df..c6fe16d37f 100644 --- a/django/utils/translation/trans_null.py +++ b/django/utils/translation/trans_null.py @@ -13,7 +13,7 @@ ngettext_lazy = ngettext def ungettext(singular, plural, number): return smart_unicode(ngettext(singular, plural, number)) -string_concat = lambda *strings: ''.join([str(el) for el in strings]) +string_concat = lambda *strings: u''.join([smart_unicode(el) for el in strings]) activate = lambda x: None deactivate = deactivate_all = install = lambda: None get_language = lambda: settings.LANGUAGE_CODE diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index b8411366f9..765152afce 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -3,7 +3,7 @@ import os, re, sys import gettext as gettext_module from cStringIO import StringIO -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import force_unicode try: import threading @@ -516,8 +516,7 @@ def templatize(src): def string_concat(*strings): """" - lazy variant of string concatenation, needed for translations that are - constructed from multiple parts. Handles lazy strings and non-strings by - first turning all arguments to unicode, before joining them. + Lazy variant of string concatenation, needed for translations that are + constructed from multiple parts. """ - return u''.join([smart_unicode(el) for el in strings]) + return u''.join([force_unicode(s) for s in strings]) |
