Index: django/http/multipartparser.py
===================================================================
--- django/http/multipartparser.py	(Revision 12361)
+++ django/http/multipartparser.py	(Arbeitskopie)
@@ -5,12 +5,13 @@
 file upload handlers for processing.
 """
 
-import cgi
+import cgi, sys
 from django.conf import settings
 from django.core.exceptions import SuspiciousOperation
 from django.utils.datastructures import MultiValueDict
 from django.utils.encoding import force_unicode
 from django.utils.text import unescape_entities
+from django.utils.py3 import b, byte
 from django.core.files.uploadhandler import StopUpload, SkipFile, StopFutureHandlers
 
 __all__ = ('MultiPartParser', 'MultiPartParserError', 'InputStreamExhausted')
@@ -28,6 +29,13 @@
 FILE = "file"
 FIELD = "field"
 
+if sys.version_info < (3, 0):
+    valid_boundary = cgi.valid_boundary
+else:
+    def valid_boundary(b):
+        # the cgi module in 3.1 insists on the boundary being a string
+        return cgi.valid_boundary(b.decode('ascii'))
+
 class MultiPartParser(object):
     """
     A rfc2388 multipart/form-data parser.
@@ -59,9 +67,12 @@
             raise MultiPartParserError('Invalid Content-Type: %s' % content_type)
 
         # Parse the header to get the boundary to split the parts.
+        if sys.version_info >= (3,0):
+            # 3.x uses str for META fields, however, MIME header parsing below is byte-based
+            content_type = content_type.encode('ascii')
         ctypes, opts = parse_header(content_type)
         boundary = opts.get('boundary')
-        if not boundary or not cgi.valid_boundary(boundary):
+        if not boundary or not valid_boundary(boundary):
             raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
 
 
@@ -266,7 +277,7 @@
         """
         self._producer = producer
         self._empty = False
-        self._leftover = ''
+        self._leftover = b('')
         self.length = length
         self.position = 0
         self._remaining = length
@@ -280,7 +291,7 @@
             remaining = (size is not None and [size] or [self._remaining])[0]
             # do the whole thing in one shot if no limit was provided.
             if remaining is None:
-                yield ''.join(self)
+                yield b('').join(self)
                 return
 
             # otherwise do some bookkeeping to return exactly enough
@@ -296,7 +307,7 @@
                 remaining -= len(emitting)
                 yield emitting
 
-        out = ''.join(parts())
+        out = b('').join(parts())
         return out
 
     def next(self):
@@ -309,7 +320,7 @@
         """
         if self._leftover:
             output = self._leftover
-            self._leftover = ''
+            self._leftover = b('')
         else:
             output = self._producer.next()
             self._unget_history = []
@@ -339,7 +350,7 @@
             return
         self._update_unget_history(len(bytes))
         self.position -= len(bytes)
-        self._leftover = ''.join([bytes, self._leftover])
+        self._leftover = b('').join([bytes, self._leftover])
 
     def _update_unget_history(self, num_bytes):
         """
@@ -451,7 +462,7 @@
             from mx.TextTools import FS
             self._fs = FS(boundary).find
         except ImportError:
-            self._fs = lambda data: data.find(boundary)
+            self._fs = lambda data: data.find(b(boundary))
 
     def __iter__(self):
         return self
@@ -478,7 +489,7 @@
         if not chunks:
             raise StopIteration()
 
-        chunk = ''.join(chunks)
+        chunk = b('').join(chunks)
         boundary = self._find_boundary(chunk, len(chunk) < self._rollback)
 
         if boundary:
@@ -514,9 +525,9 @@
             end = index
             next = index + len(self._boundary)
             # backup over CRLF
-            if data[max(0,end-1)] == '\n':
+            if data[max(0,end-1)] == byte('\n'):
                 end -= 1
-            if data[max(0,end-1)] == '\r':
+            if data[max(0,end-1)] == byte('\r'):
                 end -= 1
             return end, next
 
@@ -550,7 +561,7 @@
     # 'find' returns the top of these four bytes, so we'll
     # need to munch them later to prevent them from polluting
     # the payload.
-    header_end = chunk.find('\r\n\r\n')
+    header_end = chunk.find(b('\r\n\r\n'))
 
     def _parse_header(line):
         main_value_pair, params = parse_header(line)
@@ -576,7 +587,7 @@
     outdict = {}
 
     # Eliminate blank lines
-    for line in header.split('\r\n'):
+    for line in header.split(b('\r\n')):
         # This terminology ("main value" and "dictionary of
         # parameters") is from the Python docs.
         try:
@@ -599,7 +610,7 @@
 class Parser(object):
     def __init__(self, stream, boundary):
         self._stream = stream
-        self._separator = '--' + boundary
+        self._separator = b('--') + boundary
 
     def __iter__(self):
         boundarystream = InterBoundaryIter(self._stream, self._separator)
@@ -609,27 +620,34 @@
 
 def parse_header(line):
     """ Parse the header into a key-value. """
-    plist = _parse_header_params(';' + line)
+    plist = _parse_header_params(b(';') + line)
     key = plist.pop(0).lower()
+    if sys.version_info >= (3,0):
+        # Assume all headers are ASCII, and decode into a string
+        # XXX this might be too restrictive
+        key = key.decode('ascii')
     pdict = {}
     for p in plist:
-        i = p.find('=')
+        i = p.find(b('='))
         if i >= 0:
             name = p[:i].strip().lower()
             value = p[i+1:].strip()
-            if len(value) >= 2 and value[0] == value[-1] == '"':
+            if sys.version_info >= (3,0):
+                # Make sure the parameter names are strings
+                name = name.decode('ascii')
+            if len(value) >= 2 and value[0] == value[-1] == byte('"'):
                 value = value[1:-1]
-                value = value.replace('\\\\', '\\').replace('\\"', '"')
+                value = value.replace(b('\\\\'), b('\\')).replace(b('\\"'), b('"'))
             pdict[name] = value
     return key, pdict
 
 def _parse_header_params(s):
     plist = []
-    while s[:1] == ';':
+    while s[:1] == b(';'):
         s = s[1:]
-        end = s.find(';')
-        while end > 0 and s.count('"', 0, end) % 2:
-            end = s.find(';', end + 1)
+        end = s.find(b(';'))
+        while end > 0 and s.count(b('"'), 0, end) % 2:
+            end = s.find(b(';'), end + 1)
         if end < 0:
             end = len(s)
         f = s[:end]
Index: django/http/__init__.py
===================================================================
--- django/http/__init__.py	(Revision 12361)
+++ django/http/__init__.py	(Arbeitskopie)
@@ -1,4 +1,4 @@
-import os
+import os, sys
 import re
 from Cookie import BaseCookie, SimpleCookie, CookieError
 from pprint import pformat
@@ -12,6 +12,7 @@
 
 from django.utils.datastructures import MultiValueDict, ImmutableList
 from django.utils.encoding import smart_str, iri_to_uri, force_unicode
+from django.utils.py3 import bytes
 from django.http.multipartparser import MultiPartParser
 from django.conf import settings
 from django.core.files import uploadhandler
@@ -144,6 +145,8 @@
             from django.conf import settings
             encoding = settings.DEFAULT_CHARSET
         self.encoding = encoding
+        if isinstance(query_string, bytes):
+            query_string = query_string.decode('ascii')
         for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
             self.appendlist(force_unicode(key, encoding, errors='replace'),
                             force_unicode(value, encoding, errors='replace'))
@@ -336,7 +339,11 @@
         for value in values:
             if isinstance(value, unicode):
                 try:
-                    value = value.encode('us-ascii')
+                    if sys.version_info < (3,0):
+                        value = value.encode('us-ascii')
+                    else:
+                        # In 3k, still use Unicode strings in headers
+                        value.encode('us-ascii')
                 except UnicodeError, e:
                     e.reason += ', HTTP response headers must be in US-ASCII format'
                     raise
@@ -408,7 +415,7 @@
         chunk = self._iterator.next()
         if isinstance(chunk, unicode):
             chunk = chunk.encode(self._charset)
-        return str(chunk)
+        return bytes(chunk)
 
     def close(self):
         if hasattr(self._container, 'close'):
@@ -489,7 +496,7 @@
 
     Returns any non-basestring objects without change.
     """
