1
0
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:
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 = {
'conv': django_conversions,
'charset': 'utf8',
'use_unicode': False,
'use_unicode': True,
}
if settings.DATABASE_USER:
kwargs['user'] = settings.DATABASE_USER

View File

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

View File

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

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

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)
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 '\\'",
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)]
"""}

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
# 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)}]

View File

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

View File

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

View File

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

View File

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

View File

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

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'>
"""