diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2012-09-05 09:39:03 -0400 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2012-09-05 09:39:03 -0400 |
| commit | b546e7eb633022ee1962570387f22fb2bcea46ed (patch) | |
| tree | f87f4a2d68fb66afae39148fa35489930710b623 /django/core | |
| parent | cd583d6dbd222ae61331a6965b0e1fc86c974c50 (diff) | |
| parent | cff911f4ba3b3e6393c58da5131ce8b188a68f0c (diff) | |
Merge branch 'master' into schema-alteration
Diffstat (limited to 'django/core')
30 files changed, 271 insertions, 197 deletions
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index d527e44d8b..06e8952bfb 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -1,9 +1,9 @@ "Base Cache class." +from __future__ import unicode_literals import warnings from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning -from django.utils.encoding import smart_bytes from django.utils.importlib import import_module class InvalidCacheBackendError(ImproperlyConfigured): @@ -23,7 +23,7 @@ def default_key_func(key, key_prefix, version): the `key_prefix'. KEY_FUNCTION can be used to specify an alternate function with custom key making behavior. """ - return ':'.join([key_prefix, str(version), smart_bytes(key)]) + return ':'.join([key_prefix, str(version), key]) def get_key_func(key_func): """ @@ -62,7 +62,7 @@ class BaseCache(object): except (ValueError, TypeError): self._cull_frequency = 3 - self.key_prefix = smart_bytes(params.get('KEY_PREFIX', '')) + self.key_prefix = params.get('KEY_PREFIX', '') self.version = params.get('VERSION', 1) self.key_func = get_key_func(params.get('KEY_FUNCTION', None)) diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index f60b4e0cd1..348b03f733 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -12,6 +12,7 @@ from django.conf import settings from django.core.cache.backends.base import BaseCache from django.db import connections, router, transaction, DatabaseError from django.utils import timezone +from django.utils.encoding import force_bytes class Options(object): @@ -72,7 +73,7 @@ class DatabaseCache(BaseDatabaseCache): transaction.commit_unless_managed(using=db) return default value = connections[db].ops.process_clob(row[1]) - return pickle.loads(base64.decodestring(value)) + return pickle.loads(base64.b64decode(force_bytes(value))) def set(self, key, value, timeout=None, version=None): key = self.make_key(key, version=version) @@ -103,7 +104,7 @@ class DatabaseCache(BaseDatabaseCache): if num > self._max_entries: self._cull(db, cursor, now) pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) - encoded = base64.encodestring(pickled).strip() + encoded = base64.b64encode(pickled).strip() cursor.execute("SELECT cache_key, expires FROM %s " "WHERE cache_key = %%s" % table, [key]) try: @@ -166,7 +167,7 @@ class DatabaseCache(BaseDatabaseCache): cursor.execute("SELECT COUNT(*) FROM %s" % table) num = cursor.fetchone()[0] if num > self._max_entries: - cull_num = num / self._cull_frequency + cull_num = num // self._cull_frequency cursor.execute( connections[db].ops.cache_key_culling_sql() % table, [cull_num]) diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index 1170996a76..96194d458f 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -10,6 +10,7 @@ except ImportError: import pickle from django.core.cache.backends.base import BaseCache +from django.utils.encoding import force_bytes class FileBasedCache(BaseCache): def __init__(self, dir, params): @@ -136,7 +137,7 @@ class FileBasedCache(BaseCache): Thus, a cache key of "foo" gets turnned into a file named ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``. """ - path = hashlib.md5(key).hexdigest() + path = hashlib.md5(force_bytes(key)).hexdigest() path = os.path.join(path[:2], path[2:4], path[4:]) return os.path.join(self._dir, path) diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py index e7724f1b91..426a0a15c0 100644 --- a/django/core/cache/backends/memcached.py +++ b/django/core/cache/backends/memcached.py @@ -6,6 +6,7 @@ from threading import local from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError from django.utils import six +from django.utils.encoding import force_str class BaseMemcachedCache(BaseCache): def __init__(self, server, params, library, value_not_found_exception): @@ -50,6 +51,10 @@ class BaseMemcachedCache(BaseCache): timeout += int(time.time()) return int(timeout) + def make_key(self, key, version=None): + # Python 2 memcache requires the key to be a byte string. + return force_str(super(BaseMemcachedCache, self).make_key(key, version)) + def add(self, key, value, timeout=0, version=None): key = self.make_key(key, version=version) return self._cache.add(key, value, self._get_memcache_timeout(timeout)) diff --git a/django/core/context_processors.py b/django/core/context_processors.py index a503270cf4..ca1ac68f55 100644 --- a/django/core/context_processors.py +++ b/django/core/context_processors.py @@ -6,12 +6,15 @@ and returns a dictionary to add to the context. These are referenced from the setting TEMPLATE_CONTEXT_PROCESSORS and used by RequestContext. """ +from __future__ import unicode_literals from django.conf import settings from django.middleware.csrf import get_token -from django.utils.encoding import smart_bytes +from django.utils import six +from django.utils.encoding import smart_text from django.utils.functional import lazy + def csrf(request): """ Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if @@ -23,10 +26,10 @@ def csrf(request): # In order to be able to provide debugging info in the # case of misconfiguration, we use a sentinel value # instead of returning an empty dict. - return b'NOTPROVIDED' + return 'NOTPROVIDED' else: - return smart_bytes(token) - _get_val = lazy(_get_val, str) + return smart_text(token) + _get_val = lazy(_get_val, six.text_type) return {'csrf_token': _get_val() } diff --git a/django/core/files/base.py b/django/core/files/base.py index d0b25250a5..b81e180292 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -1,11 +1,14 @@ from __future__ import unicode_literals import os -from io import BytesIO +from io import BytesIO, StringIO, UnsupportedOperation -from django.utils.encoding import smart_bytes, smart_text +from django.utils.encoding import smart_text from django.core.files.utils import FileProxyMixin +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class File(FileProxyMixin): DEFAULT_CHUNK_SIZE = 64 * 2**10 @@ -18,9 +21,6 @@ class File(FileProxyMixin): self.mode = file.mode def __str__(self): - return smart_bytes(self.name or '') - - def __unicode__(self): return smart_text(self.name or '') def __repr__(self): @@ -65,8 +65,10 @@ class File(FileProxyMixin): if not chunk_size: chunk_size = self.DEFAULT_CHUNK_SIZE - if hasattr(self, 'seek'): + try: self.seek(0) + except (AttributeError, UnsupportedOperation): + pass while True: data = self.read(chunk_size) @@ -124,13 +126,15 @@ class File(FileProxyMixin): def close(self): self.file.close() +@python_2_unicode_compatible class ContentFile(File): """ A File-like object that takes just raw content, rather than an actual file. """ def __init__(self, content, name=None): content = content or b'' - super(ContentFile, self).__init__(BytesIO(content), name=name) + stream_class = StringIO if isinstance(content, six.text_type) else BytesIO + super(ContentFile, self).__init__(stream_class(content), name=name) self.size = len(content) def __str__(self): diff --git a/django/core/files/move.py b/django/core/files/move.py index f9060fd3d8..3af02634fe 100644 --- a/django/core/files/move.py +++ b/django/core/files/move.py @@ -66,7 +66,7 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove try: locks.lock(fd, locks.LOCK_EX) current_chunk = None - while current_chunk != '': + while current_chunk != b'': current_chunk = old_file.read(chunk_size) os.write(fd, current_chunk) finally: diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 7542dcda46..0b300cd31e 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -195,11 +195,18 @@ class FileSystemStorage(Storage): fd = os.open(full_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0)) try: locks.lock(fd, locks.LOCK_EX) + _file = None for chunk in content.chunks(): - os.write(fd, chunk) + if _file is None: + mode = 'wb' if isinstance(chunk, bytes) else 'wt' + _file = os.fdopen(fd, mode) + _file.write(chunk) finally: locks.unlock(fd) - os.close(fd) + if _file is not None: + _file.close() + else: + os.close(fd) except OSError as e: if e.errno == errno.EEXIST: # Ooops, the file exists. We need a new file name. diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py index 3a6c632975..39b99ff78f 100644 --- a/django/core/files/uploadedfile.py +++ b/django/core/files/uploadedfile.py @@ -8,7 +8,7 @@ from io import BytesIO from django.conf import settings from django.core.files.base import File from django.core.files import temp as tempfile -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_str __all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile', 'SimpleUploadedFile') @@ -30,7 +30,7 @@ class UploadedFile(File): self.charset = charset def __repr__(self): - return smart_bytes("<%s: %s (%s)>" % ( + return force_str("<%s: %s (%s)>" % ( self.__class__.__name__, self.name, self.content_type)) def _get_name(self): diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py index 68d540e595..c422945d6f 100644 --- a/django/core/files/uploadhandler.py +++ b/django/core/files/uploadhandler.py @@ -10,6 +10,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile from django.utils import importlib +from django.utils.encoding import python_2_unicode_compatible __all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler', 'TemporaryFileUploadHandler', 'MemoryFileUploadHandler', @@ -21,6 +22,7 @@ class UploadFileException(Exception): """ pass +@python_2_unicode_compatible class StopUpload(UploadFileException): """ This exception is raised when an upload must abort. @@ -33,7 +35,7 @@ class StopUpload(UploadFileException): """ self.connection_reset = connection_reset - def __unicode__(self): + def __str__(self): if self.connection_reset: return 'StopUpload: Halt current upload.' else: diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 5a6825f0a7..791382bac0 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import sys +import types from django import http from django.core import signals @@ -125,10 +126,10 @@ class BaseHandler(object): # Complain if the view returned None (a common error). if response is None: - try: - view_name = callback.func_name # If it's a function - except AttributeError: - view_name = callback.__class__.__name__ + '.__call__' # If it's a class + if isinstance(callback, types.FunctionType): # FBV + view_name = callback.__name__ + else: # CBV + view_name = callback.__class__.__name__ + '.__call__' raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)) # If the response supports deferred rendering, apply template @@ -152,10 +153,8 @@ class BaseHandler(object): callback, param_dict = resolver.resolve404() response = callback(request, **param_dict) except: - try: - response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) - finally: - signals.got_request_exception.send(sender=self.__class__, request=request) + signals.got_request_exception.send(sender=self.__class__, request=request) + response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) except exceptions.PermissionDenied: logger.warning( 'Forbidden (Permission denied): %s', request.path, @@ -167,12 +166,10 @@ class BaseHandler(object): callback, param_dict = resolver.resolve403() response = callback(request, **param_dict) except: - try: - response = self.handle_uncaught_exception(request, - resolver, sys.exc_info()) - finally: - signals.got_request_exception.send( + signals.got_request_exception.send( sender=self.__class__, request=request) + response = self.handle_uncaught_exception(request, + resolver, sys.exc_info()) except SystemExit: # Allow sys.exit() to actually exit. See tickets #1023 and #4701 raise @@ -225,7 +222,7 @@ class BaseHandler(object): # If Http500 handler is not installed, re-raise last exception if resolver.urlconf_module is None: - six.reraise(exc_info[1], None, exc_info[2]) + six.reraise(*exc_info) # Return an HttpResponse that displays a friendly error message. callback, param_dict = resolver.resolve500() return callback(request, **param_dict) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 70b23f8515..e445d07a61 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -9,7 +9,7 @@ from django.core import signals from django.core.handlers import base from django.core.urlresolvers import set_script_prefix from django.utils import datastructures -from django.utils.encoding import force_text, smart_bytes, iri_to_uri +from django.utils.encoding import force_str, force_text, iri_to_uri from django.utils.log import getLogger logger = getLogger('django.request') @@ -246,5 +246,5 @@ class WSGIHandler(base.BaseHandler): response_headers = [(str(k), str(v)) for k, v in response.items()] for c in response.cookies.values(): response_headers.append((str('Set-Cookie'), str(c.output(header='')))) - start_response(smart_bytes(status), response_headers) + start_response(force_str(status), response_headers) return response diff --git a/django/core/mail/message.py b/django/core/mail/message.py index b332ffba04..db9023a0bb 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -99,7 +99,12 @@ def sanitize_address(addr, encoding): if isinstance(addr, six.string_types): addr = parseaddr(force_text(addr)) nm, addr = addr - nm = Header(nm, encoding).encode() + # This try-except clause is needed on Python 3 < 3.2.4 + # http://bugs.python.org/issue14291 + try: + nm = Header(nm, encoding).encode() + except UnicodeEncodeError: + nm = Header(nm, 'utf-8').encode() try: addr.encode('ascii') except UnicodeEncodeError: # IDN diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 268cd63c38..98f75e0310 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -51,14 +51,19 @@ def find_management_module(app_name): # module, we need look for the case where the project name is part # of the app_name but the project directory itself isn't on the path. try: - f, path, descr = imp.find_module(part,path) + f, path, descr = imp.find_module(part, path) except ImportError as e: if os.path.basename(os.getcwd()) != part: raise e + else: + if f: + f.close() while parts: part = parts.pop() f, path, descr = imp.find_module(part, path and [path] or None) + if f: + f.close() return path def load_command_class(app_name, name): diff --git a/django/core/management/base.py b/django/core/management/base.py index 5e630d5207..895753ea12 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -12,7 +12,7 @@ import traceback import django from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style -from django.utils.encoding import smart_str +from django.utils.encoding import force_str from django.utils.six import StringIO @@ -65,7 +65,7 @@ class OutputWrapper(object): msg += ending style_func = [f for f in (style_func, self.style_func, lambda x:x) if f is not None][0] - self._out.write(smart_str(style_func(msg))) + self._out.write(force_str(style_func(msg))) class BaseCommand(object): diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index 7c868e4b60..936d5e89ab 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -1,4 +1,7 @@ +from __future__ import unicode_literals + import keyword +import re from optparse import make_option from django.core.management.base import NoArgsCommand, CommandError @@ -31,6 +34,7 @@ class Command(NoArgsCommand): table_name_filter = options.get('table_name_filter') table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') + strip_prefix = lambda s: s.startswith("u'") and s[1:] or s cursor = connection.cursor() yield "# This is an auto-generated Django model module." @@ -41,6 +45,7 @@ class Command(NoArgsCommand): yield "#" yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'" yield "# into your database." + yield "from __future__ import unicode_literals" yield '' yield 'from %s import models' % self.db_module yield '' @@ -59,16 +64,19 @@ class Command(NoArgsCommand): indexes = connection.introspection.get_indexes(cursor, table_name) except NotImplementedError: indexes = {} + used_column_names = [] # Holds column names used in the table so far for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)): - column_name = row[0] - att_name = column_name.lower() comment_notes = [] # Holds Field notes, to be displayed in a Python comment. extra_params = {} # Holds Field parameters such as 'db_column'. + column_name = row[0] + is_relation = i in relations + + att_name, params, notes = self.normalize_col_name( + column_name, used_column_names, is_relation) + extra_params.update(params) + comment_notes.extend(notes) - # If the column name can't be used verbatim as a Python - # attribute, set the "db_column" for this Field. - if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name: - extra_params['db_column'] = column_name + used_column_names.append(att_name) # Add primary_key and unique, if necessary. if column_name in indexes: @@ -77,30 +85,12 @@ class Command(NoArgsCommand): elif indexes[column_name]['unique']: extra_params['unique'] = True - # Modify the field name to make it Python-compatible. - if ' ' in att_name: - att_name = att_name.replace(' ', '_') - comment_notes.append('Field renamed to remove spaces.') - - if '-' in att_name: - att_name = att_name.replace('-', '_') - comment_notes.append('Field renamed to remove dashes.') - - if column_name != att_name: - comment_notes.append('Field name made lowercase.') - - if i in relations: + if is_relation: rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1]) - if rel_to in known_models: field_type = 'ForeignKey(%s' % rel_to else: field_type = "ForeignKey('%s'" % rel_to - - if att_name.endswith('_id'): - att_name = att_name[:-3] - else: - extra_params['db_column'] = column_name else: # Calling `get_field_type` to get the field type string and any # additional paramters and notes. @@ -110,16 +100,6 @@ class Command(NoArgsCommand): field_type += '(' - if keyword.iskeyword(att_name): - att_name += '_field' - comment_notes.append('Field renamed because it was a Python reserved word.') - - if att_name[0].isdigit(): - att_name = 'number_%s' % att_name - extra_params['db_column'] = six.text_type(column_name) - comment_notes.append("Field renamed because it wasn't a " - "valid Python identifier.") - # Don't output 'id = meta.AutoField(primary_key=True)', because # that's assumed if it doesn't exist. if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}: @@ -136,7 +116,9 @@ class Command(NoArgsCommand): if extra_params: if not field_desc.endswith('('): field_desc += ', ' - field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()]) + field_desc += ', '.join([ + '%s=%s' % (k, strip_prefix(repr(v))) + for k, v in extra_params.items()]) field_desc += ')' if comment_notes: field_desc += ' # ' + ' '.join(comment_notes) @@ -144,6 +126,62 @@ class Command(NoArgsCommand): for meta_line in self.get_meta(table_name): yield meta_line + def normalize_col_name(self, col_name, used_column_names, is_relation): + """ + Modify the column name to make it Python-compatible as a field name + """ + field_params = {} + field_notes = [] + + new_name = col_name.lower() + if new_name != col_name: + field_notes.append('Field name made lowercase.') + + if is_relation: + if new_name.endswith('_id'): + new_name = new_name[:-3] + else: + field_params['db_column'] = col_name + + new_name, num_repl = re.subn(r'\W', '_', new_name) + if num_repl > 0: + field_notes.append('Field renamed to remove unsuitable characters.') + + if new_name.find('__') >= 0: + while new_name.find('__') >= 0: + new_name = new_name.replace('__', '_') + if col_name.lower().find('__') >= 0: + # Only add the comment if the double underscore was in the original name + field_notes.append("Field renamed because it contained more than one '_' in a row.") + + if new_name.startswith('_'): + new_name = 'field%s' % new_name + field_notes.append("Field renamed because it started with '_'.") + + if new_name.endswith('_'): + new_name = '%sfield' % new_name + field_notes.append("Field renamed because it ended with '_'.") + + if keyword.iskeyword(new_name): + new_name += '_field' + field_notes.append('Field renamed because it was a Python reserved word.') + + if new_name[0].isdigit(): + new_name = 'number_%s' % new_name + field_notes.append("Field renamed because it wasn't a valid Python identifier.") + + if new_name in used_column_names: + num = 0 + while '%s_%d' % (new_name, num) in used_column_names: + num += 1 + new_name = '%s_%d' % (new_name, num) + field_notes.append('Field renamed because of name conflict.') + + if col_name != new_name and field_notes: + field_params['db_column'] = col_name + + return new_name, field_params, field_notes + def get_field_type(self, connection, table_name, row): """ Given the database connection, the table name, and the cursor row @@ -181,6 +219,6 @@ class Command(NoArgsCommand): to construct the inner Meta class for the model corresponding to the given database table name. """ - return [' class Meta:', - ' db_table = %r' % table_name, - ''] + return [" class Meta:", + " db_table = '%s'" % table_name, + ""] diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 1896e53cee..30cf740cdf 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -196,6 +196,10 @@ class Command(BaseCommand): loaded_object_count += loaded_objects_in_fixture fixture_object_count += objects_in_fixture label_found = True + except Exception as e: + if not isinstance(e, CommandError): + e.args = ("Problem installing fixture '%s': %s" % (full_path, e),) + raise finally: fixture.close() @@ -209,7 +213,11 @@ class Command(BaseCommand): # Since we disabled constraint checks, we must manually check for # any invalid keys that might have been added table_names = [model._meta.db_table for model in models] - connection.check_constraints(table_names=table_names) + try: + connection.check_constraints(table_names=table_names) + except Exception as e: + e.args = ("Problem installing fixtures: %s" % e,) + raise except (SystemExit, KeyboardInterrupt): raise @@ -217,8 +225,6 @@ class Command(BaseCommand): if commit: transaction.rollback(using=using) transaction.leave_transaction_management(using=using) - if not isinstance(e, CommandError): - e.args = ("Problem installing fixture '%s': %s" % (full_path, e),) raise # If we found even one object in a fixture, we need to reset the diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index d8e8f6cff7..81c4fdf8cc 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -47,32 +47,27 @@ def _popen(cmd): output, errors = p.communicate() return output, errors, p.returncode -def walk(root, topdown=True, onerror=None, followlinks=False, - ignore_patterns=None, verbosity=0, stdout=sys.stdout): +def find_files(root, ignore_patterns, verbosity, stdout=sys.stdout, symlinks=False): """ - A version of os.walk that can follow symlinks for Python < 2.6 + Helper function to get all files in the given root. """ - if ignore_patterns is None: - ignore_patterns = [] dir_suffix = '%s*' % os.sep - norm_patterns = map(lambda p: p.endswith(dir_suffix) - and p[:-len(dir_suffix)] or p, ignore_patterns) - for dirpath, dirnames, filenames in os.walk(root, topdown, onerror): - remove_dirs = [] - for dirname in dirnames: + norm_patterns = [p[:-len(dir_suffix)] if p.endswith(dir_suffix) else p for p in ignore_patterns] + all_files = [] + for dirpath, dirnames, filenames in os.walk(root, topdown=True, followlinks=symlinks): + for dirname in dirnames[:]: if is_ignored(os.path.normpath(os.path.join(dirpath, dirname)), norm_patterns): - remove_dirs.append(dirname) - for dirname in remove_dirs: - dirnames.remove(dirname) - if verbosity > 1: - stdout.write('ignoring directory %s\n' % dirname) - yield (dirpath, dirnames, filenames) - if followlinks: - for d in dirnames: - p = os.path.join(dirpath, d) - if os.path.islink(p): - for link_dirpath, link_dirnames, link_filenames in walk(p): - yield (link_dirpath, link_dirnames, link_filenames) + dirnames.remove(dirname) + if verbosity > 1: + stdout.write('ignoring directory %s\n' % dirname) + for filename in filenames: + if is_ignored(os.path.normpath(os.path.join(dirpath, filename)), ignore_patterns): + if verbosity > 1: + stdout.write('ignoring file %s in %s\n' % (filename, dirpath)) + else: + all_files.extend([(dirpath, filename)]) + all_files.sort() + return all_files def is_ignored(path, ignore_patterns): """ @@ -83,23 +78,6 @@ def is_ignored(path, ignore_patterns): return True return False -def find_files(root, ignore_patterns, verbosity, stdout=sys.stdout, symlinks=False): - """ - Helper function to get all files in the given root. - """ - all_files = [] - for (dirpath, dirnames, filenames) in walk(root, followlinks=symlinks, - ignore_patterns=ignore_patterns, verbosity=verbosity, stdout=stdout): - for filename in filenames: - norm_filepath = os.path.normpath(os.path.join(dirpath, filename)) - if is_ignored(norm_filepath, ignore_patterns): - if verbosity > 1: - stdout.write('ignoring file %s in %s\n' % (filename, dirpath)) - else: - all_files.extend([(dirpath, filename)]) - all_files.sort() - return all_files - def copy_plural_forms(msgs, locale, domain, verbosity, stdout=sys.stdout): """ Copies plural forms header contents from a Django catalog of locale to @@ -144,7 +122,7 @@ def write_pot_file(potfile, msgs, file, work_file, is_templatized): msgs = '\n'.join(dropwhile(len, msgs.split('\n'))) else: msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8') - with open(potfile, 'ab') as fp: + with open(potfile, 'a') as fp: fp.write(msgs) def process_file(file, dirpath, potfile, domain, verbosity, @@ -252,7 +230,7 @@ def write_po_file(pofile, potfile, domain, locale, verbosity, stdout, msgs = copy_plural_forms(msgs, locale, domain, verbosity, stdout) msgs = msgs.replace( "#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % domain, "") - with open(pofile, 'wb') as fp: + with open(pofile, 'w') as fp: fp.write(msgs) os.unlink(potfile) if no_obsolete: diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index 4e7d1dbbf4..52a8cab7e4 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -80,14 +80,14 @@ class Command(NoArgsCommand): readline.parse_and_bind("tab:complete") # We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system - # conventions and get $PYTHONSTARTUP first then import user. + # conventions and get $PYTHONSTARTUP first then .pythonrc.py. if not use_plain: - pythonrc = os.environ.get("PYTHONSTARTUP") - if pythonrc and os.path.isfile(pythonrc): - try: - execfile(pythonrc) - except NameError: - pass - # This will import .pythonrc.py as a side-effect - import user + for pythonrc in (os.environ.get("PYTHONSTARTUP"), + os.path.expanduser('~/.pythonrc.py')): + if pythonrc and os.path.isfile(pythonrc): + try: + with open(pythonrc) as handle: + exec(compile(handle.read(), pythonrc, 'exec')) + except NameError: + pass code.interact(local=imported_objects) diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index cf26e754ae..cceec07be8 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -75,7 +75,7 @@ class Command(NoArgsCommand): (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables)) manifest = SortedDict( - (app_name, filter(model_installed, model_list)) + (app_name, list(filter(model_installed, model_list))) for app_name, model_list in all_models ) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 7579cbe8ab..b02a548314 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import codecs import os import re @@ -168,10 +169,10 @@ def custom_sql_for_model(model, style, connection): os.path.join(app_dir, "%s.sql" % opts.object_name.lower())] for sql_file in sql_files: if os.path.exists(sql_file): - with open(sql_file, 'U') as fp: + with codecs.open(sql_file, 'U', encoding=settings.FILE_CHARSET) as fp: # Some backends can't execute more than one SQL statement at a time, # so split into separate statements. - output.extend(_split_statements(fp.read().decode(settings.FILE_CHARSET))) + output.extend(_split_statements(fp.read())) return output diff --git a/django/core/management/templates.py b/django/core/management/templates.py index 52d0e5c89d..aa65593e9c 100644 --- a/django/core/management/templates.py +++ b/django/core/management/templates.py @@ -8,6 +8,8 @@ import shutil import stat import sys import tempfile +import codecs + try: from urllib.request import urlretrieve except ImportError: # Python 2 @@ -154,12 +156,12 @@ class TemplateCommand(BaseCommand): # Only render the Python files, as we don't want to # accidentally render Django templates files - with open(old_path, 'r') as template_file: + with codecs.open(old_path, 'r', 'utf-8') as template_file: content = template_file.read() if filename.endswith(extensions) or filename in extra_files: template = Template(content) content = template.render(context) - with open(new_path, 'w') as new_file: + with codecs.open(new_path, 'w', 'utf-8') as new_file: new_file.write(content) if self.verbosity >= 2: diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 274f98ee79..6cd66f3a6a 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -1,7 +1,7 @@ import sys from django.core.management.color import color_style -from django.utils.encoding import smart_str +from django.utils.encoding import force_str from django.utils.itercompat import is_iterable from django.utils import six @@ -13,7 +13,7 @@ class ModelErrorCollection: def add(self, context, error): self.errors.append((context, error)) - self.outfile.write(self.style.ERROR(smart_str("%s: %s\n" % (context, error)))) + self.outfile.write(self.style.ERROR(force_str("%s: %s\n" % (context, error)))) def get_validation_errors(outfile, app=None): """ diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index bdb43db9f3..276f9a4738 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -2,8 +2,6 @@ Module for abstract serializer/unserializer base classes. """ -from io import BytesIO - from django.db import models from django.utils.encoding import smart_text from django.utils import six @@ -35,7 +33,7 @@ class Serializer(object): """ self.options = options - self.stream = options.pop("stream", BytesIO()) + self.stream = options.pop("stream", six.StringIO()) self.selected_fields = options.pop("fields", None) self.use_natural_keys = options.pop("use_natural_keys", False) @@ -125,7 +123,7 @@ class Deserializer(object): """ self.options = options if isinstance(stream_or_string, six.string_types): - self.stream = BytesIO(stream_or_string) + self.stream = six.StringIO(stream_or_string) else: self.stream = stream_or_string # hack to make sure that the models have all been loaded before diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index 3bac24d33a..ed65f2922c 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -12,7 +12,6 @@ import json from django.core.serializers.base import DeserializationError from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Deserializer as PythonDeserializer -from django.utils.encoding import smart_bytes from django.utils import six from django.utils.timezone import is_aware @@ -61,13 +60,12 @@ def Deserializer(stream_or_string, **options): """ Deserialize a stream or string of JSON data. """ + if not isinstance(stream_or_string, (bytes, six.string_types)): + stream_or_string = stream_or_string.read() if isinstance(stream_or_string, bytes): stream_or_string = stream_or_string.decode('utf-8') try: - if isinstance(stream_or_string, six.string_types): - objects = json.loads(stream_or_string) - else: - objects = json.load(stream_or_string) + objects = json.loads(stream_or_string) for obj in PythonDeserializer(objects, **options): yield obj except GeneratorExit: diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py index 9be1ea4492..4c11626bad 100644 --- a/django/core/serializers/pyyaml.py +++ b/django/core/serializers/pyyaml.py @@ -12,7 +12,6 @@ from django.db import models from django.core.serializers.base import DeserializationError from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Deserializer as PythonDeserializer -from django.utils.encoding import smart_bytes from django.utils import six diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 2db4e5ef6a..19b287af87 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -7,6 +7,8 @@ This is a simple server for use in testing or debugging Django apps. It hasn't been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE! """ +from __future__ import unicode_literals + import os import socket import sys @@ -71,12 +73,12 @@ class WSGIServerException(Exception): class ServerHandler(simple_server.ServerHandler, object): - error_status = "500 INTERNAL SERVER ERROR" + error_status = str("500 INTERNAL SERVER ERROR") def write(self, data): - """'write()' callable as specified by PEP 333""" + """'write()' callable as specified by PEP 3333""" - assert isinstance(data, str), "write() argument must be string" + assert isinstance(data, bytes), "write() argument must be bytestring" if not self.status: raise AssertionError("write() before start_response()") @@ -200,7 +202,7 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object): def run(addr, port, wsgi_handler, ipv6=False, threading=False): server_address = (addr, port) if threading: - httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, WSGIServer), {}) + httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {}) else: httpd_cls = WSGIServer httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) diff --git a/django/core/signing.py b/django/core/signing.py index 9ab8c5b8b0..147e54780c 100644 --- a/django/core/signing.py +++ b/django/core/signing.py @@ -32,6 +32,9 @@ start of the base64 JSON. There are 65 url-safe characters: the 64 used by url-safe base64 and the ':'. These functions make use of all of them. """ + +from __future__ import unicode_literals + import base64 import json import time @@ -41,7 +44,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils import baseconv from django.utils.crypto import constant_time_compare, salted_hmac -from django.utils.encoding import force_text, smart_bytes +from django.utils.encoding import force_bytes, force_str, force_text from django.utils.importlib import import_module @@ -60,11 +63,11 @@ class SignatureExpired(BadSignature): def b64_encode(s): - return base64.urlsafe_b64encode(s).strip('=') + return base64.urlsafe_b64encode(s).strip(b'=') def b64_decode(s): - pad = '=' * (-len(s) % 4) + pad = b'=' * (-len(s) % 4) return base64.urlsafe_b64decode(s + pad) @@ -114,7 +117,7 @@ def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer, value or re-using a salt value across different parts of your application without good cause is a security risk. """ - data = serializer().dumps(obj) + data = force_bytes(serializer().dumps(obj)) # Flag for if it's been compressed or not is_compressed = False @@ -127,7 +130,7 @@ def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer, is_compressed = True base64d = b64_encode(data) if is_compressed: - base64d = '.' + base64d + base64d = b'.' + base64d return TimestampSigner(key, salt=salt).sign(base64d) @@ -135,35 +138,40 @@ def loads(s, key=None, salt='django.core.signing', serializer=JSONSerializer, ma """ Reverse of dumps(), raises BadSignature if signature fails """ - base64d = smart_bytes( - TimestampSigner(key, salt=salt).unsign(s, max_age=max_age)) + # TimestampSigner.unsign always returns unicode but base64 and zlib + # compression operate on bytes. + base64d = force_bytes(TimestampSigner(key, salt=salt).unsign(s, max_age=max_age)) decompress = False - if base64d[0] == '.': + if base64d[0] == b'.': # It's compressed; uncompress it first base64d = base64d[1:] decompress = True data = b64_decode(base64d) if decompress: data = zlib.decompress(data) - return serializer().loads(data) + return serializer().loads(force_str(data)) class Signer(object): + def __init__(self, key=None, sep=':', salt=None): - self.sep = sep - self.key = key or settings.SECRET_KEY - self.salt = salt or ('%s.%s' % - (self.__class__.__module__, self.__class__.__name__)) + # Use of native strings in all versions of Python + self.sep = str(sep) + self.key = str(key or settings.SECRET_KEY) + self.salt = str(salt or + '%s.%s' % (self.__class__.__module__, self.__class__.__name__)) def signature(self, value): - return base64_hmac(self.salt + 'signer', value, self.key) + signature = base64_hmac(self.salt + 'signer', value, self.key) + # Convert the signature from bytes to str only on Python 3 + return force_str(signature) def sign(self, value): - value = smart_bytes(value) - return '%s%s%s' % (value, self.sep, self.signature(value)) + value = force_str(value) + return str('%s%s%s') % (value, self.sep, self.signature(value)) def unsign(self, signed_value): - signed_value = smart_bytes(signed_value) + signed_value = force_str(signed_value) if not self.sep in signed_value: raise BadSignature('No "%s" found in value' % self.sep) value, sig = signed_value.rsplit(self.sep, 1) @@ -178,8 +186,9 @@ class TimestampSigner(Signer): return baseconv.base62.encode(int(time.time())) def sign(self, value): - value = smart_bytes('%s%s%s' % (value, self.sep, self.timestamp())) - return '%s%s%s' % (value, self.sep, self.signature(value)) + value = force_str(value) + value = str('%s%s%s') % (value, self.sep, self.timestamp()) + return super(TimestampSigner, self).sign(value) def unsign(self, value, max_age=None): result = super(TimestampSigner, self).unsign(value) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 2fe744e8eb..af3df83d0a 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -14,7 +14,7 @@ from threading import local from django.http import Http404 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.utils.datastructures import MultiValueDict -from django.utils.encoding import iri_to_uri, force_text, smart_bytes +from django.utils.encoding import force_str, force_text, iri_to_uri from django.utils.functional import memoize, lazy from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule @@ -89,18 +89,11 @@ def get_callable(lookup_view, can_fail=False): """ if not callable(lookup_view): mod_name, func_name = get_mod_func(lookup_view) + if func_name == '': + return lookup_view + try: - if func_name != '': - lookup_view = getattr(import_module(mod_name), func_name) - if not callable(lookup_view): - raise ViewDoesNotExist( - "Could not import %s.%s. View is not callable." % - (mod_name, func_name)) - except AttributeError: - if not can_fail: - raise ViewDoesNotExist( - "Could not import %s. View does not exist in module %s." % - (lookup_view, mod_name)) + mod = import_module(mod_name) except ImportError: parentmod, submod = get_mod_func(mod_name) if (not can_fail and submod != '' and @@ -110,6 +103,18 @@ def get_callable(lookup_view, can_fail=False): (lookup_view, mod_name)) if not can_fail: raise + else: + try: + lookup_view = getattr(mod, func_name) + if not callable(lookup_view): + raise ViewDoesNotExist( + "Could not import %s.%s. View is not callable." % + (mod_name, func_name)) + except AttributeError: + if not can_fail: + raise ViewDoesNotExist( + "Could not import %s. View does not exist in module %s." % + (lookup_view, mod_name)) return lookup_view get_callable = memoize(get_callable, _callable_cache, 1) @@ -190,7 +195,7 @@ class RegexURLPattern(LocaleRegexProvider): self.name = name def __repr__(self): - return smart_bytes('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) + return force_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) def add_prefix(self, prefix): """ @@ -240,7 +245,14 @@ class RegexURLResolver(LocaleRegexProvider): self._app_dict = {} def __repr__(self): - return smart_bytes('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) + if isinstance(self.urlconf_name, list) and len(self.urlconf_name): + # Don't bother to output the whole list, it can be huge + urlconf_repr = '<%s list>' % self.urlconf_name[0].__class__.__name__ + else: + urlconf_repr = repr(self.urlconf_name) + return force_str('<%s %s (%s:%s) %s>' % ( + self.__class__.__name__, urlconf_repr, self.app_name, + self.namespace, self.regex.pattern)) def _populate(self): lookups = MultiValueDict() diff --git a/django/core/validators.py b/django/core/validators.py index 456fa3cec5..317e3880bf 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -8,7 +8,7 @@ except ImportError: # Python 2 from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_text +from django.utils.encoding import force_text from django.utils.ipv6 import is_valid_ipv6_address from django.utils import six @@ -36,7 +36,7 @@ class RegexValidator(object): """ Validates that the input matches the regular expression. """ - if not self.regex.search(smart_text(value)): + if not self.regex.search(force_text(value)): raise ValidationError(self.message, code=self.code) class URLValidator(RegexValidator): @@ -44,7 +44,8 @@ class URLValidator(RegexValidator): r'^(?:http|ftp)s?://' # http:// or https:// r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain... r'localhost|' #localhost... - r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 + r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)$', re.IGNORECASE) @@ -54,10 +55,10 @@ class URLValidator(RegexValidator): except ValidationError as e: # Trivial case failed. Try for possible IDN domain if value: - value = smart_text(value) + value = force_text(value) scheme, netloc, path, query, fragment = urlsplit(value) try: - netloc = netloc.encode('idna') # IDN -> ACE + netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE except UnicodeError: # invalid domain part raise e url = urlunsplit((scheme, netloc, path, query, fragment)) @@ -84,7 +85,7 @@ class EmailValidator(RegexValidator): if value and '@' in value: parts = value.split('@') try: - parts[-1] = parts[-1].encode('idna') + parts[-1] = parts[-1].encode('idna').decode('ascii') except UnicodeError: raise e super(EmailValidator, self).__call__('@'.join(parts)) @@ -99,7 +100,7 @@ email_re = re.compile( r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3) validate_email = EmailValidator(email_re, _('Enter a valid e-mail address.'), 'invalid') -slug_re = re.compile(r'^[-\w]+$') +slug_re = re.compile(r'^[-a-zA-Z0-9_]+$') validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') |
