diff options
| author | Justin Bronn <jbronn@gmail.com> | 2007-10-26 20:47:20 +0000 |
|---|---|---|
| committer | Justin Bronn <jbronn@gmail.com> | 2007-10-26 20:47:20 +0000 |
| commit | 4ffbddf92d89c3b31cef90043721184a501cd454 (patch) | |
| tree | db8131d40b0a5437270a6b1e8d579113ab3508e8 /django/utils | |
| parent | f66ee9d0065838a0f6c9c76203a775a78446cdf7 (diff) | |
gis: Merged revisions 6525-6613 via svnmerge from [repos:django/trunk trunk].
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6615 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/utils')
| -rw-r--r-- | django/utils/checksums.py | 22 | ||||
| -rw-r--r-- | django/utils/datastructures.py | 70 | ||||
| -rw-r--r-- | django/utils/encoding.py | 2 | ||||
| -rw-r--r-- | django/utils/feedgenerator.py | 10 | ||||
| -rw-r--r-- | django/utils/functional.py | 16 | ||||
| -rw-r--r-- | django/utils/http.py | 3 | ||||
| -rw-r--r-- | django/utils/translation/__init__.py | 5 | ||||
| -rw-r--r-- | django/utils/translation/trans_null.py | 2 | ||||
| -rw-r--r-- | django/utils/translation/trans_real.py | 125 |
9 files changed, 163 insertions, 92 deletions
diff --git a/django/utils/checksums.py b/django/utils/checksums.py new file mode 100644 index 0000000000..970f563f38 --- /dev/null +++ b/django/utils/checksums.py @@ -0,0 +1,22 @@ +""" +Common checksum routines (used in multiple localflavor/ cases, for example). +""" + +__all__ = ['luhn',] + +LUHN_ODD_LOOKUP = (0, 2, 4, 6, 8, 1, 3, 5, 7, 9) # sum_of_digits(index * 2) + +def luhn(candidate): + """ + Checks a candidate number for validity according to the Luhn + algorithm (used in validation of, for example, credit cards). + Both numeric and string candidates are accepted. + """ + if not isinstance(candidate, basestring): + candidate = str(candidate) + try: + evens = sum([int(c) for c in candidate[-1::-2]]) + odds = sum([LUHN_ODD_LOOKUP[int(c)] for c in candidate[-2::-2]]) + return ((evens + odds) % 10 == 0) + except ValueError: # Raised if an int conversion fails + return False diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index d750f098f0..e0835b2cfc 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -42,22 +42,32 @@ class MergeDict(object): if key in dict: return True return False - + __contains__ = has_key def copy(self): - """ returns a copy of this object""" + """Returns a copy of this object.""" return self.__copy__() class SortedDict(dict): - "A dictionary that keeps its keys in the order in which they're inserted." + """ + A dictionary that keeps its keys in the order in which they're inserted. + """ def __init__(self, data=None): - if data is None: data = {} + if data is None: + data = {} dict.__init__(self, data) if isinstance(data, dict): self.keyOrder = data.keys() else: - self.keyOrder=[key for key, value in data] + self.keyOrder = [key for key, value in data] + + def __deepcopy__(self,memo): + from copy import deepcopy + obj = self.__class__() + for k, v in self.items(): + obj[k] = deepcopy(v, memo) + return obj def __setitem__(self, key, value): dict.__setitem__(self, key, value) @@ -72,6 +82,20 @@ class SortedDict(dict): for k in self.keyOrder: yield k + def pop(self, k, *args): + result = dict.pop(self, k, *args) + try: + self.keyOrder.remove(k) + except ValueError: + # Key wasn't in the dictionary in the first place. No problem. + pass + return result + + def popitem(self): + result = dict.popitem(self) + self.keyOrder.remove(result[0]) + return result + def items(self): return zip(self.keyOrder, self.values()) @@ -102,20 +126,21 @@ class SortedDict(dict): return dict.setdefault(self, key, default) def value_for_index(self, index): - "Returns the value of the item at the given zero-based index." + """Returns the value of the item at the given zero-based index.""" return self[self.keyOrder[index]] def insert(self, index, key, value): - "Inserts the key, value pair before the item with the given index." + """Inserts the key, value pair before the item with the given index.""" if key in self.keyOrder: n = self.keyOrder.index(key) del self.keyOrder[n] - if n < index: index -= 1 + if n < index: + index -= 1 self.keyOrder.insert(index, key) dict.__setitem__(self, key, value) def copy(self): - "Returns a copy of this object." + """Returns a copy of this object.""" # This way of initializing the copy means it works for subclasses, too. obj = self.__class__(self) obj.keyOrder = self.keyOrder @@ -133,7 +158,8 @@ class MultiValueDictKeyError(KeyError): class MultiValueDict(dict): """ - A subclass of dictionary customized to handle multiple values for the same key. + A subclass of dictionary customized to handle multiple values for the + same key. >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']}) >>> d['name'] @@ -176,15 +202,17 @@ class MultiValueDict(dict): def __deepcopy__(self, memo=None): import copy - if memo is None: memo = {} + if memo is None: + memo = {} result = self.__class__() memo[id(self)] = result for key, value in dict.items(self): - dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo)) + dict.__setitem__(result, copy.deepcopy(key, memo), + copy.deepcopy(value, memo)) return result def get(self, key, default=None): - "Returns the default value if the requested data doesn't exist" + """Returns the default value if the requested data doesn't exist.""" try: val = self[key] except KeyError: @@ -194,7 +222,7 @@ class MultiValueDict(dict): return val def getlist(self, key): - "Returns an empty list if the requested data doesn't exist" + """Returns an empty list if the requested data doesn't exist.""" try: return dict.__getitem__(self, key) except KeyError: @@ -214,7 +242,7 @@ class MultiValueDict(dict): return self.getlist(key) def appendlist(self, key, value): - "Appends an item to the internal list associated with key" + """Appends an item to the internal list associated with key.""" self.setlistdefault(key, []) dict.__setitem__(self, key, self.getlist(key) + [value]) @@ -226,19 +254,22 @@ class MultiValueDict(dict): return [(key, self[key]) for key in self.keys()] def lists(self): - "Returns a list of (key, list) pairs." + """Returns a list of (key, list) pairs.""" return dict.items(self) def values(self): - "Returns a list of the last value on every key list." + """Returns a list of the last value on every key list.""" return [self[key] for key in self.keys()] def copy(self): - "Returns a copy of this object." + """Returns a copy of this object.""" return self.__deepcopy__() def update(self, *args, **kwargs): - "update() extends rather than replaces existing key lists. Also accepts keyword args." + """ + update() extends rather than replaces existing key lists. + Also accepts keyword args. + """ if len(args) > 1: raise TypeError, "update expected at most 1 arguments, got %d" % len(args) if args: @@ -299,4 +330,3 @@ class FileDict(dict): d = dict(self, content='<omitted>') return dict.__repr__(d) return dict.__repr__(self) - diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 6daae4386d..2bd1ef6563 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -31,7 +31,7 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): If strings_only is True, don't convert (some) non-string-like objects. """ - if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.time, float)): + if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)): return s if not isinstance(s, basestring,): if hasattr(s, '__unicode__'): diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index e296331324..3e0826c968 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -45,7 +45,7 @@ class SyndicationFeed(object): "Base class for all syndication feeds. Subclasses should provide write()" def __init__(self, title, link, description, language=None, author_email=None, author_name=None, author_link=None, subtitle=None, categories=None, - feed_url=None, feed_copyright=None, feed_guid=None): + feed_url=None, feed_copyright=None, feed_guid=None, ttl=None): to_unicode = lambda s: force_unicode(s, strings_only=True) if categories: categories = [force_unicode(c) for c in categories] @@ -62,12 +62,13 @@ class SyndicationFeed(object): 'feed_url': iri_to_uri(feed_url), 'feed_copyright': to_unicode(feed_copyright), 'id': feed_guid or link, + 'ttl': ttl, } self.items = [] def add_item(self, title, link, description, author_email=None, author_name=None, author_link=None, pubdate=None, comments=None, - unique_id=None, enclosure=None, categories=(), item_copyright=None): + unique_id=None, enclosure=None, categories=(), item_copyright=None, ttl=None): """ Adds an item to the feed. All args are expected to be Python Unicode objects except pubdate, which is a datetime.datetime object, and @@ -89,6 +90,7 @@ class SyndicationFeed(object): 'enclosure': enclosure, 'categories': categories or (), 'item_copyright': to_unicode(item_copyright), + 'ttl': ttl, }) def num_items(self): @@ -146,6 +148,8 @@ class RssFeed(SyndicationFeed): if self.feed['feed_copyright'] is not None: handler.addQuickElement(u"copyright", self.feed['feed_copyright']) handler.addQuickElement(u"lastBuildDate", rfc2822_date(self.latest_post_date()).decode('ascii')) + if self.feed['ttl'] is not None: + handler.addQuickElement(u"ttl", self.feed['ttl']) self.write_items(handler) self.endChannelElement(handler) handler.endElement(u"rss") @@ -190,6 +194,8 @@ class Rss201rev2Feed(RssFeed): handler.addQuickElement(u"comments", item['comments']) if item['unique_id'] is not None: handler.addQuickElement(u"guid", item['unique_id']) + if item['ttl'] is not None: + handler.addQuickElement(u"ttl", item['ttl']) # Enclosure. if item['enclosure'] is not None: diff --git a/django/utils/functional.py b/django/utils/functional.py index f4580e7bd5..e0c862b0b7 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -53,7 +53,11 @@ def lazy(func, *resultclasses): self._delegate_unicode = unicode in resultclasses assert not (self._delegate_str and self._delegate_unicode), "Cannot call lazy() with both str and unicode return types." if self._delegate_unicode: - self.__unicode__ = self.__unicode_cast + # Each call to lazy() makes a new __proxy__ object, so this + # doesn't interfere with any other lazy() results. + __proxy__.__unicode__ = __proxy__.__unicode_cast + elif self._delegate_str: + __proxy__.__str__ = __proxy__.__str_cast def __promise__(self, klass, funcname, func): # Builds a wrapper around some magic method and registers that magic @@ -72,14 +76,8 @@ def lazy(func, *resultclasses): def __unicode_cast(self): return self.__func(*self.__args, **self.__kw) - def __str__(self): - # As __str__ is always a method on the type (class), it is looked - # up (and found) there first. So we can't just assign to it on a - # per-instance basis in __init__. - if self._delegate_str: - return str(self.__func(*self.__args, **self.__kw)) - else: - return Promise.__str__(self) + def __str_cast(self): + return str(self.__func(*self.__args, **self.__kw)) def __cmp__(self, rhs): if self._delegate_str: diff --git a/django/utils/http.py b/django/utils/http.py index 4c3b6af868..4912c9c46a 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -9,7 +9,8 @@ def urlquote(url, safe='/'): can safely be used as part of an argument to a subsequent iri_to_uri() call without double-quoting occurring. """ - return force_unicode(urllib.quote(smart_str(url))) + return force_unicode(urllib.quote(smart_str(url), safe)) + urlquote = allow_lazy(urlquote, unicode) def urlquote_plus(url, safe=''): diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index 9ca7419e1e..43ce3e591a 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -8,7 +8,7 @@ __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext', 'ngettext_lazy', 'string_concat', 'activate', 'deactivate', 'get_language', 'get_language_bidi', 'get_date_formats', 'get_partial_date_formats', 'check_for_language', 'to_locale', - 'get_language_from_request', 'install', 'templatize', 'ugettext', + 'get_language_from_request', 'templatize', 'ugettext', 'ungettext', 'deactivate_all'] # Here be dragons, so a short explanation of the logic won't hurt: @@ -96,9 +96,6 @@ def to_locale(language): def get_language_from_request(request): return real_get_language_from_request(request) -def install(): - return real_install() - def templatize(src): return real_templatize(src) diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py index 771a80e99c..0b62907e44 100644 --- a/django/utils/translation/trans_null.py +++ b/django/utils/translation/trans_null.py @@ -14,7 +14,7 @@ def ungettext(singular, plural, number): return force_unicode(ngettext(singular, plural, number)) activate = lambda x: None -deactivate = deactivate_all = install = lambda: None +deactivate = deactivate_all = lambda: None get_language = lambda: settings.LANGUAGE_CODE get_language_bidi = lambda: settings.LANGUAGE_CODE in settings.LANGUAGES_BIDI get_date_formats = lambda: (settings.DATE_FORMAT, settings.DATETIME_FORMAT, settings.TIME_FORMAT) diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 250af04e8c..98b4769031 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -1,8 +1,12 @@ "Translation helper functions" -import os, re, sys +import locale +import os +import re +import sys import gettext as gettext_module from cStringIO import StringIO + from django.utils.encoding import force_unicode try: @@ -25,15 +29,25 @@ _active = {} # The default translation is based on the settings file. _default = None -# This is a cache for accept-header to translation object mappings to prevent -# the accept parser to run multiple times for one user. +# This is a cache for normalised accept-header languages to prevent multiple +# file lookups when checking the same locale on repeated requests. _accepted = {} -def to_locale(language): +# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9. +accept_language_re = re.compile(r''' + ([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*" + (?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8" + (?:\s*,\s*|$) # Multiple accepts per header. + ''', re.VERBOSE) + +def to_locale(language, to_lower=False): "Turns a language name (en-us) into a locale name (en_US)." p = language.find('-') if p >= 0: - return language[:p].lower()+'_'+language[p+1:].upper() + if to_lower: + return language[:p].lower()+'_'+language[p+1:].lower() + else: + return language[:p].lower()+'_'+language[p+1:].upper() else: return language.lower() @@ -249,8 +263,10 @@ def catalog(): def do_translate(message, translation_function): """ - Translate 'message' using the given 'translation_function' name -- which - will be either gettext or ugettext. + Translates 'message' using the given 'translation_function' name -- which + will be either gettext or ugettext. It uses the current thread to find the + translation object to use. If no current translation is activated, the + message will be run through the default translation object. """ global _default, _active t = _active.get(currentThread(), None) @@ -262,12 +278,6 @@ def do_translate(message, translation_function): return getattr(_default, translation_function)(message) def gettext(message): - """ - This function will be patched into the builtins module to provide the _ - helper function. It will use the current thread as a discriminator to find - the translation object to use. If no current translation is activated, the - message will be run through the default translation object. - """ return do_translate(message, 'gettext') def ugettext(message): @@ -338,46 +348,40 @@ def get_language_from_request(request): if lang_code in supported and lang_code is not None and check_for_language(lang_code): return lang_code - lang_code = request.COOKIES.get('django_language', None) - if lang_code in supported and lang_code is not None and check_for_language(lang_code): + lang_code = request.COOKIES.get('django_language') + if lang_code and lang_code in supported and check_for_language(lang_code): return lang_code - accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None) - if accept is not None: + accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '') + for lang, unused in parse_accept_lang_header(accept): + if lang == '*': + break - t = _accepted.get(accept, None) - if t is not None: - return t + # We have a very restricted form for our language files (no encoding + # specifier, since they all must be UTF-8 and only one possible + # language each time. So we avoid the overhead of gettext.find() and + # look up the MO file manually. - def _parsed(el): - p = el.find(';q=') - if p >= 0: - lang = el[:p].strip() - order = int(float(el[p+3:].strip())*100) - else: - lang = el - order = 100 - p = lang.find('-') - if p >= 0: - mainlang = lang[:p] - else: - mainlang = lang - return (lang, mainlang, order) + normalized = locale.locale_alias.get(to_locale(lang, True)) + if not normalized: + continue + + # Remove the default encoding from locale_alias + normalized = normalized.split('.')[0] - langs = [_parsed(el) for el in accept.split(',')] - langs.sort(lambda a,b: -1*cmp(a[2], b[2])) + if normalized in _accepted: + # We've seen this locale before and have an MO file for it, so no + # need to check again. + return _accepted[normalized] - for lang, mainlang, order in langs: - if lang in supported or mainlang in supported: - langfile = gettext_module.find('django', globalpath, [to_locale(lang)]) - if langfile: - # reconstruct the actual language from the language - # filename, because otherwise we might incorrectly - # report de_DE if we only have de available, but - # did find de_DE because of language normalization - lang = langfile[len(globalpath):].split(os.path.sep)[1] - _accepted[accept] = lang - return lang + for lang in (normalized, normalized.split('_')[0]): + if lang not in supported: + continue + langfile = os.path.join(globalpath, lang, 'LC_MESSAGES', + 'django.mo') + if os.path.exists(langfile): + _accepted[normalized] = lang + return lang return settings.LANGUAGE_CODE @@ -414,13 +418,6 @@ def get_partial_date_formats(): month_day_format = settings.MONTH_DAY_FORMAT return year_month_format, month_day_format -def install(): - """ - Installs the gettext function as the default translation function under - the name '_'. - """ - __builtins__['_'] = gettext - dot_re = re.compile(r'\S') def blankout(src, char): """ @@ -516,3 +513,23 @@ def templatize(src): out.write(blankout(t.contents, 'X')) return out.getvalue() +def parse_accept_lang_header(lang_string): + """ + Parses the lang_string, which is the body of an HTTP Accept-Language + header, and returns a list of (lang, q-value), ordered by 'q' values. + + Any format errors in lang_string results in an empty list being returned. + """ + result = [] + pieces = accept_language_re.split(lang_string) + if pieces[-1]: + return [] + for i in range(0, len(pieces) - 1, 3): + first, lang, priority = pieces[i : i + 3] + if first: + return [] + priority = priority and float(priority) or 1.0 + result.append((lang, priority)) + result.sort(lambda x, y: -cmp(x[1], y[1])) + return result + |
