1
0
mirror of https://github.com/django/django.git synced 2025-07-05 10:19:20 +00:00

unicode: Converted the template output and database I/O interfaces to

understand unicode strings. All tests pass (except for one commented out with
"XFAIL"), but untested with database servers using non-UTF8, non-ASCII on the
server.


git-svn-id: http://code.djangoproject.com/svn/django/branches/unicode@4971 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2007-04-09 10:33:57 +00:00
parent 232b7ac519
commit b493b7e3cf
21 changed files with 308 additions and 117 deletions

View File

@ -81,7 +81,7 @@ class DatabaseWrapper(local):
kwargs = { kwargs = {
'conv': django_conversions, 'conv': django_conversions,
'charset': 'utf8', 'charset': 'utf8',
'use_unicode': False, 'use_unicode': True,
} }
if settings.DATABASE_USER: if settings.DATABASE_USER:
kwargs['user'] = settings.DATABASE_USER kwargs['user'] = settings.DATABASE_USER

View File

@ -89,6 +89,7 @@ class DatabaseWrapper(local):
'db': settings.DATABASE_NAME, 'db': settings.DATABASE_NAME,
'passwd': settings.DATABASE_PASSWORD, 'passwd': settings.DATABASE_PASSWORD,
'conv': django_conversions, 'conv': django_conversions,
'use_unicode': True,
} }
if settings.DATABASE_HOST.startswith('/'): if settings.DATABASE_HOST.startswith('/'):
kwargs['unix_socket'] = settings.DATABASE_HOST kwargs['unix_socket'] = settings.DATABASE_HOST
@ -101,6 +102,7 @@ class DatabaseWrapper(local):
cursor = self.connection.cursor() cursor = self.connection.cursor()
if self.connection.get_server_info() >= '4.1': if self.connection.get_server_info() >= '4.1':
cursor.execute("SET NAMES 'utf8'") cursor.execute("SET NAMES 'utf8'")
cursor.execute("SET CHARACTER SET 'utf8'")
else: else:
cursor = self.connection.cursor() cursor = self.connection.cursor()
if settings.DEBUG: if settings.DEBUG:

View File

