mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes. Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702 git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
@@ -58,8 +58,10 @@ import re
|
||||
from inspect import getargspec
|
||||
from django.conf import settings
|
||||
from django.template.context import Context, RequestContext, ContextPopException
|
||||
from django.utils.functional import curry
|
||||
from django.utils.functional import curry, Promise
|
||||
from django.utils.text import smart_split
|
||||
from django.utils.encoding import smart_unicode, force_unicode
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
|
||||
|
||||
@@ -122,6 +124,9 @@ class TemplateSyntaxError(Exception):
|
||||
class TemplateDoesNotExist(Exception):
|
||||
pass
|
||||
|
||||
class TemplateEncodingError(Exception):
|
||||
pass
|
||||
|
||||
class VariableDoesNotExist(Exception):
|
||||
|
||||
def __init__(self, msg, params=()):
|
||||
@@ -155,6 +160,10 @@ class StringOrigin(Origin):
|
||||
class Template(object):
|
||||
def __init__(self, template_string, origin=None, name='<Unknown Template>'):
|
||||
"Compilation stage"
|
||||
try:
|
||||
template_string = smart_unicode(template_string)
|
||||
except UnicodeDecodeError:
|
||||
raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
|
||||
if settings.TEMPLATE_DEBUG and origin == None:
|
||||
origin = StringOrigin(template_string)
|
||||
# Could do some crazy stack-frame stuff to record where this string
|
||||
@@ -693,6 +702,13 @@ def resolve_variable(path, context):
|
||||
else:
|
||||
raise
|
||||
del bits[0]
|
||||
if isinstance(current, (basestring, Promise)):
|
||||
try:
|
||||
current = force_unicode(current)
|
||||
except UnicodeDecodeError:
|
||||
# Failing to convert to unicode can happen sometimes (e.g. debug
|
||||
# tracebacks). So we allow it in this particular instance.
|
||||
pass
|
||||
return current
|
||||
|
||||
class Node(object):
|
||||
@@ -720,7 +736,7 @@ class NodeList(list):
|
||||
bits.append(self.render_node(node, context))
|
||||
else:
|
||||
bits.append(node)
|
||||
return ''.join(bits)
|
||||
return ''.join([force_unicode(b) for b in bits])
|
||||
|
||||
def get_nodes_by_type(self, nodetype):
|
||||
"Return a list of all nodes of the given type"
|
||||
@@ -730,7 +746,7 @@ class NodeList(list):
|
||||
return nodes
|
||||
|
||||
def render_node(self, node, context):
|
||||
return(node.render(context))
|
||||
return node.render(context)
|
||||
|
||||
class DebugNodeList(NodeList):
|
||||
def render_node(self, node, context):
|
||||
@@ -765,32 +781,17 @@ class VariableNode(Node):
|
||||
def __repr__(self):
|
||||
return "<Variable Node: %s>" % self.filter_expression
|
||||
|
||||
def encode_output(self, output):
|
||||
# Check type so that we don't run str() on a Unicode object
|
||||
if not isinstance(output, basestring):
|
||||
try:
|
||||
return str(output)
|
||||
except UnicodeEncodeError:
|
||||
# If __str__() returns a Unicode object, convert it to bytestring.
|
||||
return unicode(output).encode(settings.DEFAULT_CHARSET)
|
||||
elif isinstance(output, unicode):
|
||||
return output.encode(settings.DEFAULT_CHARSET)
|
||||
else:
|
||||
return output
|
||||
|
||||
def render(self, context):
|
||||
output = self.filter_expression.resolve(context)
|
||||
return self.encode_output(output)
|
||||
return self.filter_expression.resolve(context)
|
||||
|
||||
class DebugVariableNode(VariableNode):
|
||||
def render(self, context):
|
||||
try:
|
||||
output = self.filter_expression.resolve(context)
|
||||
return self.filter_expression.resolve(context)
|
||||
except TemplateSyntaxError, e:
|
||||
if not hasattr(e, 'source'):
|
||||
e.source = self.source
|
||||
raise
|
||||
return self.encode_output(output)
|
||||
|
||||
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
|
||||
"Returns a template.Node subclass."
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
from django.template import resolve_variable, Library
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext, ngettext
|
||||
from django.utils.translation import ugettext, ungettext
|
||||
from django.utils.encoding import force_unicode, smart_str, iri_to_uri
|
||||
import re
|
||||
import random as random_module
|
||||
|
||||
@@ -12,29 +13,18 @@ register = Library()
|
||||
# STRING DECORATOR #
|
||||
#######################
|
||||
|
||||
def smart_string(obj):
|
||||
# FUTURE: Unicode strings should probably be normalized to a specific
|
||||
# encoding and non-unicode strings should be converted to unicode too.
|
||||
# if isinstance(obj, unicode):
|
||||
# obj = obj.encode(settings.DEFAULT_CHARSET)
|
||||
# else:
|
||||
# obj = unicode(obj, settings.DEFAULT_CHARSET)
|
||||
# FUTURE: Replace dumb string logic below with cool unicode logic above.
|
||||
if not isinstance(obj, basestring):
|
||||
obj = str(obj)
|
||||
return obj
|
||||
|
||||
def stringfilter(func):
|
||||
"""
|
||||
Decorator for filters which should only receive strings. The object passed
|
||||
as the first positional argument will be converted to a string.
|
||||
Decorator for filters which should only receive unicode objects. The object
|
||||
passed as the first positional argument will be converted to a unicode
|
||||
object.
|
||||
"""
|
||||
def _dec(*args, **kwargs):
|
||||
if args:
|
||||
args = list(args)
|
||||
args[0] = smart_string(args[0])
|
||||
args[0] = force_unicode(args[0])
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
# Include a reference to the real function (used to check original
|
||||
# arguments by the template parser).
|
||||
_dec._decorated_function = getattr(func, '_decorated_function', func)
|
||||
@@ -54,7 +44,7 @@ def capfirst(value):
|
||||
"Capitalizes the first character of the value"
|
||||
return value and value[0].upper() + value[1:]
|
||||
capfirst = stringfilter(capfirst)
|
||||
|
||||
|
||||
def fix_ampersands(value):
|
||||
"Replaces ampersands with ``&`` entities"
|
||||
from django.utils.html import fix_ampersands
|
||||
@@ -83,27 +73,32 @@ def floatformat(text, arg=-1):
|
||||
try:
|
||||
f = float(text)
|
||||
except ValueError:
|
||||
return ''
|
||||
return u''
|
||||
try:
|
||||
d = int(arg)
|
||||
except ValueError:
|
||||
return smart_string(f)
|
||||
return force_unicode(f)
|
||||
m = f - int(f)
|
||||
if not m and d < 0:
|
||||
return '%d' % int(f)
|
||||
return u'%d' % int(f)
|
||||
else:
|
||||
formatstr = '%%.%df' % abs(d)
|
||||
formatstr = u'%%.%df' % abs(d)
|
||||
return formatstr % f
|
||||
|
||||
def iriencode(value):
|
||||
"Escapes an IRI value for use in a URL"
|
||||
return force_unicode(iri_to_uri(value))
|
||||
iriencode = stringfilter(iriencode)
|
||||
|
||||
def linenumbers(value):
|
||||
"Displays text with line numbers"
|
||||
from django.utils.html import escape
|
||||
lines = value.split('\n')
|
||||
lines = value.split(u'\n')
|
||||
# Find the maximum width of the line count, for use with zero padding string format command
|
||||
width = str(len(str(len(lines))))
|
||||
width = unicode(len(unicode(len(lines))))
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
|
||||
return '\n'.join(lines)
|
||||
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
|
||||
return u'\n'.join(lines)
|
||||
linenumbers = stringfilter(linenumbers)
|
||||
|
||||
def lower(value):
|
||||
@@ -120,8 +115,13 @@ def make_list(value):
|
||||
make_list = stringfilter(make_list)
|
||||
|
||||
def slugify(value):
|
||||
"Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
|
||||
value = re.sub('[^\w\s-]', '', value).strip().lower()
|
||||
"""
|
||||
Normalizes string, converts to lowercase, removes non-alpha chars and
|
||||
converts spaces to hyphens.
|
||||
"""
|
||||
import unicodedata
|
||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
|
||||
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
|
||||
return re.sub('[-\s]+', '-', value)
|
||||
slugify = stringfilter(slugify)
|
||||
|
||||
@@ -135,9 +135,9 @@ def stringformat(value, arg):
|
||||
of Python string formatting
|
||||
"""
|
||||
try:
|
||||
return ("%" + str(arg)) % value
|
||||
return (u"%" + unicode(arg)) % value
|
||||
except (ValueError, TypeError):
|
||||
return ""
|
||||
return u""
|
||||
|
||||
def title(value):
|
||||
"Converts a string into titlecase"
|
||||
@@ -155,8 +155,6 @@ def truncatewords(value, arg):
|
||||
length = int(arg)
|
||||
except ValueError: # invalid literal for int()
|
||||
return value # Fail silently.
|
||||
if not isinstance(value, basestring):
|
||||
value = str(value)
|
||||
return truncate_words(value, length)
|
||||
truncatewords = stringfilter(truncatewords)
|
||||
|
||||
@@ -171,8 +169,6 @@ def truncatewords_html(value, arg):
|
||||
length = int(arg)
|
||||
except ValueError: # invalid literal for int()
|
||||
return value # Fail silently.
|
||||
if not isinstance(value, basestring):
|
||||
value = str(value)
|
||||
return truncate_html_words(value, length)
|
||||
truncatewords_html = stringfilter(truncatewords_html)
|
||||
|
||||
@@ -183,10 +179,8 @@ upper = stringfilter(upper)
|
||||
|
||||
def urlencode(value):
|
||||
"Escapes a value for use in a URL"
|
||||
import urllib
|
||||
if not isinstance(value, basestring):
|
||||
value = str(value)
|
||||
return urllib.quote(value)
|
||||
from django.utils.http import urlquote
|
||||
return urlquote(value)
|
||||
urlencode = stringfilter(urlencode)
|
||||
|
||||
def urlize(value):
|
||||
@@ -246,7 +240,7 @@ center = stringfilter(center)
|
||||
|
||||
def cut(value, arg):
|
||||
"Removes all values of arg from the given string"
|
||||
return value.replace(arg, '')
|
||||
return value.replace(arg, u'')
|
||||
cut = stringfilter(cut)
|
||||
|
||||
###################
|
||||
@@ -273,11 +267,11 @@ linebreaksbr = stringfilter(linebreaksbr)
|
||||
def removetags(value, tags):
|
||||
"Removes a space separated list of [X]HTML tags from the output"
|
||||
tags = [re.escape(tag) for tag in tags.split()]
|
||||
tags_re = '(%s)' % '|'.join(tags)
|
||||
starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re)
|
||||
endtag_re = re.compile('</%s>' % tags_re)
|
||||
value = starttag_re.sub('', value)
|
||||
value = endtag_re.sub('', value)
|
||||
tags_re = u'(%s)' % u'|'.join(tags)
|
||||
starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
|
||||
endtag_re = re.compile(u'</%s>' % tags_re)
|
||||
value = starttag_re.sub(u'', value)
|
||||
value = endtag_re.sub(u'', value)
|
||||
return value
|
||||
removetags = stringfilter(removetags)
|
||||
|
||||
@@ -296,7 +290,7 @@ def dictsort(value, arg):
|
||||
Takes a list of dicts, returns that list sorted by the property given in
|
||||
the argument.
|
||||
"""
|
||||
decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
|
||||
decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value]
|
||||
decorated.sort()
|
||||
return [item[1] for item in decorated]
|
||||
|
||||
@@ -305,7 +299,7 @@ def dictsortreversed(value, arg):
|
||||
Takes a list of dicts, returns that list sorted in reverse order by the
|
||||
property given in the argument.
|
||||
"""
|
||||
decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
|
||||
decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value]
|
||||
decorated.sort()
|
||||
decorated.reverse()
|
||||
return [item[1] for item in decorated]
|
||||
@@ -315,12 +309,12 @@ def first(value):
|
||||
try:
|
||||
return value[0]
|
||||
except IndexError:
|
||||
return ''
|
||||
return u''
|
||||
|
||||
def join(value, arg):
|
||||
"Joins a list with a string, like Python's ``str.join(list)``"
|
||||
try:
|
||||
return arg.join(map(smart_string, value))
|
||||
return arg.join(map(force_unicode, value))
|
||||
except AttributeError: # fail silently but nicely
|
||||
return value
|
||||
|
||||
@@ -346,7 +340,7 @@ def slice_(value, arg):
|
||||
"""
|
||||
try:
|
||||
bits = []
|
||||
for x in arg.split(':'):
|
||||
for x in arg.split(u':'):
|
||||
if len(x) == 0:
|
||||
bits.append(None)
|
||||
else:
|
||||
@@ -378,12 +372,12 @@ def unordered_list(value):
|
||||
</li>
|
||||
"""
|
||||
def _helper(value, tabs):
|
||||
indent = '\t' * tabs
|
||||
indent = u'\t' * tabs
|
||||
if value[1]:
|
||||
return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
|
||||
'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
|
||||
return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, force_unicode(value[0]), indent,
|
||||
u'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
|
||||
else:
|
||||
return '%s<li>%s</li>' % (indent, value[0])
|
||||
return u'%s<li>%s</li>' % (indent, force_unicode(value[0]))
|
||||
return _helper(value, 1)
|
||||
|
||||
###################
|
||||
@@ -421,7 +415,7 @@ def date(value, arg=None):
|
||||
"Formats a date according to the given format"
|
||||
from django.utils.dateformat import format
|
||||
if not value:
|
||||
return ''
|
||||
return u''
|
||||
if arg is None:
|
||||
arg = settings.DATE_FORMAT
|
||||
return format(value, arg)
|
||||
@@ -429,8 +423,8 @@ def date(value, arg=None):
|
||||
def time(value, arg=None):
|
||||
"Formats a time according to the given format"
|
||||
from django.utils.dateformat import time_format
|
||||
if value in (None, ''):
|
||||
return ''
|
||||
if value in (None, u''):
|
||||
return u''
|
||||
if arg is None:
|
||||
arg = settings.TIME_FORMAT
|
||||
return time_format(value, arg)
|
||||
@@ -439,7 +433,7 @@ def timesince(value, arg=None):
|
||||
'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
|
||||
from django.utils.timesince import timesince
|
||||
if not value:
|
||||
return ''
|
||||
return u''
|
||||
if arg:
|
||||
return timesince(arg, value)
|
||||
return timesince(value)
|
||||
@@ -449,7 +443,7 @@ def timeuntil(value, arg=None):
|
||||
from django.utils.timesince import timesince
|
||||
from datetime import datetime
|
||||
if not value:
|
||||
return ''
|
||||
return u''
|
||||
if arg:
|
||||
return timesince(arg, value)
|
||||
return timesince(datetime.now(), value)
|
||||
@@ -488,8 +482,8 @@ def yesno(value, arg=None):
|
||||
========== ====================== ==================================
|
||||
"""
|
||||
if arg is None:
|
||||
arg = gettext('yes,no,maybe')
|
||||
bits = arg.split(',')
|
||||
arg = ugettext('yes,no,maybe')
|
||||
bits = arg.split(u',')
|
||||
if len(bits) < 2:
|
||||
return value # Invalid arg.
|
||||
try:
|
||||
@@ -514,28 +508,28 @@ def filesizeformat(bytes):
|
||||
try:
|
||||
bytes = float(bytes)
|
||||
except TypeError:
|
||||
return "0 bytes"
|
||||
|
||||
if bytes < 1024:
|
||||
return ngettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
|
||||
if bytes < 1024 * 1024:
|
||||
return gettext("%.1f KB") % (bytes / 1024)
|
||||
if bytes < 1024 * 1024 * 1024:
|
||||
return gettext("%.1f MB") % (bytes / (1024 * 1024))
|
||||
return gettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
|
||||
return u"0 bytes"
|
||||
|
||||
def pluralize(value, arg='s'):
|
||||
if bytes < 1024:
|
||||
return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
|
||||
if bytes < 1024 * 1024:
|
||||
return ugettext("%.1f KB") % (bytes / 1024)
|
||||
if bytes < 1024 * 1024 * 1024:
|
||||
return ugettext("%.1f MB") % (bytes / (1024 * 1024))
|
||||
return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
|
||||
|
||||
def pluralize(value, arg=u's'):
|
||||
"""
|
||||
Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes'
|
||||
By default, 's' is used as a suffix; if an argument is provided, that string
|
||||
is used instead. If the provided argument contains a comma, the text before
|
||||
the comma is used for the singular case.
|
||||
"""
|
||||
if not ',' in arg:
|
||||
arg = ',' + arg
|
||||
bits = arg.split(',')
|
||||
if not u',' in arg:
|
||||
arg = u',' + arg
|
||||
bits = arg.split(u',')
|
||||
if len(bits) > 2:
|
||||
return ''
|
||||
return u''
|
||||
singular_suffix, plural_suffix = bits[:2]
|
||||
|
||||
try:
|
||||
@@ -562,7 +556,7 @@ def pprint(value):
|
||||
try:
|
||||
return pformat(value)
|
||||
except Exception, e:
|
||||
return "Error in formatting:%s" % e
|
||||
return u"Error in formatting:%s" % force_unicode(e)
|
||||
|
||||
# Syntax: register.filter(name of filter, callback)
|
||||
register.filter(add)
|
||||
@@ -582,6 +576,7 @@ register.filter(first)
|
||||
register.filter(fix_ampersands)
|
||||
register.filter(floatformat)
|
||||
register.filter(get_digit)
|
||||
register.filter(iriencode)
|
||||
register.filter(join)
|
||||
register.filter(length)
|
||||
register.filter(length_is)
|
||||
|
||||
@@ -4,6 +4,7 @@ from django.template import Node, NodeList, Template, Context, resolve_variable
|
||||
from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
|
||||
from django.template import get_library, Library, InvalidTemplateLibrary
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
from django.utils.itercompat import groupby
|
||||
import sys
|
||||
import re
|
||||
@@ -64,8 +65,8 @@ class FirstOfNode(Node):
|
||||
except VariableDoesNotExist:
|
||||
continue
|
||||
if value:
|
||||
return str(value)
|
||||
return ''
|
||||
return smart_unicode(value)
|
||||
return u''
|
||||
|
||||
class ForNode(Node):
|
||||
def __init__(self, loopvars, sequence, reversed, nodelist_loop):
|
||||
@@ -337,7 +338,7 @@ class URLNode(Node):
|
||||
def render(self, context):
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
args = [arg.resolve(context) for arg in self.args]
|
||||
kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
|
||||
kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) for k, v in self.kwargs.items()])
|
||||
try:
|
||||
return reverse(self.view_name, args=args, kwargs=kwargs)
|
||||
except NoReverseMatch:
|
||||
|
||||
@@ -34,7 +34,7 @@ def get_template_sources(template_name, template_dirs=None):
|
||||
def load_template_source(template_name, template_dirs=None):
|
||||
for filepath in get_template_sources(template_name, template_dirs):
|
||||
try:
|
||||
return (open(filepath).read(), filepath)
|
||||
return (open(filepath).read().decode(settings.FILE_CHARSET), filepath)
|
||||
except IOError:
|
||||
pass
|
||||
raise TemplateDoesNotExist, template_name
|
||||
|
||||
@@ -18,7 +18,7 @@ def load_template_source(template_name, template_dirs=None):
|
||||
pkg_name = 'templates/' + template_name
|
||||
for app in settings.INSTALLED_APPS:
|
||||
try:
|
||||
return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name))
|
||||
return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name)).decode(settings.FILE_CHARSET)
|
||||
except:
|
||||
pass
|
||||
raise TemplateDoesNotExist, template_name
|
||||
|
||||
@@ -14,7 +14,7 @@ def load_template_source(template_name, template_dirs=None):
|
||||
tried = []
|
||||
for filepath in get_template_sources(template_name, template_dirs):
|
||||
try:
|
||||
return (open(filepath).read(), filepath)
|
||||
return (open(filepath).read().decode(settings.FILE_CHARSET), filepath)
|
||||
except IOError:
|
||||
tried.append(filepath)
|
||||
if tried:
|
||||
|
||||
Reference in New Issue
Block a user