-    if isinstance(s, str):
+    if isinstance(s, bytes):
         return unicode(s, encoding, 'replace')
     else:
         return s
Index: django/conf/project_template/manage.py
===================================================================
--- django/conf/project_template/manage.py	(Revision 12361)
+++ django/conf/project_template/manage.py	(Arbeitskopie)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 from django.core.management import execute_manager
 try:
-    import settings # Assumed to be in the same directory.
+    from . import  settings # XXX fixer should do from import # Assumed to be in the same directory.
 except ImportError:
     import sys
     sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
Index: django/db/models/base.py
===================================================================
--- django/db/models/base.py	(Revision 12361)
+++ django/db/models/base.py	(Arbeitskopie)
@@ -340,11 +340,16 @@
             u = unicode(self)
         except (UnicodeEncodeError, UnicodeDecodeError):
             u = '[Bad Unicode data]'
-        return smart_str(u'<%s: %s>' % (self.__class__.__name__, u))
+        if sys.version_info < (3,0):
+            return smart_str(u'<%s: %s>' % (self.__class__.__name__, u))
+        else:
+            return '<%s: %s>' % (self.__class__.__name__, u)
 
     def __str__(self):
         if hasattr(self, '__unicode__'):
-            return force_unicode(self).encode('utf-8')
+            if sys.version_info < (3,0):
+                return force_unicode(self).encode('utf-8')
+            return force_unicode(self)
         return '%s object' % self.__class__.__name__
 
     def __eq__(self, other):
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(Revision 12361)
+++ django/db/models/fields/__init__.py	(Arbeitskopie)
@@ -153,6 +153,10 @@
         # This is needed because bisect does not take a comparison function.
         return cmp(self.creation_counter, other.creation_counter)
 
+    # XXX Shouldn't a fixer insert the proper methods?
+    def __lt__(self, other):
+        return self.creation_counter < other.creation_counter
+
     def __deepcopy__(self, memodict):
         # We don't have to deepcopy very much here, since most things are not
         # intended to be altered after initial creation.
Index: django/db/backends/sqlite3/base.py
===================================================================
--- django/db/backends/sqlite3/base.py	(Revision 12361)
+++ django/db/backends/sqlite3/base.py	(Arbeitskopie)
@@ -17,6 +17,7 @@
 from django.db.backends.sqlite3.introspection import DatabaseIntrospection
 from django.utils.safestring import SafeString
 
+from django.utils.py3 import b
 try:
     try:
         from pysqlite2 import dbapi2 as Database
@@ -36,7 +37,7 @@
 DatabaseError = Database.DatabaseError
 IntegrityError = Database.IntegrityError
 
-Database.register_converter("bool", lambda s: str(s) == '1')
+Database.register_converter("bool", lambda s: s == b('1'))
 Database.register_converter("time", util.typecast_time)
 Database.register_converter("date", util.typecast_date)
 Database.register_converter("datetime", util.typecast_timestamp)
Index: django/db/backends/util.py
===================================================================
--- django/db/backends/util.py	(Revision 12361)
+++ django/db/backends/util.py	(Arbeitskopie)
@@ -65,6 +65,8 @@
     # "2005-07-29 15:48:00.590358-05"
     # "2005-07-29 09:56:00-05"
     if not s: return None
+    # XXX should the database pass in Unicode here already?
+    s = s.decode("ascii")
     if not ' ' in s: return typecast_date(s)
     d, t = s.split()
     # Extract timezone information, if it exists. Currently we just throw
Index: django/forms/formsets.py
===================================================================
--- django/forms/formsets.py	(Revision 12361)
+++ django/forms/formsets.py	(Arbeitskopie)
@@ -191,14 +191,12 @@
             # A sort function to order things numerically ascending, but
             # None should be sorted below anything else. Allowing None as
             # a comparison value makes it so we can leave ordering fields
-            # blamk.
-            def compare_ordering_values(x, y):
-                if x[1] is None:
-                    return 1
-                if y[1] is None:
-                    return -1
-                return x[1] - y[1]
-            self._ordering.sort(compare_ordering_values)
+            # blank.
+            def compare_ordering_key(k):
+                if k[1] is None:
+                    return (1, 0) # +infinity, larger than any number
+                return (0, k[1])
+            self._ordering.sort(key=compare_ordering_key)
         # Return a list of form.cleaned_data dicts in the order spcified by
         # the form data.
         return [self.forms[i[0]] for i in self._ordering]
Index: django/forms/forms.py
===================================================================
--- django/forms/forms.py	(Revision 12361)
+++ django/forms/forms.py	(Arbeitskopie)
@@ -34,7 +34,7 @@
     Also integrates any additional media definitions
     """
     fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
-    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
+    fields.sort(key=lambda x: x[1].creation_counter)
 
     # If this class is subclassing another Form, add that Form's fields.
     # Note that we loop over the bases in *reverse*. This is necessary in
Index: django/__init__.py
===================================================================
--- django/__init__.py	(Revision 12361)
+++ django/__init__.py	(Arbeitskopie)
@@ -1,5 +1,7 @@
 VERSION = (1, 2, 0, 'alpha', 1)
 
+from django.utils.py3 import u
+
 def get_version():
     version = '%s.%s' % (VERSION[0], VERSION[1])
     if VERSION[2]:
@@ -11,6 +13,6 @@
             version = '%s %s %s' % (version, VERSION[3], VERSION[4])
     from django.utils.version import get_svn_revision
     svn_rev = get_svn_revision()
-    if svn_rev != u'SVN-unknown':
+    if svn_rev != u('SVN-unknown'):
         version = "%s %s" % (version, svn_rev)
     return version
Index: django/core/mail/message.py
===================================================================
--- django/core/mail/message.py	(Revision 12361)
+++ django/core/mail/message.py	(Arbeitskopie)
@@ -2,12 +2,21 @@
 import os
 import random
 import time
-from email import Charset, Encoders
-from email.MIMEText import MIMEText
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEBase import MIMEBase
-from email.Header import Header
-from email.Utils import formatdate, getaddresses, formataddr
+import sys
+if sys.version_info < (2,5): # email package renamed to lowercase in 2.5
+    from email import Charset, Encoders
+    from email.MIMEText import MIMEText
+    from email.MIMEMultipart import MIMEMultipart
+    from email.MIMEBase import MIMEBase
+    from email.Header import Header
+    from email.Utils import formatdate, getaddresses, formataddr
+else:
+    from email import charset as Charset, encoders as Encoders
+    from email.mime.text import MIMEText
+    from email.mime.multipart import MIMEMultipart
+    from email.mime.base import MIMEBase
+    from email.header import Header
+    from email.utils import formatdate, getaddresses, formataddr
 
 from django.conf import settings
 from django.core.mail.utils import DNS_NAME
Index: django/core/servers/basehttp.py
===================================================================
--- django/core/servers/basehttp.py	(Revision 12361)
+++ django/core/servers/basehttp.py	(Arbeitskopie)
@@ -18,6 +18,7 @@
 from django.core.management.color import color_style
 from django.utils.http import http_date
 from django.utils._os import safe_join
+from django.utils.py3 import b, bytes
 
 __version__ = "0.1"
 __all__ = ['WSGIServer','WSGIRequestHandler']
@@ -318,7 +319,7 @@
         """
         if not self.result_is_file() or not self.sendfile():
             for data in self.result:
-                self.write(data)
+                self.write(b(data))
             self.finish_content()
         self.close()
 
@@ -376,20 +377,20 @@
         """Transmit version/status/date/server, via self._write()"""
         if self.origin_server:
             if self.client_is_modern():
-                self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
+                self._write(b('HTTP/%s %s\r\n' % (self.http_version,self.status)))
                 if 'Date' not in self.headers:
                     self._write(
-                        'Date: %s\r\n' % http_date()
+                        b('Date: %s\r\n' % http_date())
                     )
                 if self.server_software and 'Server' not in self.headers:
-                    self._write('Server: %s\r\n' % self.server_software)
+                    self._write(b('Server: %s\r\n' % self.server_software))
         else:
-            self._write('Status: %s\r\n' % self.status)
+            self._write(b('Status: %s\r\n' % self.status))
 
     def write(self, data):
         """'write()' callable as specified by PEP 333"""
 
-        assert isinstance(data, str), "write() argument must be string"
+        assert isinstance(data, bytes), "write() argument must be string"
 
         if not self.status:
             raise AssertionError("write() before start_response()")
@@ -462,7 +463,7 @@
         self.headers_sent = True
         if not self.origin_server or self.client_is_modern():
             self.send_preamble()
-            self._write(str(self.headers))
+            self._write(b(str(self.headers)))
 
     def result_is_file(self):
         """True if 'self.result' is an instance of 'self.wsgi_file_wrapper'"""
@@ -574,17 +575,25 @@
         env['QUERY_STRING'] = query
         env['REMOTE_ADDR'] = self.client_address[0]
 
-        if self.headers.typeheader is None:
-            env['CONTENT_TYPE'] = self.headers.type
+        if self.headers.get('content-type') is None:
+            # 3.x: self.headers.type is gone. Since it should
+            # be text/plain anyway if no content-type header is
+            # present, accessing .type is redundant
+            env['CONTENT_TYPE'] = 'text/plain'
         else:
-            env['CONTENT_TYPE'] = self.headers.typeheader
+            env['CONTENT_TYPE'] = self.headers.get('content-type')
 
-        length = self.headers.getheader('content-length')
+        length = self.headers.get('content-length')
         if length:
             env['CONTENT_LENGTH'] = length
 
-        for h in self.headers.headers:
-            k,v = h.split(':',1)
+        if sys.version_info < (3,0):
+            headers = [h.split(':', 1) for h in self.headers.headers]
+        else:
+            # 3.x: .headers is gone; iterating over message itself
+            # yields duplicate headers as necessary
+            headers = self.headers.items()
+        for k,v in headers:
             k=k.replace('-','_').upper(); v=v.strip()
             if k in env:
                 continue                    # skip content length, type,etc.
Index: django/core/urlresolvers.py
===================================================================
--- django/core/urlresolvers.py	(Revision 12361)
+++ django/core/urlresolvers.py	(Arbeitskopie)
@@ -13,7 +13,7 @@
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
 from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import iri_to_uri, force_unicode, smart_str
+from django.utils.encoding import iri_to_uri, force_unicode, smart_kw
 from django.utils.functional import memoize
 from django.utils.importlib import import_module
 from django.utils.regex_helper import normalize
@@ -56,7 +56,7 @@
     if not callable(lookup_view):
         try:
             # Bail early for non-ASCII strings (they can't be functions).
-            lookup_view = lookup_view.encode('ascii')
+            lookup_view.encode('ascii')
             mod_name, func_name = get_mod_func(lookup_view)
             if func_name != '':
                 lookup_view = getattr(import_module(mod_name), func_name)
@@ -228,10 +228,10 @@
                         tried.append(pattern.regex.pattern)
                 else:
                     if sub_match:
-                        sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
+                        sub_match_dict = dict([(smart_kw(k), v) for k, v in match.groupdict().items()])
                         sub_match_dict.update(self.default_kwargs)
                         for k, v in sub_match[2].iteritems():
-                            sub_match_dict[smart_str(k)] = v
+                            sub_match_dict[smart_kw(k)] = v
                         return sub_match[0], sub_match[1], sub_match_dict
                     tried.append(pattern.regex.pattern)
             raise Resolver404({'tried': tried, 'path': new_path})
Index: django/core/handlers/wsgi.py
===================================================================
--- django/core/handlers/wsgi.py	(Revision 12361)
+++ django/core/handlers/wsgi.py	(Arbeitskopie)
@@ -1,9 +1,12 @@
 from threading import Lock
 from pprint import pformat
 try:
-    from cStringIO import StringIO
+    from io import BytesIO as StringIO
 except ImportError:
-    from StringIO import StringIO
+    try:
+        from cStringIO import StringIO
+    except ImportError:
+        from StringIO import StringIO
 
 from django import http
 from django.core import signals
Index: django/core/management/commands/loaddata.py
===================================================================
--- django/core/management/commands/loaddata.py	(Revision 12361)
+++ django/core/management/commands/loaddata.py	(Arbeitskopie)
@@ -73,7 +73,7 @@
                 return zipfile.ZipFile.read(self, self.namelist()[0])
 
         compression_types = {
-            None:   file,
+            None:   open,
             'gz':   gzip.GzipFile,
             'zip':  SingleZipReader
         }
Index: django/core/management/base.py
===================================================================
--- django/core/management/base.py	(Revision 12361)
+++ django/core/management/base.py	(Arbeitskopie)
@@ -11,6 +11,7 @@
 import django
 from django.core.exceptions import ImproperlyConfigured
 from django.core.management.color import color_style
+from django.utils.py3 import b
 
 try:
     set
@@ -221,6 +222,9 @@
                 self.validate()
             output = self.handle(*args, **options)
             if output:
+                if sys.version_info >= (3,0):
+                    # Decode to Unicode so that print can render it correctly
+                    output = output.decode('utf-8')
                 if self.output_transaction:
                     # This needs to be imported here, because it relies on settings.
                     from django.db import connection
@@ -286,7 +290,7 @@
             app_output = self.handle_app(app, **options)
             if app_output:
                 output.append(app_output)
-        return '\n'.join(output)
+        return b('\n').join(output)
 
     def handle_app(self, app, **options):
         """
@@ -404,9 +408,10 @@
                 continue
             path_old = os.path.join(d, f)
             path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name))
-            fp_old = open(path_old, 'r')
-            fp_new = open(path_new, 'w')
-            fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name))
+            fp_old = open(path_old, 'rb')
+            fp_new = open(path_new, 'wb')
+            # XXX file encoding is actually unknown; use latin-1 as a hack
+            fp_new.write(fp_old.read().decode("latin-1").replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name).encode("latin-1"))
             fp_old.close()
             fp_new.close()
             try:
Index: django/views/i18n.py
===================================================================
--- django/views/i18n.py	(Revision 12361)
+++ django/views/i18n.py	(Arbeitskopie)
@@ -1,4 +1,4 @@
-import os
+import os, sys
 import gettext as gettext_module
 
 from django import http
@@ -55,6 +55,10 @@
                 pass
     src = []
     for k, v in result.items():
+        if sys.version_info >= (3,0):
+            # XXX why is it necessary to javascript_quote the keys, when they are from
+            # FORMAT_SETTINGS, which is all safe?
+            k = k.encode('ascii')
         if isinstance(v, (basestring, int)):
             src.append("formats['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(smart_unicode(v))))
         elif isinstance(v, (tuple, list)):
Index: django/views/debug.py
===================================================================
--- django/views/debug.py	(Revision 12361)
+++ django/views/debug.py	(Arbeitskopie)
@@ -9,6 +9,7 @@
 from django.utils.importlib import import_module
 from django.http import HttpResponse, HttpResponseServerError, HttpResponseNotFound
 from django.utils.encoding import smart_unicode, smart_str
+from django.utils.py3 import b
 
 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
 
@@ -179,7 +180,7 @@
                 source = source.splitlines()
         if source is None:
             try:
-                f = open(filename)
+                f = open(filename, 'rb')
                 try:
                     source = f.readlines()
                 finally:
@@ -193,7 +194,7 @@
         for line in source[:2]:
             # File coding may be specified. Match pattern from PEP-263
             # (http://www.python.org/dev/peps/pep-0263/)
-            match = re.search(r'coding[:=]\s*([-\w.]+)', line)
+            match = re.search(b(r'coding[:=]\s*([-\w.]+)'), line)
             if match:
                 encoding = match.group(1)
                 break
Index: django/utils/html.py
===================================================================
--- django/utils/html.py	(Revision 12361)
+++ django/utils/html.py	(Arbeitskopie)
@@ -25,7 +25,10 @@
 html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
 hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL)
 trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')
-del x # Temporary variable
+try:
+    del x # Temporary variable, gone in 3k
+except NameError:
+    pass
 
 def escape(html):
     """
Index: django/utils/safestring.py
===================================================================
--- django/utils/safestring.py	(Revision 12361)
+++ django/utils/safestring.py	(Arbeitskopie)
@@ -5,11 +5,12 @@
 be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
 """
 from django.utils.functional import curry, Promise
+from django.utils.py3 import bytes, b
 
 class EscapeData(object):
     pass
 
-class EscapeString(str, EscapeData):
+class EscapeString(bytes, EscapeData):
     """
     A string that should be HTML-escaped when output.
     """
@@ -24,7 +25,7 @@
 class SafeData(object):
     pass
 
-class SafeString(str, SafeData):
+class SafeString(bytes, SafeData):
     """
     A string subclass that has been specifically marked as "safe" (requires no
     further escaping) for HTML output purposes.
@@ -49,12 +50,12 @@
         """
         method = kwargs.pop('method')
         data = method(self, *args, **kwargs)
-        if isinstance(data, str):
+        if isinstance(data, bytes):
             return SafeString(data)
         else:
             return SafeUnicode(data)
 
-    decode = curry(_proxy_method, method = str.decode)
+    decode = curry(_proxy_method, method = bytes.decode)
 
 class SafeUnicode(unicode, SafeData):
     """
@@ -79,7 +80,7 @@
         """
         method = kwargs.pop('method')
         data = method(self, *args, **kwargs)
-        if isinstance(data, str):
+        if isinstance(data, bytes):
             return SafeString(data)
         else:
             return SafeUnicode(data)
@@ -95,11 +96,11 @@
     """
     if isinstance(s, SafeData):
         return s
-    if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
+    if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str):
         return SafeString(s)
     if isinstance(s, (unicode, Promise)):
         return SafeUnicode(s)
-    return SafeString(str(s))
+    return SafeString(b(str(s)))
 
 def mark_for_escaping(s):
     """
@@ -111,7 +112,7 @@
     """
     if isinstance(s, (SafeData, EscapeData)):
         return s
-    if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
+    if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_str):
         return EscapeString(s)
     if isinstance(s, (unicode, Promise)):
         return EscapeUnicode(s)
Index: django/utils/translation/trans_real.py
===================================================================
--- django/utils/translation/trans_real.py	(Revision 12361)
+++ django/utils/translation/trans_real.py	(Arbeitskopie)
@@ -287,8 +287,11 @@
 def gettext(message):
     return do_translate(message, 'gettext')
 
-def ugettext(message):
-    return do_translate(message, 'ugettext')
+if sys.version_info < (3, 0):
+    def ugettext(message):
+        return do_translate(message, 'ugettext')
+else:
+    ugettext = gettext
 
 def gettext_noop(message):
     """
@@ -323,6 +326,8 @@
     plural, based on the number.
     """
     return do_ntranslate(singular, plural, number, 'ungettext')
+if sys.version_info >= (3,0):
+    ungettext = ngettext
 
 def check_for_language(lang_code):
     """
@@ -507,7 +512,7 @@
             return []
         priority = priority and float(priority) or 1.0
         result.append((lang, priority))
-    result.sort(lambda x, y: -cmp(x[1], y[1]))
+    result.sort(key=lambda k: k[1], reverse=True)
     return result
 
 # get_date_formats and get_partial_date_formats aren't used anymore by Django
Index: django/utils/translation/__init__.py
===================================================================
--- django/utils/translation/__init__.py	(Revision 12361)
+++ django/utils/translation/__init__.py	(Arbeitskopie)
@@ -3,6 +3,7 @@
 """
 from django.utils.functional import lazy
 from django.utils.encoding import force_unicode
+from django.utils.py3 import bytes
 
 __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
         'ngettext_lazy', 'string_concat', 'activate', 'deactivate',
@@ -64,8 +65,8 @@
 def ungettext(singular, plural, number):
     return real_ungettext(singular, plural, number)
 
-ngettext_lazy = lazy(ngettext, str)
-gettext_lazy = lazy(gettext, str)
+ngettext_lazy = lazy(ngettext, bytes)
+gettext_lazy = lazy(gettext, bytes)
 ungettext_lazy = lazy(ungettext, unicode)
 ugettext_lazy = lazy(ugettext, unicode)
 
Index: django/utils/py3.py
===================================================================
--- django/utils/py3.py	(Revision 0)
+++ django/utils/py3.py	(Revision 0)
@@ -0,0 +1,33 @@
+# Compatibility layer for running Django both in 2.x and 3.x
+"""
+This module currently provides the following helper symbols
+ * bytes (name of byte string type; str in 2.x, bytes in 3.x)
+ * b (function converting a string literal to an ASCII byte string;
+      can be also used to convert a Unicode string with only ASCII
+      characters into a byte string)
+ * byte (data type for an individual byte)
+"""
+import sys
+
+if sys.version_info < (3,0):
+    b = bytes = str
+    def byte(n):
+        return n
+    u = unicode
+    def next(i):
+        return i.next()
+else:
+    bytes = __builtins__['bytes']
+    def b(s):
+        if isinstance(s, str):
+            return s.encode("ascii")
+        elif isinstance(s, bytes):
+            return s
+        else:
+            raise TypeError("Invalid argument %r for b()" % (s,))
+    def byte(n):
+        # assume n is a Latin-1 string of length 1
+        return ord(n)
+    u = str
+    next = __builtins__['next']
+    

Eigenschaftsänderungen: django/utils/py3.py
___________________________________________________________________
Hinzugefügt: svn:keywords
   + Id
Hinzugefügt: svn:eol-style
   + native

Index: django/utils/formats.py
===================================================================
--- django/utils/formats.py	(Revision 12361)
+++ django/utils/formats.py	(Arbeitskopie)
@@ -1,3 +1,4 @@
+import sys
 import decimal
 import datetime
 
@@ -4,7 +5,7 @@
 from django.conf import settings
 from django.utils.translation import get_language, to_locale, check_for_language
 from django.utils.importlib import import_module
-from django.utils.encoding import smart_str
+from django.utils.encoding import smart_str, smart_unicode
 from django.utils import dateformat, numberformat, datetime_safe
 
 def get_format_modules(reverse=False):
@@ -40,7 +41,10 @@
     language (locale), defaults to the format in the settings.
     format_type is the name of the format, e.g. 'DATE_FORMAT'
     """
-    format_type = smart_str(format_type)
+    if sys.version_info < (3,0):
+        format_type = smart_str(format_type)
+    else:
+        format_type = smart_unicode(format_type)
     if settings.USE_L10N:
         for module in get_format_modules():
             try:
@@ -99,15 +103,19 @@
     Checks if an input value is a localizable type and returns it
     formatted with the appropriate formatting string of the current locale.
     """
+    if sys.version_info <= (3,0):
+        fmtfun = smart_str
+    else:
+        fmtfun = smart_unicode
     if isinstance(value, datetime.datetime):
         value = datetime_safe.new_datetime(value)
-        format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
+        format = fmtfun(default or get_format('DATETIME_INPUT_FORMATS')[0])
         return value.strftime(format)
     elif isinstance(value, datetime.date):
         value = datetime_safe.new_date(value)
-        format = smart_str(default or get_format('DATE_INPUT_FORMATS')[0])
+        format = fmtfun(default or get_format('DATE_INPUT_FORMATS')[0])
         return value.strftime(format)
     elif isinstance(value, datetime.time):
-        format = smart_str(default or get_format('TIME_INPUT_FORMATS')[0])
+        format = fmtfun(default or get_format('TIME_INPUT_FORMATS')[0])
         return value.strftime(format)
     return value
Index: django/utils/http.py
===================================================================
--- django/utils/http.py	(Revision 12361)
+++ django/utils/http.py	(Arbeitskopie)
@@ -1,6 +1,9 @@
 import re
 import urllib
-from email.Utils import formatdate
+try:
+    from email.Utils import formatdate
+except ImportError:
+    from email.utils import formatdate
 
 from django.utils.encoding import smart_str, force_unicode
 from django.utils.functional import allow_lazy
Index: django/utils/simplejson/encoder.py
===================================================================
--- django/utils/simplejson/encoder.py	(Revision 12361)
+++ django/utils/simplejson/encoder.py	(Arbeitskopie)
@@ -251,10 +251,10 @@
 
 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
         ## HACK: hand-optimized bytecode; turn globals into locals
-        False=False,
-        True=True,
+        _False=False,
+        _True=True,
         ValueError=ValueError,
-        basestring=basestring,
+        _basestring=basestring, # XXX bug in fixer: replaces LHS with str
         dict=dict,
         float=float,
         id=id,
@@ -284,19 +284,19 @@
         else:
             newline_indent = None
             separator = _item_separator
-        first = True
+        first = _True
         for value in lst:
             if first:
-                first = False
+                first = _False
             else:
                 buf = separator
-            if isinstance(value, basestring):
+            if isinstance(value, _basestring):
                 yield buf + _encoder(value)
             elif value is None:
                 yield buf + 'null'
-            elif value is True:
+            elif value is _True:
                 yield buf + 'true'
-            elif value is False:
+            elif value is _False:
                 yield buf + 'false'
             elif isinstance(value, (int, long)):
                 yield buf + str(value)
@@ -337,14 +337,14 @@
         else:
             newline_indent = None
             item_separator = _item_separator
-        first = True
+        first = _True
         if _sort_keys:
             items = dct.items()
             items.sort(key=lambda kv: kv[0])
         else:
             items = dct.iteritems()
         for key, value in items:
-            if isinstance(key, basestring):
+            if isinstance(key, _basestring):
                 pass
             # JavaScript is weakly typed for these, so it makes sense to
             # also allow them.  Many encoders seem to do something like this.
@@ -352,9 +352,9 @@
                 key = _floatstr(key)
             elif isinstance(key, (int, long)):
                 key = str(key)
-            elif key is True:
+            elif key is _True:
                 key = 'true'
-            elif key is False:
+            elif key is _False:
                 key = 'false'
             elif key is None:
                 key = 'null'
@@ -363,18 +363,18 @@
             else:
                 raise TypeError("key %r is not a string" % (key,))
             if first:
-                first = False
+                first = _False
             else:
                 yield item_separator
             yield _encoder(key)
             yield _key_separator
-            if isinstance(value, basestring):
+            if isinstance(value, _basestring):
                 yield _encoder(value)
             elif value is None:
                 yield 'null'
-            elif value is True:
+            elif value is _True:
                 yield 'true'
-            elif value is False:
+            elif value is _False:
                 yield 'false'
             elif isinstance(value, (int, long)):
                 yield str(value)
@@ -397,13 +397,13 @@
             del markers[markerid]
 
     def _iterencode(o, _current_indent_level):
-        if isinstance(o, basestring):
+        if isinstance(o, _basestring):
             yield _encoder(o)
         elif o is None:
             yield 'null'
-        elif o is True:
+        elif o is _True:
             yield 'true'
-        elif o is False:
+        elif o is _False:
             yield 'false'
         elif isinstance(o, (int, long)):
             yield str(o)
Index: django/utils/simplejson/decoder.py
===================================================================
--- django/utils/simplejson/decoder.py	(Revision 12361)
+++ django/utils/simplejson/decoder.py	(Arbeitskopie)
@@ -1,6 +1,7 @@
 """Implementation of JSONDecoder
 """
 import re
+from binascii import unhexlify
 import sys
 import struct
 
@@ -12,7 +13,7 @@
 FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
 
 def _floatconstants():
-    _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
+    _BYTES = unhexlify('7FF80000000000007FF0000000000000')
     if sys.byteorder != 'big':
         _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
     nan, inf = struct.unpack('dd', _BYTES)
Index: django/utils/functional.py
===================================================================
--- django/utils/functional.py	(Revision 12361)
+++ django/utils/functional.py	(Arbeitskopie)
@@ -49,6 +49,8 @@
 # agrees to be bound by the terms and conditions of this License
 # Agreement.
 
+import sys, operator
+from django.utils.py3 import bytes
 
 def curry(_curried_func, *args, **kwargs):
     def _curried(*moreargs, **morekwargs):
@@ -174,11 +176,13 @@
                     if hasattr(cls, k):
                         continue
                     setattr(cls, k, meth)
-            cls._delegate_str = str in resultclasses
+            cls._delegate_str = bytes in resultclasses
             cls._delegate_unicode = unicode in resultclasses
             assert not (cls._delegate_str and cls._delegate_unicode), "Cannot call lazy() with both str and unicode return types."
             if cls._delegate_unicode:
                 cls.__unicode__ = cls.__unicode_cast
+                if sys.version_info >= (3,0):
+                    __proxy__.__str__ = __proxy__.__unicode_cast
             elif cls._delegate_str:
                 cls.__str__ = cls.__str_cast
         __prepare_class__ = classmethod(__prepare_class__)
@@ -205,23 +209,40 @@
             return self.__func(*self.__args, **self.__kw)
 
         def __str_cast(self):
-            return str(self.__func(*self.__args, **self.__kw))
+            return bytes(self.__func(*self.__args, **self.__kw))
 
-        def __cmp__(self, rhs):
+        def _cmp_(self, rhs, op, negop):
             if self._delegate_str:
-                s = str(self.__func(*self.__args, **self.__kw))
+                s = bytes(self.__func(*self.__args, **self.__kw))
             elif self._delegate_unicode:
                 s = unicode(self.__func(*self.__args, **self.__kw))
             else:
                 s = self.__func(*self.__args, **self.__kw)
             if isinstance(rhs, Promise):
-                return -cmp(rhs, s)
+                return negop(rhs, s)
             else:
-                return cmp(s, rhs)
+                return op(s, rhs)
 
+        def __cmp__(self, rhs):
+            return self._cmp_(rhs, cmp, lambda a,b:-cmp(a,b))
+
+        if sys.version_info > (3,0):
+            def __lt__(self, rhs):
+                return self._cmp_(rhs, operator.__lt__, operator.__ge__)
+            def __le__(self, rhs):
+                return self._cmp_(rhs, operator.__le__, operator.__gt__)
+            def __eq__(self, rhs):
+                return self._cmp_(rhs, operator.__eq__, operator.__ne__)
+            def __ne(self, rhs):
+                return self._cmp_(rhs, operator.__ne__, operator.__eq__)
+            def __ge__(self, rhs):
+                return self._cmp_(rhs, operator.__ge__, operator.__lt__)
+            def __gt__(self, rhs):
+                return self._cmp_(rhs, operator.__gt__, operator.__le__)
+
         def __mod__(self, rhs):
             if self._delegate_str:
-                return str(self) % rhs
+                return bytes(self) % rhs
             elif self._delegate_unicode:
                 return unicode(self) % rhs
             else:
Index: django/utils/text.py
===================================================================
--- django/utils/text.py	(Revision 12361)
+++ django/utils/text.py	(Arbeitskopie)
@@ -3,7 +3,12 @@
 from django.utils.encoding import force_unicode
 from django.utils.functional import allow_lazy
 from django.utils.translation import ugettext_lazy
-from htmlentitydefs import name2codepoint
+try:
+    from htmlentitydefs import name2codepoint
+except ImportError:
+    # XXX fixer replaces this with relative import,
+    # due to presence of html module
+    name2codepoint = __import__('html.entities').entities.name2codepoint
 
 # Capitalizes the first letter of a string.
 capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:]
@@ -183,7 +188,7 @@
     def fix(match):
         return r"\u%04x" % ord(match.group(1))
 
-    if type(s) == str:
+    if type(s) == bytes:
         s = s.decode('utf-8')
     elif type(s) != unicode:
         raise TypeError(s)
Index: django/utils/version.py
===================================================================
--- django/utils/version.py	(Revision 12361)
+++ django/utils/version.py	(Arbeitskopie)
@@ -1,6 +1,7 @@
 import django
 import os.path
 import re
+from django.utils.py3 import u
 
 def get_svn_revision(path=None):
     """
@@ -38,5 +39,5 @@
             rev = dom.getElementsByTagName('entry')[0].getAttribute('revision')
 
     if rev:
-        return u'SVN-%s' % rev
-    return u'SVN-unknown'
+        return u('SVN-%s' % rev)
+    return u('SVN-unknown')
Index: django/utils/encoding.py
===================================================================
--- django/utils/encoding.py	(Revision 12361)
+++ django/utils/encoding.py	(Arbeitskopie)
@@ -1,3 +1,4 @@
+import sys
 import types
 import urllib
 import locale
@@ -28,8 +29,12 @@
 
     Useful as a mix-in.
     """
-    def __str__(self):
-        return self.__unicode__().encode('utf-8')
+    if sys.version_info < (3,0):
+        def __str__(self):
+            return self.__unicode__().encode('utf-8')
+    else:
+        def __str__(self):
+            return self.__unicode__()
 
 def smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
     """
@@ -66,12 +71,19 @@
     if strings_only and is_protected_type(s):
         return s
     try:
-        if not isinstance(s, basestring,):
+        # 2to3 fixes basestring into str, dropping support for bytes
+        if not isinstance(s, (bytes, unicode)):
             if hasattr(s, '__unicode__'):
-                s = unicode(s)
+                s = s.__unicode__()
             else:
                 try:
-                    s = unicode(str(s), encoding, errors)
+                    if sys.version_info >= (3,0):
+                        if isinstance(s, bytes):
+                            s = str(s, encoding, errors)
+                        else:
+                            s = str(s)
+                    else:
+                        s = unicode(str(s), encoding, errors)
                 except UnicodeEncodeError:
                     if not isinstance(s, Exception):
                         raise
@@ -120,6 +132,11 @@
     else:
         return s
 
+if sys.version_info < (3,0):
+    smart_kw = smart_str
+else:
+    smart_kw = smart_unicode
+
 def iri_to_uri(iri):
     """
     Convert an Internationalized Resource Identifier (IRI) portion to a URI
@@ -145,7 +162,8 @@
     # converted.
     if iri is None:
         return iri
-    return urllib.quote(smart_str(iri), safe="/#%[]=:;$&()+,!?*@'~")
+    # XXX 3k expects a Unicode string for quote.
+    return urllib.quote(smart_unicode(iri), safe="/#%[]=:;$&()+,!?*@'~")
 
 
 # The encoding of the default system locale but falls back to the
Index: django/contrib/admin/options.py
===================================================================
--- django/contrib/admin/options.py	(Revision 12361)
+++ django/contrib/admin/options.py	(Arbeitskopie)
@@ -498,7 +498,7 @@
 
         # Convert the actions into a SortedDict keyed by name
         # and sorted by description.
-        actions.sort(lambda a,b: cmp(a[2].lower(), b[2].lower()))
+        actions.sort(key=lambda k: k[2].lower())
         actions = SortedDict([
             (name, (func, name, desc))
             for func, name, desc in actions
Index: django/contrib/admin/helpers.py
===================================================================
--- django/contrib/admin/helpers.py	(Revision 12361)
+++ django/contrib/admin/helpers.py	(Arbeitskopie)
@@ -86,7 +86,7 @@
 class Fieldline(object):
     def __init__(self, form, field, readonly_fields=None, model_admin=None):
         self.form = form # A django.forms.Form instance
-        if not hasattr(field, "__iter__"):
+        if not hasattr(field, "__iter__") or isinstance(field, str):
             self.fields = [field]
         else:
             self.fields = field
Index: django/contrib/admin/templatetags/log.py
===================================================================
--- django/contrib/admin/templatetags/log.py	(Revision 12361)
+++ django/contrib/admin/templatetags/log.py	(Arbeitskopie)
@@ -5,7 +5,8 @@
 
 class AdminLogNode(template.Node):
     def __init__(self, limit, varname, user):
-        self.limit, self.varname, self.user = limit, varname, user
+        # XXX why is int() conversion necessary in 3k?
+        self.limit, self.varname, self.user = int(limit), varname, user
 
     def __repr__(self):
         return "<GetAdminLog Node>"
Index: django/contrib/admin/sites.py
===================================================================
--- django/contrib/admin/sites.py	(Revision 12361)
+++ django/contrib/admin/sites.py	(Arbeitskopie)
@@ -378,11 +378,11 @@
 
         # Sort the apps alphabetically.
         app_list = app_dict.values()
-        app_list.sort(lambda x, y: cmp(x['name'], y['name']))
+        app_list.sort(key=lambda x: x['name'])
 
         # Sort the models alphabetically within each app.
         for app in app_list:
-            app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
+            app['models'].sort(key=lambda x: x['name'])
 
         context = {
             'title': _('Site administration'),
@@ -442,7 +442,7 @@
         if not app_dict:
             raise http.Http404('The requested admin page does not exist.')
         # Sort the models alphabetically within each app.
-        app_dict['models'].sort(lambda x, y: cmp(x['name'], y['name']))
+        app_dict['models'].sort(key=lambda x: x['name'])
         context = {
             'title': _('%s administration') % capfirst(app_label),
             'app_list': [app_dict],
Index: django/contrib/databrowse/plugins/calendars.py
===================================================================
--- django/contrib/databrowse/plugins/calendars.py	(Revision 12361)
+++ django/contrib/databrowse/plugins/calendars.py	(Arbeitskopie)
@@ -60,7 +60,7 @@
     def homepage_view(self, request):
         easy_model = EasyModel(self.site, self.model)
         field_list = self.fields.values()
-        field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name))
+        field_list.sort(key=lambda k:k.verbose_name)
         return render_to_response('databrowse/calendar_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list})
 
     def calendar_view(self, request, field, year=None, month=None, day=None):
Index: django/contrib/databrowse/plugins/fieldchoices.py
===================================================================
--- django/contrib/databrowse/plugins/fieldchoices.py	(Revision 12361)
+++ django/contrib/databrowse/plugins/fieldchoices.py	(Arbeitskopie)
@@ -61,7 +61,7 @@
     def homepage_view(self, request):
         easy_model = EasyModel(self.site, self.model)
         field_list = self.fields.values()
-        field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name))
+        field_list.sort(key=lambda k: k.verbose_name)
         return render_to_response('databrowse/fieldchoice_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list})
 
     def field_view(self, request, field, value=None):
Index: django/contrib/sessions/backends/base.py
===================================================================
--- django/contrib/sessions/backends/base.py	(Revision 12361)
+++ django/contrib/sessions/backends/base.py	(Arbeitskopie)
@@ -3,7 +3,9 @@
 import random
 import sys
 import time
+from django.utils.py3 import b
 from datetime import datetime, timedelta
+
 try:
     import cPickle as pickle
 except ImportError:
@@ -86,13 +88,18 @@
     def encode(self, session_dict):
         "Returns the given session dictionary pickled and encoded as a string."
         pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
-        pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
-        return base64.encodestring(pickled + pickled_md5)
+        pickled_md5 = md5_constructor(pickled + b(settings.SECRET_KEY)).hexdigest()
+        res = base64.encodestring(pickled + b(pickled_md5))
+        # 3.0: convert the result back to str, so that the data round-trip
+        # through the database correctly.
+        if sys.version_info >= (3,0):
+            res = res.decode('ascii')
+        return res
 
     def decode(self, session_data):
-        encoded_data = base64.decodestring(session_data)
+        encoded_data = base64.decodestring(session_data.encode('ascii'))
         pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
-        if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
+        if b(md5_constructor(pickled + b(settings.SECRET_KEY)).hexdigest()) != tamper_check:
             raise SuspiciousOperation("User tampered with session cookie.")
         try:
             return pickle.loads(pickled)
@@ -138,9 +145,10 @@
             # No getpid() in Jython, for example
             pid = 1
         while 1:
-            session_key = md5_constructor("%s%s%s%s"
+            # XXX should session_key be a byte string in 3k?
+            session_key = md5_constructor(b("%s%s%s%s"
                     % (randrange(0, MAX_SESSION_KEY), pid, time.time(),
-                       settings.SECRET_KEY)).hexdigest()
+                       settings.SECRET_KEY))).hexdigest()
             if not self.exists(session_key):
                 break
         return session_key
Index: django/contrib/sessions/models.py
===================================================================
--- django/contrib/sessions/models.py	(Revision 12361)
+++ django/contrib/sessions/models.py	(Arbeitskopie)
@@ -3,6 +3,7 @@
 
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
+from django.utils.py3 import b
 from django.conf import settings
 from django.utils.hashcompat import md5_constructor
 
@@ -13,8 +14,8 @@
         Returns the given session dictionary pickled and encoded as a string.
         """
         pickled = pickle.dumps(session_dict)
-        pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
-        return base64.encodestring(pickled + pickled_md5)
+        pickled_md5 = md5_constructor(pickled + b(settings.SECRET_KEY)).hexdigest()
+        return base64.encodestring(pickled + b(pickled_md5))
 
     def save(self, session_key, session_dict, expire_date):
         s = self.model(session_key, self.encode(session_dict), expire_date)
@@ -56,7 +57,7 @@
     def get_decoded(self):
         encoded_data = base64.decodestring(self.session_data)
         pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
-        if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
+        if md5_constructor(pickled + b(settings.SECRET_KEY)).hexdigest() != tamper_check:
             from django.core.exceptions import SuspiciousOperation
             raise SuspiciousOperation("User tampered with session cookie.")
         try:
Index: django/template/__init__.py
===================================================================
--- django/template/__init__.py	(Revision 12361)
+++ django/template/__init__.py	(Arbeitskopie)
@@ -106,10 +106,10 @@
 class TemplateSyntaxError(Exception):
     def __str__(self):
         try:
-            import cStringIO as StringIO
+            import cStringIO as _StringIO # XXX fixer does not handle renaming correctly
         except ImportError:
-            import StringIO
-        output = StringIO.StringIO()
+            import StringIO as _StringIO
+        output = _StringIO.StringIO()
         output.write(Exception.__str__(self))
         # Check if we wrapped an exception and print that too.
         if hasattr(self, 'exc_info'):
Index: django/template/loaders/app_directories.py
===================================================================
--- django/template/loaders/app_directories.py	(Revision 12361)
+++ django/template/loaders/app_directories.py	(Arbeitskopie)
@@ -23,7 +23,9 @@
         raise ImproperlyConfigured('ImportError %s: %s' % (app, e.args[0]))
     template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates')
     if os.path.isdir(template_dir):
-        app_template_dirs.append(template_dir.decode(fs_encoding))
+        if sys.version_info < (3,0):
+            template_dir = template_dir.decode(fs_encoding)
+        app_template_dirs.append(template_dir)
 
 # It won't change, so convert it to a tuple to save memory.
 app_template_dirs = tuple(app_template_dirs)
@@ -52,7 +54,7 @@
     def load_template_source(self, template_name, template_dirs=None):
         for filepath in self.get_template_sources(template_name, template_dirs):
             try:
-                file = open(filepath)
+                file = open(filepath, 'rb')
                 try:
                     return (file.read().decode(settings.FILE_CHARSET), filepath)
                 finally:
Index: django/template/loaders/filesystem.py
===================================================================
--- django/template/loaders/filesystem.py	(Revision 12361)
+++ django/template/loaders/filesystem.py	(Arbeitskopie)
@@ -34,7 +34,7 @@
         tried = []
         for filepath in self.get_template_sources(template_name, template_dirs):
             try:
-                file = open(filepath)
+                file = open(filepath, 'rb')
                 try:
                     return (file.read().decode(settings.FILE_CHARSET), filepath)
                 finally:
Index: django/template/defaulttags.py
===================================================================
--- django/template/defaulttags.py	(Revision 12361)
+++ django/template/defaulttags.py	(Arbeitskopie)
@@ -16,6 +16,7 @@
 from django.utils.encoding import smart_str, smart_unicode
 from django.utils.itercompat import groupby
 from django.utils.safestring import mark_safe
+from django.utils.py3 import next
 
 register = Library()
 
@@ -64,7 +65,7 @@
         if self not in context.render_context:
             context.render_context[self] = itertools_cycle(self.cyclevars)
         cycle_iter = context.render_context[self]
-        value = cycle_iter.next().resolve(context)
+        value = next(cycle_iter).resolve(context)
         if self.variable_name:
             context[self.variable_name] = value
         return value
@@ -1119,7 +1120,7 @@
         bits = iter(bits[2:])
         for bit in bits:
             if bit == 'as':
-                asvar = bits.next()
+                asvar = next(bits)
                 break
             else:
                 for arg in bit.split(","):
Index: django/template/context.py
===================================================================
--- django/template/context.py	(Revision 12361)
+++ django/template/context.py	(Arbeitskopie)
@@ -54,9 +54,12 @@
             if key in d:
                 return True
         return False
+    _has_key = has_key
 
     def __contains__(self, key):
-        return self.has_key(key)
+        # 2to3 fixes  "self.has_key(key)" to "key in self",
+        # causing infinite recursion
+        return self._has_key(key)
 
     def get(self, key, otherwise=None):
         for d in reversed(self.dicts):
Index: django/template/loader_tags.py
===================================================================
--- django/template/loader_tags.py	(Revision 12361)
+++ django/template/loader_tags.py	(Arbeitskopie)
@@ -152,7 +152,7 @@
             template_name = self.template_name.resolve(context)
             t = get_template(template_name)
             return t.render(context)
-        except TemplateSyntaxError, e:
+        except TemplateSyntaxError as e:
             if settings.TEMPLATE_DEBUG:
                 raise
             return ''
Index: django/template/loader.py
===================================================================
--- django/template/loader.py	(Revision 12361)
+++ django/template/loader.py	(Arbeitskopie)
@@ -72,7 +72,7 @@
         return None
 
 def find_template_loader(loader):
-    if hasattr(loader, '__iter__'):
+    if hasattr(loader, '__iter__') and not isinstance(loader, str):
         loader, args = loader[0], loader[1:]
     else:
         args = []
Index: django/middleware/csrf.py
===================================================================
--- django/middleware/csrf.py	(Revision 12361)
+++ django/middleware/csrf.py	(Arbeitskopie)
@@ -34,8 +34,8 @@
     return get_callable(settings.CSRF_FAILURE_VIEW)
 
 def _get_new_csrf_key():
-    return md5_constructor("%s%s"
-                % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
+    return md5_constructor((u"%s%s"
+                % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).encode('ascii')).hexdigest()
 
 def _make_legacy_session_token(session_id):
     return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
Index: setup.py
===================================================================
--- setup.py	(Revision 12361)
+++ setup.py	(Arbeitskopie)
@@ -4,6 +4,20 @@
 import os
 import sys
 
+if sys.version_info < (3,):
+    u = unicode
+else:
+    u = str
+
+try:
+    from distutils.command.build_py import build_py_2to3 as build_py
+    # Drop fixers in 3.0 that do more harm than good
+    from lib2to3.fixes import fix_imports
+    del fix_imports.MAPPING['commands']
+    fix_imports.FixImports.PATTERN="|".join(fix_imports.build_pattern())
+except ImportError:
+    from distutils.command.build_py import build_py
+
 class osx_install_data(install_data):
     # On MacOS, the platform-specific lib dir is /System/Library/Framework/Python/.../
     # which is wrong. Python 2.5 supplied with MacOS 10.5 has an Apple-specific fix
@@ -22,6 +36,8 @@
 else: 
     cmdclasses = {'install_data': install_data} 
 
+cmdclasses['build_py'] = build_py
+
 def fullsplit(path, result=None):
     """
     Split a pathname into components (the opposite of os.path.join) in a
@@ -67,7 +83,7 @@
 
 # Dynamically calculate the version based on django.VERSION.
 version = __import__('django').get_version()
-if u'SVN' in version:
+if u('SVN') in version:
     version = ' '.join(version.split(' ')[:-1])
 
 setup(