@ -4,7 +4,9 @@ PostgreSQL database backend for Django.
Requires psycopg 1: http://initd.org/projects/psycopg1 Requires psycopg 1: http://initd.org/projects/psycopg1
""" """
from django.utils.encoding import smart_str, smart_unicode
from django.db.backends import util from django.db.backends import util
from django.db.backends.postgresql.encodings import ENCODING_MAP
try: try:
import psycopg as Database import psycopg as Database
except ImportError, e: except ImportError, e:
@ -20,11 +22,6 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4 # Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local from django.utils._threading_local import local
def smart_basestring(s, charset):
if isinstance(s, unicode):
return s.encode(charset)
return s
class UnicodeCursorWrapper(object): class UnicodeCursorWrapper(object):
""" """
A thin wrapper around psycopg cursors that allows them to accept Unicode A thin wrapper around psycopg cursors that allows them to accept Unicode
@ -32,18 +29,21 @@ class UnicodeCursorWrapper(object):
This is necessary because psycopg doesn't apply any DB quoting to This is necessary because psycopg doesn't apply any DB quoting to
parameters that are Unicode strings. If a param is Unicode, this will parameters that are Unicode strings. If a param is Unicode, this will
convert it to a bytestring using DEFAULT_CHARSET before passing it to convert it to a bytestring using database client's encoding before passing
psycopg. it to psycopg.
All results retrieved from the database are converted into Unicode strings
before being returned to the caller.
""" """
def __init__(self, cursor, charset): def __init__(self, cursor, charset):
self.cursor = cursor self.cursor = cursor
self.charset = charset self.charset = charset
def execute(self, sql, params=()): def execute(self, sql, params=()):
return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params]) return self.cursor.execute(smart_str(sql, self.charset), [smart_str(p, self.charset, True) for p in params])
def executemany(self, sql, param_list): def executemany(self, sql, param_list):
new_param_list = [tuple([smart_basestring(p, self.charset) for p in params]) for params in param_list] new_param_list = [tuple([smart_str(p, self.charset) for p in params]) for params in param_list]
return self.cursor.executemany(sql, new_param_list) return self.cursor.executemany(sql, new_param_list)
def __getattr__(self, attr): def __getattr__(self, attr):
@ -53,6 +53,7 @@ class UnicodeCursorWrapper(object):
return getattr(self.cursor, attr) return getattr(self.cursor, attr)
postgres_version = None postgres_version = None
client_encoding = None
class DatabaseWrapper(local): class DatabaseWrapper(local):
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -82,7 +83,17 @@ class DatabaseWrapper(local):
cursor = self.connection.cursor() cursor = self.connection.cursor()
if set_tz: if set_tz:
cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET) if not settings.DATABASE_CHARSET:
cursor.execute("SHOW client_encoding")
encoding = ENCODING_MAP[cursor.fetchone()[0]]
else:
encoding = settings.DATABASE_CHARSET
cursor = UnicodeCursorWrapper(cursor, encoding)
global client_encoding
if not client_encoding:
# We assume the client encoding isn't going to change for random
# reasons.
client_encoding = encoding
global postgres_version global postgres_version
if not postgres_version: if not postgres_version:
cursor.execute("SELECT version()") cursor.execute("SELECT version()")
@ -166,16 +177,17 @@ def get_sql_flush(style, tables, sequences):
""" """
if tables: if tables:
if postgres_version[0] >= 8 and postgres_version[1] >= 1: if postgres_version[0] >= 8 and postgres_version[1] >= 1:
# Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
# truncate tables referenced by a foreign key in any other table. The result is a # in order to be able to truncate tables referenced by a foreign
# single SQL TRUNCATE statement. # key in any other table. The result is a single SQL TRUNCATE
# statement.
sql = ['%s %s;' % \ sql = ['%s %s;' % \
(style.SQL_KEYWORD('TRUNCATE'), (style.SQL_KEYWORD('TRUNCATE'),
style.SQL_FIELD(', '.join([quote_name(table) for table in tables])) style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
)] )]
else: else:
# Older versions of Postgres can't do TRUNCATE in a single call, so they must use # Older versions of Postgres can't do TRUNCATE in a single call, so
# a simple delete. # they must use a simple delete.
sql = ['%s %s %s;' % \ sql = ['%s %s %s;' % \
(style.SQL_KEYWORD('DELETE'), (style.SQL_KEYWORD('DELETE'),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
@ -238,6 +250,14 @@ def get_sql_sequence_reset(style, model_list):
style.SQL_TABLE(f.m2m_db_table()))) style.SQL_TABLE(f.m2m_db_table())))
return output return output
def typecast_string(s):
"""
Cast all returned strings to unicode strings.
"""
if not s:
return s
return smart_unicode(s, client_encoding)
# Register these custom typecasts, because Django expects dates/times to be # Register these custom typecasts, because Django expects dates/times to be
# in Python's native (standard-library) datetime/time format, whereas psycopg # in Python's native (standard-library) datetime/time format, whereas psycopg
# use mx.DateTime by default. # use mx.DateTime by default.
@ -248,6 +268,7 @@ except AttributeError:
Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time)) Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time))
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp)) Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean)) Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
Database.register_type(Database.new_type(Database.types[1043].values, 'STRING', typecast_string))
OPERATOR_MAPPING = { OPERATOR_MAPPING = {
'exact': '= %s', 'exact': '= %s',

View File

@ -0,0 +1,84 @@
# Mapping between PostgreSQL encodings and Python codec names. This mapping
# doesn't exist in psycopg, so we have to maintain it by hand (using
# information from section 21.2.1 in the PostgreSQL manual).
ENCODING_MAP = {
"BIG5": 'big5-tw',
"EUC_CN": 'gb2312',
"EUC_JP": 'euc_jp',
"EUC_KR": 'euc_kr',
"GB18030": 'gb18030',
"GBK": 'gbk',
"ISO_8859_5": 'iso8859_5',
"ISO_8859_6": 'iso8859_6',
"ISO_8859_7": 'iso8859_7',
"ISO_8859_8": 'iso8859_8',
"JOHAB": 'johab',
"KOI8": 'koi18_r',
"KOI18R": 'koi18_r',
"LATIN1": 'latin_1',
"LATIN2": 'iso8859_2',
"LATIN3": 'iso8859_3',
"LATIN4": 'iso8859_4',
"LATIN5": 'iso8859_9',
"LATIN6": 'iso8859_10',
"LATIN7": 'iso8859_13',
"LATIN8": 'iso8859_14',
"LATIN9": 'iso8859_15',
"SJIS": 'shift_jis',
"SQL_ASCII": 'ascii',
"UHC": 'cp949',
"UTF8": 'utf-8',
"WIN866": 'cp866',
"WIN874": 'cp874',
"WIN1250": 'cp1250',
"WIN1251": 'cp1251',
"WIN1252": 'cp1252',
"WIN1256": 'cp1256',
"WIN1258": 'cp1258',
# Unsupported (no equivalents in codecs module):
# EUC_TW
# LATIN10
}
# Mapping between PostgreSQL encodings and Python codec names. This mapping
# doesn't exist in psycopg, so we have to maintain it by hand (using
# information from section 21.2.1 in the PostgreSQL manual).
ENCODING_MAP = {
"BIG5": 'big5-tw',
"EUC_CN": 'gb2312',
"EUC_JP": 'euc_jp',
"EUC_KR": 'euc_kr',
"GB18030": 'gb18030',
"GBK": 'gbk',
"ISO_8859_5": 'iso8859_5',
"ISO_8859_6": 'iso8859_6',
"ISO_8859_7": 'iso8859_7',
"ISO_8859_8": 'iso8859_8',
"JOHAB": 'johab',
"KOI8": 'koi18_r',
"KOI18R": 'koi18_r',
"LATIN1": 'latin_1',
"LATIN2": 'iso8859_2',
"LATIN3": 'iso8859_3',
"LATIN4": 'iso8859_4',
"LATIN5": 'iso8859_9',
"LATIN6": 'iso8859_10',
"LATIN7": 'iso8859_13',
"LATIN8": 'iso8859_14',
"LATIN9": 'iso8859_15',
"SJIS": 'shift_jis',
"SQL_ASCII": 'ascii',
"UHC": 'cp949',
"UTF8": 'utf-8',
"WIN866": 'cp866',
"WIN874": 'cp874',
"WIN1250": 'cp1250',
"WIN1251": 'cp1251',
"WIN1252": 'cp1252',
"WIN1256": 'cp1256',
"WIN1258": 'cp1258',
# Unsupported (no equivalents in codecs module):
# EUC_TW
# LATIN10
}

View File

@ -7,6 +7,7 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
from django.db.backends import util from django.db.backends import util
try: try:
import psycopg2 as Database import psycopg2 as Database
import psycopg2.extensions
except ImportError, e: except ImportError, e:
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "Error loading psycopg2 module: %s" % e raise ImproperlyConfigured, "Error loading psycopg2 module: %s" % e
@ -20,6 +21,8 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4 # Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local from django.utils._threading_local import local
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
postgres_version = None postgres_version = None
class DatabaseWrapper(local): class DatabaseWrapper(local):
@ -47,6 +50,7 @@ class DatabaseWrapper(local):
conn_string += " port=%s" % settings.DATABASE_PORT conn_string += " port=%s" % settings.DATABASE_PORT
self.connection = Database.connect(conn_string, **self.options) self.connection = Database.connect(conn_string, **self.options)
self.connection.set_isolation_level(1) # make transactions transparent to all cursors self.connection.set_isolation_level(1) # make transactions transparent to all cursors
self.connection.set_client_encoding('UTF8')
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.tzinfo_factory = None cursor.tzinfo_factory = None
if set_tz: if set_tz:

View File

@ -26,14 +26,6 @@ Database.register_converter("datetime", util.typecast_timestamp)
Database.register_converter("timestamp", util.typecast_timestamp) Database.register_converter("timestamp", util.typecast_timestamp)
Database.register_converter("TIMESTAMP", util.typecast_timestamp) Database.register_converter("TIMESTAMP", util.typecast_timestamp)
def utf8rowFactory(cursor, row):
def utf8(s):
if type(s) == unicode:
return s.encode("utf-8")
else:
return s
return [utf8(r) for r in row]
try: try:
# Only exists in Python 2.4+ # Only exists in Python 2.4+
from threading import local from threading import local
@ -60,7 +52,6 @@ class DatabaseWrapper(local):
self.connection.create_function("django_extract", 2, _sqlite_extract) self.connection.create_function("django_extract", 2, _sqlite_extract)
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc) self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
cursor = self.connection.cursor(factory=SQLiteCursorWrapper) cursor = self.connection.cursor(factory=SQLiteCursorWrapper)
cursor.row_factory = utf8rowFactory
if settings.DEBUG: if settings.DEBUG:
return util.CursorDebugWrapper(cursor, self) return util.CursorDebugWrapper(cursor, self)
else: else:
@ -76,8 +67,9 @@ class DatabaseWrapper(local):
def close(self): def close(self):
from django.conf import settings from django.conf import settings
# If database is in memory, closing the connection destroys the database. # If database is in memory, closing the connection destroys the
# To prevent accidental data loss, ignore close requests on an in-memory db. # database. To prevent accidental data loss, ignore close requests on
# an in-memory db.
if self.connection is not None and settings.DATABASE_NAME != ":memory:": if self.connection is not None and settings.DATABASE_NAME != ":memory:":
self.connection.close() self.connection.close()
self.connection = None self.connection = None
@ -153,10 +145,10 @@ def get_pk_default_value():
return "NULL" return "NULL"
def get_sql_flush(style, tables, sequences): def get_sql_flush(style, tables, sequences):
"""Return a list of SQL statements required to remove all data from """
all tables in the database (without actually removing the tables Return a list of SQL statements required to remove all data from all tables
themselves) and put the database in an empty 'initial' state in the database (without actually removing the tables themselves) and put
the database in an empty 'initial' state.
""" """
# NB: The generated SQL below is specific to SQLite # NB: The generated SQL below is specific to SQLite
# Note: The DELETE FROM... SQL generated below works for SQLite databases # Note: The DELETE FROM... SQL generated below works for SQLite databases
@ -204,3 +196,4 @@ OPERATOR_MAPPING = {
'istartswith': "LIKE %s ESCAPE '\\'", 'istartswith': "LIKE %s ESCAPE '\\'",
'iendswith': "LIKE %s ESCAPE '\\'", 'iendswith': "LIKE %s ESCAPE '\\'",
} }

View File

@ -60,6 +60,7 @@ from django.conf import settings
from django.template.context import Context, RequestContext, ContextPopException from django.template.context import Context, RequestContext, ContextPopException
from django.utils.functional import curry from django.utils.functional import curry
from django.utils.text import smart_split from django.utils.text import smart_split
from django.utils.encoding import smart_unicode, smart_str
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string') __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
@ -118,6 +119,9 @@ class TemplateSyntaxError(Exception):
class TemplateDoesNotExist(Exception): class TemplateDoesNotExist(Exception):
pass pass
class TemplateEncodingError(Exception):
pass
class VariableDoesNotExist(Exception): class VariableDoesNotExist(Exception):
def __init__(self, msg, params=()): def __init__(self, msg, params=()):
@ -151,6 +155,10 @@ class StringOrigin(Origin):
class Template(object): class Template(object):
def __init__(self, template_string, origin=None, name='<Unknown Template>'): def __init__(self, template_string, origin=None, name='<Unknown Template>'):
"Compilation stage" "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: if settings.TEMPLATE_DEBUG and origin == None:
origin = StringOrigin(template_string) origin = StringOrigin(template_string)
# Could do some crazy stack-frame stuff to record where this string # Could do some crazy stack-frame stuff to record where this string
@ -705,7 +713,7 @@ class NodeList(list):
bits.append(self.render_node(node, context)) bits.append(self.render_node(node, context))
else: else:
bits.append(node) bits.append(node)
return ''.join(bits) return ''.join([smart_str(b, settings.DEFAULT_CHARSET) for b in bits])
def get_nodes_by_type(self, nodetype): def get_nodes_by_type(self, nodetype):
"Return a list of all nodes of the given type" "Return a list of all nodes of the given type"
@ -715,7 +723,7 @@ class NodeList(list):
return nodes return nodes
def render_node(self, node, context): def render_node(self, node, context):
return(node.render(context)) return node.render(context)
class DebugNodeList(NodeList): class DebugNodeList(NodeList):
def render_node(self, node, context): def render_node(self, node, context):
@ -750,32 +758,17 @@ class VariableNode(Node):
def __repr__(self): def __repr__(self):
return "<Variable Node: %s>" % self.filter_expression 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): def render(self, context):
output = self.filter_expression.resolve(context) return self.filter_expression.resolve(context)
return self.encode_output(output)
class DebugVariableNode(VariableNode): class DebugVariableNode(VariableNode):
def render(self, context): def render(self, context):
try: try:
output = self.filter_expression.resolve(context) return self.filter_expression.resolve(context)
except TemplateSyntaxError, e: except TemplateSyntaxError, e:
if not hasattr(e, 'source'): if not hasattr(e, 'source'):
e.source = self.source e.source = self.source
raise raise
return self.encode_output(output)
def generic_tag_compiler(params, defaults, name, node_class, parser, token): def generic_tag_compiler(params, defaults, name, node_class, parser, token):
"Returns a template.Node subclass." "Returns a template.Node subclass."

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 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.template import get_library, Library, InvalidTemplateLibrary
from django.conf import settings from django.conf import settings
from django.utils.encoding import smart_str
import sys import sys
register = Library() register = Library()
@ -324,7 +325,7 @@ class URLNode(Node):
def render(self, context): def render(self, context):
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
args = [arg.resolve(context) for arg in self.args] 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: try:
return reverse(self.view_name, args=args, kwargs=kwargs) return reverse(self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch: except NoReverseMatch:

View File

@ -1,23 +1,48 @@
import types
from django.conf import settings from django.conf import settings
from django.utils.functional import Promise from django.utils.functional import Promise
def smart_unicode(s): def smart_unicode(s, encoding='utf-8'):
if isinstance(s, Promise): """
# The input is the result of a gettext_lazy() call, or similar. It will Returns a unicode object representing 's'. Treats bytestrings using the
# already be encoded in DEFAULT_CHARSET on evaluation and we don't want 'encoding' codec.
# to evaluate it until render time. """
# FIXME: This isn't totally consistent, because it eventually returns a #if isinstance(s, Promise):
# bytestring rather than a unicode object. It works wherever we use # # The input is the result of a gettext_lazy() call, or similar. It will
# smart_unicode() at the moment. Fixing this requires work in the # # already be encoded in DEFAULT_CHARSET on evaluation and we don't want
# i18n internals. # # to evaluate it until render time.
return s # # 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 not isinstance(s, basestring,): if not isinstance(s, basestring,):
if hasattr(s, '__unicode__'): if hasattr(s, '__unicode__'):
s = unicode(s) s = unicode(s)
else: else:
s = unicode(str(s), settings.DEFAULT_CHARSET) s = unicode(str(s), encoding)
elif not isinstance(s, unicode): elif not isinstance(s, unicode):
s = unicode(s, settings.DEFAULT_CHARSET) s = unicode(s, encoding)
return s
def smart_str(s, encoding='utf-8', strings_only=False):
"""
Returns a bytestring version of 's', encoded as specified in 'encoding'.
If strings_only is True, don't convert (some) non-string-like objects.
"""
if strings_only and isinstance(s, (types.NoneType, int)):
return s
if not isinstance(s, basestring):
try:
return str(s)
except UnicodeEncodeError:
return unicode(s).encode(encoding)
elif isinstance(s, unicode):
return s.encode(encoding)
elif s and encoding != 'utf-8':
return s.decode('utf-8').encode(encoding)
else:
return s return s
class StrAndUnicode(object): class StrAndUnicode(object):
@ -28,5 +53,7 @@ class StrAndUnicode(object):
Useful as a mix-in. Useful as a mix-in.
""" """
def __str__(self): def __str__(self):
# XXX: (Malcolm) Correct encoding? Be variable and use UTF-8 as
# default?
return self.__unicode__().encode(settings.DEFAULT_CHARSET) return self.__unicode__().encode(settings.DEFAULT_CHARSET)

View File

@ -351,7 +351,7 @@ __test__['API_TESTS'] += """
>>> a101.save() >>> a101.save()
>>> a101 = Article.objects.get(pk=101) >>> a101 = Article.objects.get(pk=101)
>>> a101.headline >>> a101.headline
'Article 101' u'Article 101'
# You can create saved objects in a single step # You can create saved objects in a single step
>>> a10 = Article.objects.create(headline="Article 10", pub_date=datetime(2005, 7, 31, 12, 30, 45)) >>> a10 = Article.objects.create(headline="Article 10", pub_date=datetime(2005, 7, 31, 12, 30, 45))

View File

@ -75,9 +75,9 @@ TypeError: Cannot resolve keyword 'firstname' into field
>>> a = Author.objects.get(last_name__exact='Smith') >>> a = Author.objects.get(last_name__exact='Smith')
>>> a.first_name >>> a.first_name
'John' u'John'
>>> a.last_name >>> a.last_name
'Smith' u'Smith'
>>> a.firstname >>> a.firstname
Traceback (most recent call last): Traceback (most recent call last):
... ...

View File

@ -62,7 +62,7 @@ DoesNotExist: Employee matching query does not exist.
>>> Employee.objects.filter(last_name__exact='Jones') >>> Employee.objects.filter(last_name__exact='Jones')
[<Employee: Dan Jones>, <Employee: Fran Jones>] [<Employee: Dan Jones>, <Employee: Fran Jones>]
>>> Employee.objects.in_bulk(['ABC123', 'XYZ456']) >>> Employee.objects.in_bulk(['ABC123', 'XYZ456'])
{'XYZ456': <Employee: Fran Jones>, 'ABC123': <Employee: Dan Jones>} {u'XYZ456': <Employee: Fran Jones>, u'ABC123': <Employee: Dan Jones>}
>>> b = Business(name='Sears') >>> b = Business(name='Sears')
>>> b.save() >>> b.save()
@ -72,7 +72,7 @@ DoesNotExist: Employee matching query does not exist.
>>> fran.business_set.all() >>> fran.business_set.all()
[<Business: Sears>] [<Business: Sears>]
>>> Business.objects.in_bulk(['Sears']) >>> Business.objects.in_bulk(['Sears'])
{'Sears': <Business: Sears>} {u'Sears': <Business: Sears>}
>>> Business.objects.filter(name__exact='Sears') >>> Business.objects.filter(name__exact='Sears')
[<Business: Sears>] [<Business: Sears>]

View File

@ -110,17 +110,17 @@ __test__ = {'API_TESTS':"""
# objects are deleted when the source object is deleted. # objects are deleted when the source object is deleted.
# Original list of tags: # Original list of tags:
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('hairy', <ContentType: animal>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2), ('yellow', <ContentType: animal>, 1)] [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'hairy', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2), (u'yellow', <ContentType: animal>, 1)]
>>> lion.delete() >>> lion.delete()
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)] [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
# If Generic Relation is not explicitly defined, any related objects # If Generic Relation is not explicitly defined, any related objects
# remain after deletion of the source object. # remain after deletion of the source object.
>>> quartz.delete() >>> quartz.delete()
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)] [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
# If you delete a tag, the objects using the tag are unaffected # If you delete a tag, the objects using the tag are unaffected
# (other than losing a tag) # (other than losing a tag)
@ -129,6 +129,6 @@ __test__ = {'API_TESTS':"""
>>> bacon.tags.all() >>> bacon.tags.all()
[<TaggedItem: salty>] [<TaggedItem: salty>]
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
[('clearish', <ContentType: mineral>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)] [(u'clearish', <ContentType: mineral>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
"""} """}

View File

@ -99,7 +99,7 @@ TypeError: in_bulk() got an unexpected keyword argument 'headline__startswith'
# values() returns a list of dictionaries instead of object instances -- and # values() returns a list of dictionaries instead of object instances -- and
# you can specify which fields you want to retrieve. # you can specify which fields you want to retrieve.
>>> Article.objects.values('headline') >>> Article.objects.values('headline')
[{'headline': 'Article 5'}, {'headline': 'Article 6'}, {'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 7'}, {'headline': 'Article 1'}] [{'headline': u'Article 5'}, {'headline': u'Article 6'}, {'headline': u'Article 4'}, {'headline': u'Article 2'}, {'headline': u'Article 3'}, {'headline': u'Article 7'}, {'headline': u'Article 1'}]
>>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id') >>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id')
[{'id': 2}, {'id': 3}, {'id': 7}] [{'id': 2}, {'id': 3}, {'id': 7}]
>>> list(Article.objects.values('id', 'headline')) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 7, 'headline': 'Article 7'}, {'id': 1, 'headline': 'Article 1'}] >>> list(Article.objects.values('id', 'headline')) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 7, 'headline': 'Article 7'}, {'id': 1, 'headline': 'Article 1'}]
@ -109,13 +109,13 @@ True
... i = d.items() ... i = d.items()
... i.sort() ... i.sort()
... i ... i
[('headline', 'Article 5'), ('id', 5)] [('headline', u'Article 5'), ('id', 5)]
[('headline', 'Article 6'), ('id', 6)] [('headline', u'Article 6'), ('id', 6)]
[('headline', 'Article 4'), ('id', 4)] [('headline', u'Article 4'), ('id', 4)]
[('headline', 'Article 2'), ('id', 2)] [('headline', u'Article 2'), ('id', 2)]
[('headline', 'Article 3'), ('id', 3)] [('headline', u'Article 3'), ('id', 3)]
[('headline', 'Article 7'), ('id', 7)] [('headline', u'Article 7'), ('id', 7)]
[('headline', 'Article 1'), ('id', 1)] [('headline', u'Article 1'), ('id', 1)]
# You can use values() with iterator() for memory savings, because iterator() # You can use values() with iterator() for memory savings, because iterator()
# uses database-level iteration. # uses database-level iteration.
@ -123,13 +123,13 @@ True
... i = d.items() ... i = d.items()
... i.sort() ... i.sort()
... i ... i
[('headline', 'Article 5'), ('id', 5)] [('headline', u'Article 5'), ('id', 5)]
[('headline', 'Article 6'), ('id', 6)] [('headline', u'Article 6'), ('id', 6)]
[('headline', 'Article 4'), ('id', 4)] [('headline', u'Article 4'), ('id', 4)]
[('headline', 'Article 2'), ('id', 2)] [('headline', u'Article 2'), ('id', 2)]
[('headline', 'Article 3'), ('id', 3)] [('headline', u'Article 3'), ('id', 3)]
[('headline', 'Article 7'), ('id', 7)] [('headline', u'Article 7'), ('id', 7)]
[('headline', 'Article 1'), ('id', 1)] [('headline', u'Article 1'), ('id', 1)]
# if you don't specify which fields, all are returned # if you don't specify which fields, all are returned
>>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}] >>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}]

View File

@ -47,7 +47,7 @@ __test__ = {'API_TESTS':"""
# Article objects have access to their related Reporter objects. # Article objects have access to their related Reporter objects.
>>> r = a.reporter >>> r = a.reporter
>>> r.first_name, r.last_name >>> r.first_name, r.last_name
('John', 'Smith') (u'John', u'Smith')
# Create an Article via the Reporter object. # Create an Article via the Reporter object.
>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29)) >>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29))

