1
0
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:
Malcolm Tredinnick
2007-07-04 12:11:04 +00:00
parent 4c958b15b2
commit 953badbea5
193 changed files with 3005 additions and 1603 deletions

View File

@@ -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."

View File

@@ -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 ``&amp;`` 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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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: