mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +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:
parent
232b7ac519
commit
b493b7e3cf
@ -81,7 +81,7 @@ class DatabaseWrapper(local):
|
||||
kwargs = {
|
||||
'conv': django_conversions,
|
||||
'charset': 'utf8',
|
||||
'use_unicode': False,
|
||||
'use_unicode': True,
|
||||
}
|
||||
if settings.DATABASE_USER:
|
||||
kwargs['user'] = settings.DATABASE_USER
|
||||
|
@ -89,6 +89,7 @@ class DatabaseWrapper(local):
|
||||
'db': settings.DATABASE_NAME,
|
||||
'passwd': settings.DATABASE_PASSWORD,
|
||||
'conv': django_conversions,
|
||||
'use_unicode': True,
|
||||
}
|
||||
if settings.DATABASE_HOST.startswith('/'):
|
||||
kwargs['unix_socket'] = settings.DATABASE_HOST
|
||||
@ -101,6 +102,7 @@ class DatabaseWrapper(local):
|
||||
cursor = self.connection.cursor()
|
||||
if self.connection.get_server_info() >= '4.1':
|
||||
cursor.execute("SET NAMES 'utf8'")
|
||||
cursor.execute("SET CHARACTER SET 'utf8'")
|
||||
else:
|
||||
cursor = self.connection.cursor()
|
||||
if settings.DEBUG:
|
||||
|
@ -4,7 +4,9 @@ PostgreSQL database backend for Django.
|
||||
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.postgresql.encodings import ENCODING_MAP
|
||||
try:
|
||||
import psycopg as Database
|
||||
except ImportError, e:
|
||||
@ -20,11 +22,6 @@ except ImportError:
|
||||
# Import copy of _thread_local.py from Python 2.4
|
||||
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):
|
||||
"""
|
||||
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
|
||||
parameters that are Unicode strings. If a param is Unicode, this will
|
||||
convert it to a bytestring using DEFAULT_CHARSET before passing it to
|
||||
psycopg.
|
||||
convert it to a bytestring using database client's encoding before passing
|
||||
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):
|
||||
self.cursor = cursor
|
||||
self.charset = charset
|
||||
|
||||
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):
|
||||
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)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
@ -53,6 +53,7 @@ class UnicodeCursorWrapper(object):
|
||||
return getattr(self.cursor, attr)
|
||||
|
||||
postgres_version = None
|
||||
client_encoding = None
|
||||
|
||||
class DatabaseWrapper(local):
|
||||
def __init__(self, **kwargs):
|
||||
@ -82,11 +83,21 @@ class DatabaseWrapper(local):
|
||||
cursor = self.connection.cursor()
|
||||
if set_tz:
|
||||
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
|
||||
if not postgres_version:
|
||||
cursor.execute("SELECT version()")
|
||||
postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
|
||||
postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
|
||||
if settings.DEBUG:
|
||||
return util.CursorDebugWrapper(cursor, self)
|
||||
return cursor
|
||||
@ -148,7 +159,7 @@ def get_random_function_sql():
|
||||
|
||||
def get_deferrable_sql():
|
||||
return " DEFERRABLE INITIALLY DEFERRED"
|
||||
|
||||
|
||||
def get_fulltext_search_sql(field_name):
|
||||
raise NotImplementedError
|
||||
|
||||
@ -162,20 +173,21 @@ 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
|
||||
themselves) and put the database in an empty 'initial' state
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
if tables:
|
||||
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
|
||||
# truncate tables referenced by a foreign key in any other table. The result is a
|
||||
# single SQL TRUNCATE statement.
|
||||
# Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
|
||||
# in order to be able to truncate tables referenced by a foreign
|
||||
# key in any other table. The result is a single SQL TRUNCATE
|
||||
# statement.
|
||||
sql = ['%s %s;' % \
|
||||
(style.SQL_KEYWORD('TRUNCATE'),
|
||||
style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
|
||||
)]
|
||||
else:
|
||||
# Older versions of Postgres can't do TRUNCATE in a single call, so they must use
|
||||
# a simple delete.
|
||||
# Older versions of Postgres can't do TRUNCATE in a single call, so
|
||||
# they must use a simple delete.
|
||||
sql = ['%s %s %s;' % \
|
||||
(style.SQL_KEYWORD('DELETE'),
|
||||
style.SQL_KEYWORD('FROM'),
|
||||
@ -237,7 +249,15 @@ def get_sql_sequence_reset(style, model_list):
|
||||
style.SQL_KEYWORD('FROM'),
|
||||
style.SQL_TABLE(f.m2m_db_table())))
|
||||
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
|
||||
# in Python's native (standard-library) datetime/time format, whereas psycopg
|
||||
# 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((1114,1184), "TIMESTAMP", util.typecast_timestamp))
|
||||
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 = {
|
||||
'exact': '= %s',
|
||||
|
84
django/db/backends/postgresql/encodings.py
Normal file
84
django/db/backends/postgresql/encodings.py
Normal 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
|
||||
}
|
@ -7,6 +7,7 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
|
||||
from django.db.backends import util
|
||||
try:
|
||||
import psycopg2 as Database
|
||||
import psycopg2.extensions
|
||||
except ImportError, e:
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured, "Error loading psycopg2 module: %s" % e
|
||||
@ -20,6 +21,8 @@ except ImportError:
|
||||
# Import copy of _thread_local.py from Python 2.4
|
||||
from django.utils._threading_local import local
|
||||
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||
|
||||
postgres_version = None
|
||||
|
||||
class DatabaseWrapper(local):
|
||||
@ -47,6 +50,7 @@ class DatabaseWrapper(local):
|
||||
conn_string += " port=%s" % settings.DATABASE_PORT
|
||||
self.connection = Database.connect(conn_string, **self.options)
|
||||
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
|
||||
self.connection.set_client_encoding('UTF8')
|
||||
cursor = self.connection.cursor()
|
||||
cursor.tzinfo_factory = None
|
||||
if set_tz:
|
||||
|
@ -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)
|
||||
|
||||
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:
|
||||
# Only exists in Python 2.4+
|
||||
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_date_trunc", 2, _sqlite_date_trunc)
|
||||
cursor = self.connection.cursor(factory=SQLiteCursorWrapper)
|
||||
cursor.row_factory = utf8rowFactory
|
||||
if settings.DEBUG:
|
||||
return util.CursorDebugWrapper(cursor, self)
|
||||
else:
|
||||
@ -76,8 +67,9 @@ class DatabaseWrapper(local):
|
||||
|
||||
def close(self):
|
||||
from django.conf import settings
|
||||
# If database is in memory, closing the connection destroys the database.
|
||||
# To prevent accidental data loss, ignore close requests on an in-memory db.
|
||||
# If database is in memory, closing the connection destroys the
|
||||
# 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:":
|
||||
self.connection.close()
|
||||
self.connection = None
|
||||
@ -153,10 +145,10 @@ def get_pk_default_value():
|
||||
return "NULL"
|
||||
|
||||
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
|
||||
themselves) and put the database in an empty 'initial' state
|
||||
|
||||
"""
|
||||
Return a list of SQL statements required to remove all data from all tables
|
||||
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
|
||||
# Note: The DELETE FROM... SQL generated below works for SQLite databases
|
||||
@ -174,7 +166,7 @@ def get_sql_sequence_reset(style, model_list):
|
||||
"Returns a list of the SQL statements to reset sequences for the given models."
|
||||
# No sequence reset required
|
||||
return []
|
||||
|
||||
|
||||
def _sqlite_date_trunc(lookup_type, dt):
|
||||
try:
|
||||
dt = util.typecast_timestamp(dt)
|
||||
@ -204,3 +196,4 @@ OPERATOR_MAPPING = {
|
||||
'istartswith': "LIKE %s ESCAPE '\\'",
|
||||
'iendswith': "LIKE %s ESCAPE '\\'",
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ from django.conf import settings
|
||||
from django.template.context import Context, RequestContext, ContextPopException
|
||||
from django.utils.functional import curry
|
||||
from django.utils.text import smart_split
|
||||
from django.utils.encoding import smart_unicode, smart_str
|
||||
|
||||
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
|
||||
|
||||
@ -118,15 +119,18 @@ class TemplateSyntaxError(Exception):
|
||||
class TemplateDoesNotExist(Exception):
|
||||
pass
|
||||
|
||||
class TemplateEncodingError(Exception):
|
||||
pass
|
||||
|
||||
class VariableDoesNotExist(Exception):
|
||||
|
||||
def __init__(self, msg, params=()):
|
||||
self.msg = msg
|
||||
self.params = params
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.msg % self.params
|
||||
|
||||
|
||||
class InvalidTemplateLibrary(Exception):
|
||||
pass
|
||||
|
||||
@ -151,6 +155,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
|
||||
@ -705,7 +713,7 @@ class NodeList(list):
|
||||
bits.append(self.render_node(node, context))
|
||||
else:
|
||||
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):
|
||||
"Return a list of all nodes of the given type"
|
||||
@ -715,7 +723,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):
|
||||
@ -750,32 +758,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."
|
||||
|
@ -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
|
||||
import sys
|
||||
|
||||
register = Library()
|
||||
@ -324,7 +325,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:
|
||||
|
@ -1,25 +1,50 @@
|
||||
import types
|
||||
from django.conf import settings
|
||||
from django.utils.functional import Promise
|
||||
|
||||
def smart_unicode(s):
|
||||
if isinstance(s, Promise):
|
||||
# The input is the result of a gettext_lazy() call, or similar. It will
|
||||
# already be encoded in DEFAULT_CHARSET on evaluation and we don't want
|
||||
# to evaluate it until render time.
|
||||
# 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
|
||||
def smart_unicode(s, encoding='utf-8'):
|
||||
"""
|
||||
Returns a unicode object representing 's'. Treats bytestrings using the
|
||||
'encoding' codec.
|
||||
"""
|
||||
#if isinstance(s, Promise):
|
||||
# # The input is the result of a gettext_lazy() call, or similar. It will
|
||||
# # already be encoded in DEFAULT_CHARSET on evaluation and we don't want
|
||||
# # to evaluate it until render time.
|
||||
# # 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 hasattr(s, '__unicode__'):
|
||||
s = unicode(s)
|
||||
else:
|
||||
s = unicode(str(s), settings.DEFAULT_CHARSET)
|
||||
s = unicode(str(s), encoding)
|
||||
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
|
||||
|
||||
class StrAndUnicode(object):
|
||||
"""
|
||||
A class whose __str__ returns its __unicode__ as a bytestring
|
||||
@ -28,5 +53,7 @@ class StrAndUnicode(object):
|
||||
Useful as a mix-in.
|
||||
"""
|
||||
def __str__(self):
|
||||
# XXX: (Malcolm) Correct encoding? Be variable and use UTF-8 as
|
||||
# default?
|
||||
return self.__unicode__().encode(settings.DEFAULT_CHARSET)
|
||||
|
||||
|
@ -351,7 +351,7 @@ __test__['API_TESTS'] += """
|
||||
>>> a101.save()
|
||||
>>> a101 = Article.objects.get(pk=101)
|
||||
>>> a101.headline
|
||||
'Article 101'
|
||||
u'Article 101'
|
||||
|
||||
# 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))
|
||||
|
@ -6,11 +6,11 @@ If your database column name is different than your model attribute, use the
|
||||
name, in API usage.
|
||||
|
||||
If your database table name is different than your model name, use the
|
||||
``db_table`` Meta attribute. This has no effect on the API used to
|
||||
``db_table`` Meta attribute. This has no effect on the API used to
|
||||
query the database.
|
||||
|
||||
If you need to use a table name for a many-to-many relationship that differs
|
||||
from the default generated name, use the ``db_table`` parameter on the
|
||||
If you need to use a table name for a many-to-many relationship that differs
|
||||
from the default generated name, use the ``db_table`` parameter on the
|
||||
ManyToMany field. This has no effect on the API for querying the database.
|
||||
|
||||
"""
|
||||
@ -37,7 +37,7 @@ class Article(models.Model):
|
||||
|
||||
class Meta:
|
||||
ordering = ('headline',)
|
||||
|
||||
|
||||
__test__ = {'API_TESTS':"""
|
||||
# Create a Author.
|
||||
>>> a = Author(first_name='John', last_name='Smith')
|
||||
@ -75,9 +75,9 @@ TypeError: Cannot resolve keyword 'firstname' into field
|
||||
|
||||
>>> a = Author.objects.get(last_name__exact='Smith')
|
||||
>>> a.first_name
|
||||
'John'
|
||||
u'John'
|
||||
>>> a.last_name
|
||||
'Smith'
|
||||
u'Smith'
|
||||
>>> a.firstname
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
|
@ -62,7 +62,7 @@ DoesNotExist: Employee matching query does not exist.
|
||||
>>> Employee.objects.filter(last_name__exact='Jones')
|
||||
[<Employee: Dan Jones>, <Employee: Fran Jones>]
|
||||
>>> 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.save()
|
||||
@ -72,7 +72,7 @@ DoesNotExist: Employee matching query does not exist.
|
||||
>>> fran.business_set.all()
|
||||
[<Business: Sears>]
|
||||
>>> Business.objects.in_bulk(['Sears'])
|
||||
{'Sears': <Business: Sears>}
|
||||
{u'Sears': <Business: Sears>}
|
||||
|
||||
>>> Business.objects.filter(name__exact='Sears')
|
||||
[<Business: Sears>]
|
||||
|
@ -1,10 +1,10 @@
|
||||
"""
|
||||
37. Fixtures.
|
||||
|
||||
Fixtures are a way of loading data into the database in bulk. Fixure data
|
||||
can be stored in any serializable format (including JSON and XML). Fixtures
|
||||
Fixtures are a way of loading data into the database in bulk. Fixure data
|
||||
can be stored in any serializable format (including JSON and XML). Fixtures
|
||||
are identified by name, and are stored in either a directory named 'fixtures'
|
||||
in the application directory, on in one of the directories named in the
|
||||
in the application directory, on in one of the directories named in the
|
||||
FIXTURE_DIRS setting.
|
||||
"""
|
||||
|
||||
@ -16,15 +16,15 @@ class Article(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return self.headline
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = ('-pub_date', 'headline')
|
||||
|
||||
|
||||
__test__ = {'API_TESTS': """
|
||||
>>> from django.core import management
|
||||
>>> from django.db.models import get_app
|
||||
|
||||
# Reset the database representation of this app.
|
||||
# Reset the database representation of this app.
|
||||
# This will return the database to a clean initial state.
|
||||
>>> management.flush(verbosity=0, interactive=False)
|
||||
|
||||
@ -42,7 +42,7 @@ __test__ = {'API_TESTS': """
|
||||
>>> Article.objects.all()
|
||||
[<Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>]
|
||||
|
||||
# Load fixture 3, XML format.
|
||||
# Load fixture 3, XML format.
|
||||
>>> management.load_data(['fixture3.xml'], verbosity=0)
|
||||
>>> Article.objects.all()
|
||||
[<Article: XML identified as leading cause of cancer>, <Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>, <Article: Python program becomes self aware>]
|
||||
@ -65,7 +65,7 @@ __test__ = {'API_TESTS': """
|
||||
[<Article: Time to reform copyright>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>]
|
||||
|
||||
# Try to load fixture 2 using format discovery; this will fail
|
||||
# because there are two fixture2's in the fixtures directory
|
||||
# because there are two fixture2's in the fixtures directory
|
||||
>>> management.load_data(['fixture2'], verbosity=0) # doctest: +ELLIPSIS
|
||||
Multiple fixtures named 'fixture2' in '...fixtures'. Aborting.
|
||||
|
||||
@ -81,7 +81,7 @@ from django.test import TestCase
|
||||
|
||||
class SampleTestCase(TestCase):
|
||||
fixtures = ['fixture1.json', 'fixture2.json']
|
||||
|
||||
|
||||
def testClassFixtures(self):
|
||||
"Check that test case has installed 4 fixture objects"
|
||||
self.assertEqual(Article.objects.count(), 4)
|
||||
|
@ -110,17 +110,17 @@ __test__ = {'API_TESTS':"""
|
||||
# objects are deleted when the source object is deleted.
|
||||
# Original list of tags:
|
||||
>>> [(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()
|
||||
>>> [(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
|
||||
# remain after deletion of the source object.
|
||||
>>> quartz.delete()
|
||||
>>> [(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
|
||||
# (other than losing a tag)
|
||||
@ -129,6 +129,6 @@ __test__ = {'API_TESTS':"""
|
||||
>>> bacon.tags.all()
|
||||
[<TaggedItem: salty>]
|
||||
>>> [(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)]
|
||||
|
||||
"""}
|
||||
|
@ -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
|
||||
# you can specify which fields you want to retrieve.
|
||||
>>> 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')
|
||||
[{'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'}]
|
||||
@ -109,13 +109,13 @@ True
|
||||
... i = d.items()
|
||||
... i.sort()
|
||||
... i
|
||||
[('headline', 'Article 5'), ('id', 5)]
|
||||
[('headline', 'Article 6'), ('id', 6)]
|
||||
[('headline', 'Article 4'), ('id', 4)]
|
||||
[('headline', 'Article 2'), ('id', 2)]
|
||||
[('headline', 'Article 3'), ('id', 3)]
|
||||
[('headline', 'Article 7'), ('id', 7)]
|
||||
[('headline', 'Article 1'), ('id', 1)]
|
||||
[('headline', u'Article 5'), ('id', 5)]
|
||||
[('headline', u'Article 6'), ('id', 6)]
|
||||
[('headline', u'Article 4'), ('id', 4)]
|
||||
[('headline', u'Article 2'), ('id', 2)]
|
||||
[('headline', u'Article 3'), ('id', 3)]
|
||||
[('headline', u'Article 7'), ('id', 7)]
|
||||
[('headline', u'Article 1'), ('id', 1)]
|
||||
|
||||
# You can use values() with iterator() for memory savings, because iterator()
|
||||
# uses database-level iteration.
|
||||
@ -123,13 +123,13 @@ True
|
||||
... i = d.items()
|
||||
... i.sort()
|
||||
... i
|
||||
[('headline', 'Article 5'), ('id', 5)]
|
||||
[('headline', 'Article 6'), ('id', 6)]
|
||||
[('headline', 'Article 4'), ('id', 4)]
|
||||
[('headline', 'Article 2'), ('id', 2)]
|
||||
[('headline', 'Article 3'), ('id', 3)]
|
||||
[('headline', 'Article 7'), ('id', 7)]
|
||||
[('headline', 'Article 1'), ('id', 1)]
|
||||
[('headline', u'Article 5'), ('id', 5)]
|
||||
[('headline', u'Article 6'), ('id', 6)]
|
||||
[('headline', u'Article 4'), ('id', 4)]
|
||||
[('headline', u'Article 2'), ('id', 2)]
|
||||
[('headline', u'Article 3'), ('id', 3)]
|
||||
[('headline', u'Article 7'), ('id', 7)]
|
||||
[('headline', u'Article 1'), ('id', 1)]
|
||||
|
||||
# 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)}]
|
||||
|
@ -47,7 +47,7 @@ __test__ = {'API_TESTS':"""
|
||||
# Article objects have access to their related Reporter objects.
|
||||
>>> r = a.reporter
|
||||
>>> r.first_name, r.last_name
|
||||
('John', 'Smith')
|
||||
(u'John', u'Smith')
|
||||
|
||||
# Create an Article via the Reporter object.
|
||||
>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29))
|
||||
|
@ -213,7 +213,7 @@ True
|
||||
1
|
||||
>>> new_art = Article.objects.get(id=1)
|
||||
>>> new_art.headline
|
||||
'New headline'
|
||||
u'New headline'
|
||||
|
||||
Add some categories and test the many-to-many form output.
|
||||
>>> new_art.categories.all()
|
||||
|
@ -100,7 +100,7 @@ __test__ = {'API_TESTS':"""
|
||||
3
|
||||
|
||||
>>> 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])
|
||||
{1: <Article: Hello>}
|
||||
|
@ -22,10 +22,12 @@ There were some problems with form translations in #3600
|
||||
>>> f = SomeForm()
|
||||
>>> print f.as_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()
|
||||
<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
|
||||
>>> deactivate()
|
||||
|
||||
# XFAIL
|
||||
# >>> activate('de')
|
||||
# >>> 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...
|
||||
>>> GENDERS = (('0', u'En tied\xe4'), ('1', u'Mies'), ('2', u'Nainen'))
|
||||
|
@ -11,8 +11,14 @@ from django.template import loader
|
||||
from django.utils.translation import activate, deactivate, install
|
||||
from django.utils.tzinfo import LocalTimezone
|
||||
from datetime import datetime, timedelta
|
||||
from unicode import unicode_tests
|
||||
import unittest
|
||||
|
||||
# Some other tests we would like to run
|
||||
__test__ = {
|
||||
'unicode': unicode_tests,
|
||||
}
|
||||
|
||||
#################################
|
||||
# Custom template tag for tests #
|
||||
#################################
|
||||
@ -202,8 +208,8 @@ class Templates(unittest.TestCase):
|
||||
# Empty strings can be passed as arguments to filters
|
||||
'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
|
||||
|
||||
# If a variable has a __str__() that returns a Unicode object, the value
|
||||
# will be converted to a bytestring.
|
||||
# Make sure that any unicode strings are converted to bytestrings
|
||||
# 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'),
|
||||
|
||||
### COMMENT SYNTAX ########################################################
|
||||
|
58
tests/regressiontests/templates/unicode.py
Normal file
58
tests/regressiontests/templates/unicode.py
Normal 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'>
|
||||
"""
|
Loading…
x
Reference in New Issue
Block a user