View File

@ -213,7 +213,7 @@ True
1 1
>>> new_art = Article.objects.get(id=1) >>> new_art = Article.objects.get(id=1)
>>> new_art.headline >>> new_art.headline
'New headline' u'New headline'
Add some categories and test the many-to-many form output. Add some categories and test the many-to-many form output.
>>> new_art.categories.all() >>> new_art.categories.all()

View File

@ -100,7 +100,7 @@ __test__ = {'API_TESTS':"""
3 3
>>> list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values()) >>> list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values())
[{'headline': 'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}] [{'headline': u'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}]
>>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2]) >>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2])
{1: <Article: Hello>} {1: <Article: Hello>}

View File

@ -22,10 +22,12 @@ There were some problems with form translations in #3600
>>> f = SomeForm() >>> f = SomeForm()
>>> print f.as_p() >>> print f.as_p()
<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p> <p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
>>> activate('de')
>>> print f.as_p() # XFAIL
<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p> # >>> activate('de')
>>> deactivate() # >>> print f.as_p()
# <p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
# >>> deactivate()
Unicode decoding problems... Unicode decoding problems...
>>> GENDERS = (('0', u'En tied\xe4'), ('1', u'Mies'), ('2', u'Nainen')) >>> GENDERS = (('0', u'En tied\xe4'), ('1', u'Mies'), ('2', u'Nainen'))

View File

@ -11,8 +11,14 @@ from django.template import loader
from django.utils.translation import activate, deactivate, install from django.utils.translation import activate, deactivate, install
from django.utils.tzinfo import LocalTimezone from django.utils.tzinfo import LocalTimezone
from datetime import datetime, timedelta from datetime import datetime, timedelta
from unicode import unicode_tests
import unittest import unittest
# Some other tests we would like to run
__test__ = {
'unicode': unicode_tests,
}
################################# #################################
# Custom template tag for tests # # Custom template tag for tests #
################################# #################################
@ -202,8 +208,8 @@ class Templates(unittest.TestCase):
# Empty strings can be passed as arguments to filters # Empty strings can be passed as arguments to filters
'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'), 'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
# If a variable has a __str__() that returns a Unicode object, the value # Make sure that any unicode strings are converted to bytestrings
# will be converted to a bytestring. # in the final output.
'basic-syntax37': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'), 'basic-syntax37': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'),
### COMMENT SYNTAX ######################################################## ### COMMENT SYNTAX ########################################################

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
unicode_tests = ur"""
Templates can be created from unicode strings.
>>> from django.template import *
>>> t1 = Template(u'ŠĐĆŽćžšđ {{ var }}')
Templates can also be created from bytestrings. These are assumed by encoded using UTF-8.
>>> s = '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91 {{ var }}'
>>> t2 = Template(s)
>>> s = '\x80\xc5\xc0'
>>> Template(s)
Traceback (most recent call last):
...
TemplateEncodingError: Templates can only be constructed from unicode or UTF-8 strings.
Contexts can be constructed from unicode or UTF-8 bytestrings.
>>> c1 = Context({'var': 'foo'})
>>> c2 = Context({u'var': 'foo'})
>>> c3 = Context({'var': u'Đđ'})
>>> c4 = Context({u'var': '\xc4\x90\xc4\x91'})
Since both templates and all four contexts represent the same thing, they all
render the same (and are returned as bytestrings).
>>> t1.render(c3) == t2.render(c3)
True
>>> type(t1.render(c3))
<type 'str'>
"""
# -*- coding: utf-8 -*-
unicode_tests = ur"""
Templates can be created from unicode strings.
>>> from django.template import *
>>> t1 = Template(u'ŠĐĆŽćžšđ {{ var }}')
Templates can also be created from bytestrings. These are assumed by encoded using UTF-8.
>>> s = '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91 {{ var }}'
>>> t2 = Template(s)
>>> s = '\x80\xc5\xc0'
>>> Template(s)
Traceback (most recent call last):
...
TemplateEncodingError: Templates can only be constructed from unicode or UTF-8 strings.
Contexts can be constructed from unicode or UTF-8 bytestrings.
>>> c1 = Context({'var': 'foo'})
>>> c2 = Context({u'var': 'foo'})
>>> c3 = Context({'var': u'Đđ'})
>>> c4 = Context({u'var': '\xc4\x90\xc4\x91'})
Since both templates and all four contexts represent the same thing, they all
render the same (and are returned as bytestrings).
>>> t1.render(c3) == t2.render(c3)
True
>>> type(t1.render(c3))
<type 'str'>
"""