summaryrefslogtreecommitdiff
path: root/django/utils
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2007-10-26 20:47:20 +0000
committerJustin Bronn <jbronn@gmail.com>2007-10-26 20:47:20 +0000
commit4ffbddf92d89c3b31cef90043721184a501cd454 (patch)
treedb8131d40b0a5437270a6b1e8d579113ab3508e8 /django/utils
parentf66ee9d0065838a0f6c9c76203a775a78446cdf7 (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.py22
-rw-r--r--django/utils/datastructures.py70
-rw-r--r--django/utils/encoding.py2
-rw-r--r--django/utils/feedgenerator.py10
-rw-r--r--django/utils/functional.py16
-rw-r--r--django/utils/http.py3
-rw-r--r--django/utils/translation/__init__.py5
-rw-r--r--django/utils/translation/trans_null.py2
-rw-r--r--django/utils/translation/trans_real.py125
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
+