diff --git a/MANIFEST.in b/MANIFEST.in index 2dde740b06..185e57646a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include README +include README.rst include AUTHORS include INSTALL include LICENSE diff --git a/django/conf/__init__.py b/django/conf/__init__.py index e1c3fd1808..f4d17ca9f3 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -152,17 +152,25 @@ class UserSettingsHolder(BaseSettings): Requests for configuration variables not in this class are satisfied from the module specified in default_settings (if possible). """ + self.__dict__['_deleted'] = set() self.default_settings = default_settings def __getattr__(self, name): + if name in self._deleted: + raise AttributeError return getattr(self.default_settings, name) + def __setattr__(self, name, value): + self._deleted.discard(name) + return super(UserSettingsHolder, self).__setattr__(name, value) + + def __delattr__(self, name): + self._deleted.add(name) + return super(UserSettingsHolder, self).__delattr__(name) + def __dir__(self): return list(self.__dict__) + dir(self.default_settings) - # For Python < 2.6: - __members__ = property(lambda self: self.__dir__()) - settings = LazySettings() diff --git a/django/conf/project_template/project_name/settings.py b/django/conf/project_template/project_name/settings.py index 99590d6fd5..6bdaa34988 100644 --- a/django/conf/project_template/project_name/settings.py +++ b/django/conf/project_template/project_name/settings.py @@ -13,10 +13,11 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 'NAME': '', # Or path to database file if using sqlite3. - 'USER': '', # Not used with sqlite3. - 'PASSWORD': '', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. + # The following settings are not used with sqlite3: + 'USER': '', + 'PASSWORD': '', + 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. + 'PORT': '', # Set to empty string for default. } } diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index 82d7296c85..e27875cdad 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -29,7 +29,7 @@ {% if change %}{% if not is_popup %} diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index ff90e1d007..889f692ac3 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -12,7 +12,7 @@ from django.utils import formats from django.utils.html import format_html from django.utils.text import capfirst from django.utils import timezone -from django.utils.encoding import force_text, smart_text, smart_bytes +from django.utils.encoding import force_str, force_text, smart_text from django.utils import six from django.utils.translation import ungettext from django.core.urlresolvers import reverse @@ -277,7 +277,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False): label = force_text(model._meta.verbose_name) attr = six.text_type elif name == "__str__": - label = smart_bytes(model._meta.verbose_name) + label = force_str(model._meta.verbose_name) attr = bytes else: if callable(name): diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 30074f1648..74ef095b4b 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -6,7 +6,7 @@ from django.core.paginator import InvalidPage from django.db import models from django.db.models.fields import FieldDoesNotExist from django.utils.datastructures import SortedDict -from django.utils.encoding import force_text, smart_bytes +from django.utils.encoding import force_str, force_text from django.utils.translation import ugettext, ugettext_lazy from django.utils.http import urlencode @@ -94,7 +94,7 @@ class ChangeList(object): # 'key' will be used as a keyword argument later, so Python # requires it to be a string. del lookup_params[key] - lookup_params[smart_bytes(key)] = value + lookup_params[force_str(key)] = value if not self.model_admin.lookup_allowed(key, value): raise SuspiciousOperation("Filtering by %s not allowed" % key) diff --git a/django/contrib/admindocs/utils.py b/django/contrib/admindocs/utils.py index 0e10eb4fa3..9be0093dfc 100644 --- a/django/contrib/admindocs/utils.py +++ b/django/contrib/admindocs/utils.py @@ -6,7 +6,7 @@ from email.errors import HeaderParseError from django.utils.safestring import mark_safe from django.core.urlresolvers import reverse -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_bytes try: import docutils.core import docutils.nodes @@ -66,7 +66,7 @@ def parse_rst(text, default_reference_context, thing_being_parsed=None): "link_base" : reverse('django-admindocs-docroot').rstrip('/') } if thing_being_parsed: - thing_being_parsed = smart_bytes("<%s>" % thing_being_parsed) + thing_being_parsed = force_bytes("<%s>" % thing_being_parsed) parts = docutils.core.publish_parts(text, source_path=thing_being_parsed, destination_path=None, writer_name='html', settings_overrides=overrides) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index cdaf75636f..bd0c6778c9 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -8,7 +8,7 @@ from django.conf import settings from django.test.signals import setting_changed from django.utils import importlib from django.utils.datastructures import SortedDict -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_bytes from django.core.exceptions import ImproperlyConfigured from django.utils.crypto import ( pbkdf2, constant_time_compare, get_random_string) @@ -299,7 +299,7 @@ class SHA1PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.sha1(smart_bytes(salt + password)).hexdigest() + hash = hashlib.sha1(force_bytes(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -327,7 +327,7 @@ class MD5PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.md5(smart_bytes(salt + password)).hexdigest() + hash = hashlib.md5(force_bytes(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -361,7 +361,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): - return hashlib.md5(smart_bytes(password)).hexdigest() + return hashlib.md5(force_bytes(password)).hexdigest() def verify(self, password, encoded): encoded_2 = self.encode(password, '') diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 277c43fd1e..2ada789cae 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -10,6 +10,7 @@ import unicodedata from django.contrib.auth import models as auth_app, get_user_model from django.core import exceptions from django.db.models import get_models, signals +from django.utils import six from django.utils.six.moves import input @@ -87,17 +88,23 @@ def get_system_username(): :returns: The username as a unicode string, or an empty string if the username could not be determined. """ - default_locale = locale.getdefaultlocale()[1] - if default_locale: + try: + result = getpass.getuser() + except (ImportError, KeyError): + # KeyError will be raised by os.getpwuid() (called by getuser()) + # if there is no corresponding entry in the /etc/passwd file + # (a very restricted chroot environment, for example). + return '' + if not six.PY3: + default_locale = locale.getdefaultlocale()[1] + if not default_locale: + return '' try: - return getpass.getuser().decode(default_locale) - except (ImportError, KeyError, UnicodeDecodeError): - # KeyError will be raised by os.getpwuid() (called by getuser()) - # if there is no corresponding entry in the /etc/passwd file - # (a very restricted chroot environment, for example). + result = result.decode(default_locale) + except UnicodeDecodeError: # UnicodeDecodeError - preventive treatment for non-latin Windows. - pass - return '' + return '' + return result def get_default_username(check_db=True): diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py index 3dcde9d218..b93f695a20 100644 --- a/django/contrib/auth/tests/management.py +++ b/django/contrib/auth/tests/management.py @@ -9,16 +9,20 @@ from django.core.management import call_command from django.core.management.base import CommandError from django.test import TestCase from django.test.utils import override_settings +from django.utils import six from django.utils.six import StringIO class GetDefaultUsernameTestCase(TestCase): def setUp(self): - self._getpass_getuser = management.get_system_username + self.old_get_system_username = management.get_system_username def tearDown(self): - management.get_system_username = self._getpass_getuser + management.get_system_username = self.old_get_system_username + + def test_actual_implementation(self): + self.assertIsInstance(management.get_system_username(), six.text_type) def test_simple(self): management.get_system_username = lambda: 'joe' diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 9d6ac34f3f..5f5f46f0d1 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from django.db import models from django.utils import formats from django.utils.text import capfirst -from django.utils.encoding import smart_text, smart_str, iri_to_uri +from django.utils.encoding import smart_text, force_str, iri_to_uri from django.db.models.query import QuerySet from django.utils.encoding import python_2_unicode_compatible @@ -23,7 +23,7 @@ class EasyModel(object): self.verbose_name_plural = model._meta.verbose_name_plural def __repr__(self): - return smart_str('' % self.model._meta.object_name) + return force_str('' % self.model._meta.object_name) def model_databrowse(self): "Returns the ModelDatabrowse class for this model." @@ -62,7 +62,7 @@ class EasyField(object): self.model, self.field = easy_model, field def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return force_str('' % (self.model.model._meta.object_name, self.field.name)) def choices(self): for value, label in self.field.choices: @@ -80,7 +80,7 @@ class EasyChoice(object): self.value, self.label = value, label def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return force_str('' % (self.model.model._meta.object_name, self.field.name)) def url(self): return '%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value)) @@ -91,7 +91,7 @@ class EasyInstance(object): self.model, self.instance = easy_model, instance def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.instance._get_pk_val())) + return force_str('' % (self.model.model._meta.object_name, self.instance._get_pk_val())) def __str__(self): val = smart_text(self.instance) @@ -135,7 +135,7 @@ class EasyInstanceField(object): self.raw_value = getattr(instance.instance, field.name) def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return force_str('' % (self.model.model._meta.object_name, self.field.name)) def values(self): """ diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index cc61dfa4d2..d87e151aea 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -1,16 +1,15 @@ from django.db import connections from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet -from django.utils import six from django.contrib.gis.db.models import aggregates from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Area, Distance -from django.utils import six from django.utils import six + class GeoQuerySet(QuerySet): "The Geographic QuerySet." diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index 8bcdba1b44..36e48f7d20 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -8,7 +8,6 @@ from django.core.paginator import EmptyPage, PageNotAnInteger from django.contrib.gis.db.models.fields import GeometryField from django.db import connections, DEFAULT_DB_ALIAS from django.db.models import get_model -from django.utils.encoding import smart_bytes from django.utils import six from django.utils.translation import ugettext as _ @@ -61,7 +60,7 @@ def sitemap(request, sitemaps, section=None): raise Http404(_("Page %s empty") % page) except PageNotAnInteger: raise Http404(_("No page '%s'") % page) - xml = smart_bytes(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls})) + xml = loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls}) return HttpResponse(xml, content_type='application/xml') def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS): diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index b06d6b5e1b..fbe30e8841 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -186,6 +186,15 @@ class GeoModelTest(TestCase): self.assertEqual(1, qs.count()) for pc in qs: self.assertEqual(32128, pc.point.srid) + def test_raw_sql_query(self): + "Testing raw SQL query." + cities1 = City.objects.all() + # Only PostGIS would support a 'select *' query because of its recognized + # HEXEWKB format for geometry fields + cities2 = City.objects.raw('select id, name, asText(point) from geoapp_city') + self.assertEqual(len(cities1), len(list(cities2))) + self.assertTrue(isinstance(cities2[0].point, Point)) + class GeoLookupTest(TestCase): diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index af9c842f42..18b7475ca7 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -13,7 +13,7 @@ markup syntaxes to HTML; currently there is support for: from django import template from django.conf import settings -from django.utils.encoding import smart_bytes, force_text +from django.utils.encoding import force_bytes, force_text from django.utils.safestring import mark_safe register = template.Library() @@ -27,7 +27,7 @@ def textile(value): raise template.TemplateSyntaxError("Error in 'textile' filter: The Python textile library isn't installed.") return force_text(value) else: - return mark_safe(force_text(textile.textile(smart_bytes(value), encoding='utf-8', output='utf-8'))) + return mark_safe(force_text(textile.textile(force_bytes(value), encoding='utf-8', output='utf-8'))) @register.filter(is_safe=True) def markdown(value, arg=''): @@ -80,5 +80,5 @@ def restructuredtext(value): return force_text(value) else: docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {}) - parts = publish_parts(source=smart_bytes(value), writer_name="html4css1", settings_overrides=docutils_settings) + parts = publish_parts(source=force_bytes(value), writer_name="html4css1", settings_overrides=docutils_settings) return mark_safe(force_text(parts["fragment"])) diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 2fb7991b49..c8393f23c6 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -14,7 +14,7 @@ from django.utils.crypto import constant_time_compare from django.utils.crypto import get_random_string from django.utils.crypto import salted_hmac from django.utils import timezone -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_bytes class CreateError(Exception): """ @@ -84,7 +84,7 @@ class SessionBase(object): return base64.b64encode(hash.encode() + b":" + pickled).decode('ascii') def decode(self, session_data): - encoded_data = base64.b64decode(smart_bytes(session_data)) + encoded_data = base64.b64decode(force_bytes(session_data)) try: # could produce ValueError if there is no ':' hash, pickled = encoded_data.split(b':', 1) diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index 7dac0ffb4c..0e16e9d649 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -192,7 +192,7 @@ Type 'yes' to continue, or 'no' to cancel: """ def clear_dir(self, path): """ - Deletes the given relative path using the destinatin storage backend. + Deletes the given relative path using the destination storage backend. """ dirs, files = self.storage.listdir(path) for f in files: diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index 4be7540c6e..9691b7849d 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -16,7 +16,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage, get_storage_class from django.utils.datastructures import SortedDict -from django.utils.encoding import force_text, smart_bytes +from django.utils.encoding import force_bytes, force_text from django.utils.functional import LazyObject from django.utils.importlib import import_module @@ -118,7 +118,7 @@ class CachedFilesMixin(object): return urlunsplit(unparsed_name) def cache_key(self, name): - return 'staticfiles:%s' % hashlib.md5(smart_bytes(name)).hexdigest() + return 'staticfiles:%s' % hashlib.md5(force_bytes(name)).hexdigest() def url(self, name, force=False): """ @@ -254,7 +254,7 @@ class CachedFilesMixin(object): if hashed_file_exists: self.delete(hashed_name) # then save the processed result - content_file = ContentFile(smart_bytes(content)) + content_file = ContentFile(force_bytes(content)) saved_name = self._save(hashed_name, content_file) hashed_name = force_text(saved_name.replace('\\', '/')) processed = True diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 52db4d1b1d..348b03f733 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -12,7 +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 smart_bytes +from django.utils.encoding import force_bytes class Options(object): @@ -73,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.b64decode(smart_bytes(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) diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index c54e8d280f..96194d458f 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -10,7 +10,7 @@ except ImportError: import pickle from django.core.cache.backends.base import BaseCache -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_bytes class FileBasedCache(BaseCache): def __init__(self, dir, params): @@ -137,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(smart_bytes(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 75ce26d20e..426a0a15c0 100644 --- a/django/core/cache/backends/memcached.py +++ b/django/core/cache/backends/memcached.py @@ -6,7 +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 smart_str +from django.utils.encoding import force_str class BaseMemcachedCache(BaseCache): def __init__(self, server, params, library, value_not_found_exception): @@ -53,7 +53,7 @@ class BaseMemcachedCache(BaseCache): def make_key(self, key, version=None): # Python 2 memcache requires the key to be a byte string. - return smart_str(super(BaseMemcachedCache, self).make_key(key, version)) + 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) diff --git a/django/core/files/base.py b/django/core/files/base.py index 4a422be90d..b81e180292 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -1,10 +1,11 @@ from __future__ import unicode_literals import os -from io import BytesIO, UnsupportedOperation +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 @@ -132,7 +133,8 @@ class ContentFile(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/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 97d53482e4..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_str +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_str("<%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/handlers/wsgi.py b/django/core/handlers/wsgi.py index a0186e552f..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_str, 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_str(status), response_headers) + start_response(force_str(status), response_headers) return response 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 - # 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 + att_name, params, notes = self.normalize_col_name( + column_name, used_column_names, is_relation) + extra_params.update(params) + comment_notes.extend(notes) + + 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 7bdd2472d3..81c4fdf8cc 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -47,31 +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 = [p[:-len(dir_suffix)] if p.endswith(dir_suffix) else p for p in ignore_patterns] - for dirpath, dirnames, filenames in os.walk(root, topdown, onerror): - remove_dirs = [] - for dirname in dirnames: + 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): """ @@ -82,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 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/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 c1cd3d8776..462d1ec0b6 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 @@ -14,7 +14,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/json.py b/django/core/serializers/json.py index 4ba0d7fd79..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 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/signing.py b/django/core/signing.py index 6fc76bc201..147e54780c 100644 --- a/django/core/signing.py +++ b/django/core/signing.py @@ -32,6 +32,7 @@ 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 @@ -43,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 smart_bytes +from django.utils.encoding import force_bytes, force_str, force_text from django.utils.importlib import import_module @@ -62,12 +63,12 @@ class SignatureExpired(BadSignature): def b64_encode(s): - return base64.urlsafe_b64encode(smart_bytes(s)).decode('ascii').strip('=') + return base64.urlsafe_b64encode(s).strip(b'=') def b64_decode(s): - pad = '=' * (-len(s) % 4) - return base64.urlsafe_b64decode(smart_bytes(s + pad)).decode('ascii') + pad = b'=' * (-len(s) % 4) + return base64.urlsafe_b64decode(s + pad) def base64_hmac(salt, value, key): @@ -116,20 +117,20 @@ 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 if compress: # Avoid zlib dependency unless compress is being used - compressed = zlib.compress(smart_bytes(data)) + compressed = zlib.compress(data) if len(compressed) < (len(data) - 1): data = compressed is_compressed = True base64d = b64_encode(data) if is_compressed: - base64d = '.' + base64d + base64d = b'.' + base64d return TimestampSigner(key, salt=salt).sign(base64d) @@ -137,37 +138,45 @@ def loads(s, key=None, salt='django.core.signing', serializer=JSONSerializer, ma """ Reverse of dumps(), raises BadSignature if signature fails """ - base64d = 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): - 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 = 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) if constant_time_compare(sig, self.signature(value)): - return value + return force_text(value) raise BadSignature('Signature "%s" does not match' % sig) @@ -177,8 +186,9 @@ class TimestampSigner(Signer): return baseconv.base62.encode(int(time.time())) def sign(self, value): - value = '%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 b83f2f51b0..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_str +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 @@ -195,7 +195,7 @@ class RegexURLPattern(LocaleRegexProvider): self.name = name def __repr__(self): - return smart_str('<%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): """ @@ -245,8 +245,13 @@ class RegexURLResolver(LocaleRegexProvider): self._app_dict = {} def __repr__(self): - return smart_str('<%s %s (%s:%s) %s>' % ( - self.__class__.__name__, self.urlconf_name, self.app_name, + 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): diff --git a/django/core/validators.py b/django/core/validators.py index 08e7e95fce..cf12f8c9fc 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 @@ -37,7 +37,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) @@ -57,7 +57,7 @@ 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').decode('ascii') # IDN -> ACE diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 649f807f26..f0fd2f56d2 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -609,7 +609,7 @@ class BaseDatabaseOperations(object): exists for database backends to provide a better implementation according to their own quoting schemes. """ - from django.utils.encoding import smart_text, force_text + from django.utils.encoding import force_text # Convert params to contain Unicode values. to_unicode = lambda s: force_text(s, strings_only=True, errors='replace') @@ -618,7 +618,7 @@ class BaseDatabaseOperations(object): else: u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()]) - return smart_text(sql) % u_params + return force_text(sql) % u_params def last_insert_id(self, cursor, table_name, pk_name): """ @@ -802,8 +802,8 @@ class BaseDatabaseOperations(object): def prep_for_like_query(self, x): """Prepares a value for use in a LIKE query.""" - from django.utils.encoding import smart_text - return smart_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") + from django.utils.encoding import force_text + return force_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") # Same as prep_for_like_query(), but called for "iexact" matches, which # need not necessarily be implemented using "LIKE" in the backend. diff --git a/django/db/backends/mysql/compiler.py b/django/db/backends/mysql/compiler.py index 012bca5da6..d8e9b3a202 100644 --- a/django/db/backends/mysql/compiler.py +++ b/django/db/backends/mysql/compiler.py @@ -1,10 +1,16 @@ +try: + from itertools import zip_longest +except ImportError: + from itertools import izip_longest as zip_longest + from django.db.models.sql import compiler + class SQLCompiler(compiler.SQLCompiler): def resolve_columns(self, row, fields=()): values = [] index_extra_select = len(self.query.extra_select) - for value, field in map(None, row[index_extra_select:], fields): + for value, field in zip_longest(row[index_extra_select:], fields): if (field and field.get_internal_type() in ("BooleanField", "NullBooleanField") and value in (0, 1)): value = bool(value) diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index 6aab0b99ab..3b5a6fecd3 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -1,8 +1,9 @@ +import re +from .base import FIELD_TYPE + from django.db.backends import BaseDatabaseIntrospection from django.utils import six -from MySQLdb import ProgrammingError, OperationalError -from MySQLdb.constants import FIELD_TYPE -import re + foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)") @@ -35,9 +36,20 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): return [row[0] for row in cursor.fetchall()] def get_table_description(self, cursor, table_name): - "Returns a description of the table, with the DB-API cursor.description interface." + """ + Returns a description of the table, with the DB-API cursor.description interface." + """ + # varchar length returned by cursor.description is an internal length, + # not visible length (#5725), use information_schema database to fix this + cursor.execute(""" + SELECT column_name, character_maximum_length FROM information_schema.columns + WHERE table_name = %s AND table_schema = DATABASE() + AND character_maximum_length IS NOT NULL""", [table_name]) + length_map = dict(cursor.fetchall()) + cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)) - return cursor.description + return [line[:3] + (length_map.get(line[0], line[3]),) + line[4:] + for line in cursor.description] def _name_to_index(self, cursor, table_name): """ diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 7d4f2b445a..6bf2e815a7 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -10,8 +10,6 @@ import decimal import sys import warnings -from django.utils import six - def _setup_environment(environ): import platform # Cygwin requires some special voodoo to set the environment variables @@ -53,7 +51,7 @@ from django.db.backends.signals import connection_created from django.db.backends.oracle.client import DatabaseClient from django.db.backends.oracle.creation import DatabaseCreation from django.db.backends.oracle.introspection import DatabaseIntrospection -from django.utils.encoding import smart_bytes, force_text +from django.utils.encoding import force_bytes, force_text from django.utils import six from django.utils import timezone @@ -66,7 +64,7 @@ IntegrityError = Database.IntegrityError if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'): convert_unicode = force_text else: - convert_unicode = smart_bytes + convert_unicode = force_bytes class DatabaseFeatures(BaseDatabaseFeatures): @@ -604,9 +602,9 @@ class OracleParam(object): elif param is False: param = "0" if hasattr(param, 'bind_parameter'): - self.smart_bytes = param.bind_parameter(cursor) + self.force_bytes = param.bind_parameter(cursor) else: - self.smart_bytes = convert_unicode(param, cursor.charset, + self.force_bytes = convert_unicode(param, cursor.charset, strings_only) if hasattr(param, 'input_size'): # If parameter has `input_size` attribute, use that. @@ -685,7 +683,7 @@ class FormatStylePlaceholderCursor(object): self.setinputsizes(*sizes) def _param_generator(self, params): - return [p.smart_bytes for p in params] + return [p.force_bytes for p in params] def execute(self, query, params=None): if params is None: diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py index 99573b9019..5d30382ddf 100644 --- a/django/db/backends/postgresql_psycopg2/introspection.py +++ b/django/db/backends/postgresql_psycopg2/introspection.py @@ -45,8 +45,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): WHERE table_name = %s""", [table_name]) null_map = dict(cursor.fetchall()) cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)) - return [tuple([item for item in line[:6]] + [null_map[line[0]]=='YES']) - for line in cursor.description] + return [line[:6] + (null_map[line[0]]=='YES',) + for line in cursor.description] def get_relations(self, cursor, table_name): """ diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py index 8135f3548c..1df4c18c1c 100644 --- a/django/db/backends/sqlite3/introspection.py +++ b/django/db/backends/sqlite3/introspection.py @@ -1,6 +1,14 @@ import re from django.db.backends import BaseDatabaseIntrospection +field_size_re = re.compile(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$') + +def get_field_size(name): + """ Extract the size number from a "varchar(11)" type name """ + m = field_size_re.search(name) + return int(m.group(1)) if m else None + + # This light wrapper "fakes" a dictionary interface, because some SQLite data # types include variables in them -- e.g. "varchar(30)" -- and can't be matched # as a simple dictionary lookup. @@ -32,10 +40,9 @@ class FlexibleFieldLookupDict(object): try: return self.base_data_types_reverse[key] except KeyError: - import re - m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key) - if m: - return ('CharField', {'max_length': int(m.group(1))}) + size = get_field_size(key) + if size is not None: + return ('CharField', {'max_length': size}) raise KeyError class DatabaseIntrospection(BaseDatabaseIntrospection): @@ -53,7 +60,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): def get_table_description(self, cursor, table_name): "Returns a description of the table, with the DB-API cursor.description interface." - return [(info['name'], info['type'], None, None, None, None, + return [(info['name'], info['type'], None, info['size'], None, None, info['null_ok']) for info in self._table_info(cursor, table_name)] def get_relations(self, cursor, table_name): @@ -171,6 +178,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): # cid, name, type, notnull, dflt_value, pk return [{'name': field[1], 'type': field[2], + 'size': get_field_size(field[2]), 'null_ok': not field[3], 'pk': field[5] # undocumented } for field in cursor.fetchall()] diff --git a/django/db/backends/util.py b/django/db/backends/util.py index 9d70248ebf..75d4d07a66 100644 --- a/django/db/backends/util.py +++ b/django/db/backends/util.py @@ -6,7 +6,7 @@ import hashlib from time import time from django.conf import settings -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_bytes from django.utils.log import getLogger from django.utils.timezone import utc @@ -138,7 +138,7 @@ def truncate_name(name, length=None, hash_len=4): if length is None or len(name) <= length: return name - hsh = hashlib.md5(smart_bytes(name)).hexdigest()[:hash_len] + hsh = hashlib.md5(force_bytes(name)).hexdigest()[:hash_len] return '%s%s' % (name[:length-hash_len], hsh) def format_number(value, max_digits, decimal_places): diff --git a/django/db/models/base.py b/django/db/models/base.py index e3418d5a1c..9fe5b9cc63 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -23,7 +23,7 @@ from django.db.models import signals from django.db.models.loading import register_models, get_model from django.utils.translation import ugettext_lazy as _ from django.utils.functional import curry -from django.utils.encoding import smart_str, force_text +from django.utils.encoding import force_str, force_text from django.utils import six from django.utils.text import get_text_list, capfirst @@ -407,7 +407,7 @@ class Model(six.with_metaclass(ModelBase, object)): u = six.text_type(self) except (UnicodeEncodeError, UnicodeDecodeError): u = '[Bad Unicode data]' - return smart_str('<%s: %s>' % (self.__class__.__name__, u)) + return force_str('<%s: %s>' % (self.__class__.__name__, u)) def __str__(self): if not six.PY3 and hasattr(self, '__unicode__'): diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index d07851bbf5..58ae3413f3 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1047,13 +1047,14 @@ class GenericIPAddressField(Field): description = _("IP address") default_error_messages = {} - def __init__(self, protocol='both', unpack_ipv4=False, *args, **kwargs): + def __init__(self, verbose_name=None, name=None, protocol='both', + unpack_ipv4=False, *args, **kwargs): self.unpack_ipv4 = unpack_ipv4 self.default_validators, invalid_error_message = \ validators.ip_address_validators(protocol, unpack_ipv4) self.default_error_messages['invalid'] = invalid_error_message kwargs['max_length'] = 39 - Field.__init__(self, *args, **kwargs) + Field.__init__(self, verbose_name, name, *args, **kwargs) def get_internal_type(self): return "GenericIPAddressField" diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index adb9c16fed..f16632ef25 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -8,7 +8,7 @@ from django.core.files.base import File from django.core.files.storage import default_storage from django.core.files.images import ImageFile from django.db.models import signals -from django.utils.encoding import force_text, smart_str +from django.utils.encoding import force_str, force_text from django.utils import six from django.utils.translation import ugettext_lazy as _ @@ -280,7 +280,7 @@ class FileField(Field): setattr(cls, self.name, self.descriptor_class(self)) def get_directory_name(self): - return os.path.normpath(force_text(datetime.datetime.now().strftime(smart_str(self.upload_to)))) + return os.path.normpath(force_text(datetime.datetime.now().strftime(force_str(self.upload_to)))) def get_filename(self, filename): return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename))) diff --git a/django/db/models/query.py b/django/db/models/query.py index 090ef0b7be..05c049b31f 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -498,9 +498,7 @@ class QuerySet(object): "Cannot use 'limit' or 'offset' with in_bulk" if not id_list: return {} - qs = self._clone() - qs.query.add_filter(('pk__in', id_list)) - qs.query.clear_ordering(force_empty=True) + qs = self.filter(pk__in=id_list).order_by() return dict([(obj._get_pk_val(), obj) for obj in qs]) def delete(self): diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 3dc0c6ece4..cf7cad29bd 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -470,9 +470,7 @@ class SQLCompiler(object): # Must use left outer joins for nullable fields and their relations. # Ordering or distinct must not affect the returned set, and INNER # JOINS for nullable fields could do this. - if joins_to_promote: - self.query.promote_alias_chain(joins_to_promote, - self.query.alias_map[joins_to_promote[0]].join_type == self.query.LOUTER) + self.query.promote_joins(joins_to_promote) return field, col, alias, joins, opts def _final_join_removal(self, col, alias): @@ -645,8 +643,6 @@ class SQLCompiler(object): alias_chain.append(alias) for (dupe_opts, dupe_col) in dupe_set: self.query.update_dupe_avoidance(dupe_opts, dupe_col, alias) - if self.query.alias_map[root_alias].join_type == self.query.LOUTER: - self.query.promote_alias_chain(alias_chain, True) else: alias = root_alias @@ -663,8 +659,6 @@ class SQLCompiler(object): columns, aliases = self.get_default_columns(start_alias=alias, opts=f.rel.to._meta, as_pairs=True) self.query.related_select_cols.extend(columns) - if self.query.alias_map[alias].join_type == self.query.LOUTER: - self.query.promote_alias_chain(aliases, True) self.query.related_select_fields.extend(f.rel.to._meta.fields) if restricted: next = requested.get(f.name, {}) @@ -738,7 +732,9 @@ class SQLCompiler(object): self.query.related_select_fields.extend(model._meta.fields) next = requested.get(f.related_query_name(), {}) - new_nullable = f.null or None + # Use True here because we are looking at the _reverse_ side of + # the relation, which is always nullable. + new_nullable = True self.fill_related_selections(model._meta, table, cur_depth+1, used, next, restricted, new_nullable) diff --git a/django/db/models/sql/constants.py b/django/db/models/sql/constants.py index 612755a012..b9cf2c96fd 100644 --- a/django/db/models/sql/constants.py +++ b/django/db/models/sql/constants.py @@ -1,7 +1,7 @@ from collections import namedtuple import re -# Valid query types (a dictionary is used for speedy lookups). +# Valid query types (a set is used for speedy lookups). QUERY_TERMS = set([ 'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in', 'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year', diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index c62c9ac23e..1915efa7b9 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -505,7 +505,7 @@ class Query(object): # Again, some of the tables won't have aliases due to # the trimming of unnecessary tables. if self.alias_refcount.get(alias) or rhs.alias_refcount.get(alias): - self.promote_alias(alias, True) + self.promote_joins([alias], True) # Now relabel a copy of the rhs where-clause and add it to the current # one. @@ -682,32 +682,38 @@ class Query(object): """ Decreases the reference count for this alias. """ self.alias_refcount[alias] -= amount - def promote_alias(self, alias, unconditional=False): + def promote_joins(self, aliases, unconditional=False): """ - Promotes the join type of an alias to an outer join if it's possible - for the join to contain NULL values on the left. If 'unconditional' is - False, the join is only promoted if it is nullable, otherwise it is - always promoted. + Promotes recursively the join type of given aliases and its children to + an outer join. If 'unconditional' is False, the join is only promoted if + it is nullable or the parent join is an outer join. - Returns True if the join was promoted by this call. + Note about join promotion: When promoting any alias, we make sure all + joins which start from that alias are promoted, too. When adding a join + in join(), we make sure any join added to already existing LOUTER join + is generated as LOUTER. This ensures we don't ever have broken join + chains which contain first a LOUTER join, then an INNER JOIN, that is + this kind of join should never be generated: a LOUTER b INNER c. The + reason for avoiding this type of join chain is that the INNER after + the LOUTER will effectively remove any effect the LOUTER had. """ - if ((unconditional or self.alias_map[alias].nullable) and - self.alias_map[alias].join_type != self.LOUTER): - data = self.alias_map[alias] - data = data._replace(join_type=self.LOUTER) - self.alias_map[alias] = data - return True - return False - - def promote_alias_chain(self, chain, must_promote=False): - """ - Walks along a chain of aliases, promoting the first nullable join and - any joins following that. If 'must_promote' is True, all the aliases in - the chain are promoted. - """ - for alias in chain: - if self.promote_alias(alias, must_promote): - must_promote = True + aliases = list(aliases) + while aliases: + alias = aliases.pop(0) + parent_alias = self.alias_map[alias].lhs_alias + parent_louter = (parent_alias + and self.alias_map[parent_alias].join_type == self.LOUTER) + already_louter = self.alias_map[alias].join_type == self.LOUTER + if ((unconditional or self.alias_map[alias].nullable + or parent_louter) and not already_louter): + data = self.alias_map[alias]._replace(join_type=self.LOUTER) + self.alias_map[alias] = data + # Join type of 'alias' changed, so re-examine all aliases that + # refer to this one. + aliases.extend( + join for join in self.alias_map.keys() + if (self.alias_map[join].lhs_alias == alias + and join not in aliases)) def reset_refcounts(self, to_counts): """ @@ -726,19 +732,10 @@ class Query(object): then and which ones haven't been used and promotes all of those aliases, plus any children of theirs in the alias tree, to outer joins. """ - # FIXME: There's some (a lot of!) overlap with the similar OR promotion - # in add_filter(). It's not quite identical, but is very similar. So - # pulling out the common bits is something for later. - considered = {} for alias in self.tables: - if alias not in used_aliases: - continue - if (alias not in initial_refcounts or + if alias in used_aliases and (alias not in initial_refcounts or self.alias_refcount[alias] == initial_refcounts[alias]): - parent = self.alias_map[alias].lhs_alias - must_promote = considered.get(parent, False) - promoted = self.promote_alias(alias, must_promote) - considered[alias] = must_promote or promoted + self.promote_joins([alias]) def change_aliases(self, change_map): """ @@ -875,6 +872,9 @@ class Query(object): LOUTER join type. This is used when joining certain types of querysets and Q-objects together. + A join is always created as LOUTER if the lhs alias is LOUTER to make + sure we do not generate chains like a LOUTER b INNER c. + If 'nullable' is True, the join can potentially involve NULL values and is a candidate for promotion (to "left outer") when combining querysets. """ @@ -900,8 +900,8 @@ class Query(object): if self.alias_map[alias].lhs_alias != lhs: continue self.ref_alias(alias) - if promote: - self.promote_alias(alias) + if promote or (lhs and self.alias_map[lhs].join_type == self.LOUTER): + self.promote_joins([alias]) return alias # No reuse is possible, so we need a new alias. @@ -910,7 +910,12 @@ class Query(object): # Not all tables need to be joined to anything. No join type # means the later columns are ignored. join_type = None - elif promote or outer_if_first: + elif (promote or outer_if_first + or self.alias_map[lhs].join_type == self.LOUTER): + # We need to use LOUTER join if asked by promote or outer_if_first, + # or if the LHS table is left-joined in the query. Adding inner join + # to an existing outer join effectively cancels the effect of the + # outer join. join_type = self.LOUTER else: join_type = self.INNER @@ -1004,8 +1009,7 @@ class Query(object): # If the aggregate references a model or field that requires a join, # those joins must be LEFT OUTER - empty join rows must be returned # in order for zeros to be returned for those aggregates. - for column_alias in join_list: - self.promote_alias(column_alias, unconditional=True) + self.promote_joins(join_list, True) col = (join_list[-1], col) else: @@ -1124,7 +1128,7 @@ class Query(object): # If the comparison is against NULL, we may need to use some left # outer joins when creating the join chain. This is only done when # needed, as it's less efficient at the database level. - self.promote_alias_chain(join_list) + self.promote_joins(join_list) join_promote = True # Process the join list to see if we can remove any inner joins from @@ -1155,16 +1159,16 @@ class Query(object): # This means that we are dealing with two different query # subtrees, so we don't need to do any join promotion. continue - join_promote = join_promote or self.promote_alias(join, unconditional) + join_promote = join_promote or self.promote_joins([join], unconditional) if table != join: - table_promote = self.promote_alias(table) + table_promote = self.promote_joins([table]) # We only get here if we have found a table that exists # in the join list, but isn't on the original tables list. # This means we've reached the point where we only have # new tables, so we can break out of this promotion loop. break - self.promote_alias_chain(join_it, join_promote) - self.promote_alias_chain(table_it, table_promote or join_promote) + self.promote_joins(join_it, join_promote) + self.promote_joins(table_it, table_promote or join_promote) if having_clause or force_having: if (alias, col) not in self.group_by: @@ -1176,7 +1180,7 @@ class Query(object): connector) if negate: - self.promote_alias_chain(join_list) + self.promote_joins(join_list) if lookup_type != 'isnull': if len(join_list) > 1: for alias in join_list: @@ -1650,7 +1654,7 @@ class Query(object): final_alias = join.lhs_alias col = join.lhs_join_col joins = joins[:-1] - self.promote_alias_chain(joins[1:]) + self.promote_joins(joins[1:]) self.select.append((final_alias, col)) self.select_fields.append(field) except MultiJoin: diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 1e8edfdb79..42d25fac6d 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -71,7 +71,8 @@ class BaseFormSet(object): return True __nonzero__ = __bool__ # Python 2 - def _management_form(self): + @property + def management_form(self): """Returns the ManagementForm instance for this FormSet.""" if self.is_bound: form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix) @@ -84,7 +85,6 @@ class BaseFormSet(object): MAX_NUM_FORM_COUNT: self.max_num }) return form - management_form = property(_management_form) def total_form_count(self): """Returns the total number of forms in this FormSet.""" @@ -140,17 +140,18 @@ class BaseFormSet(object): self.add_fields(form, i) return form - def _get_initial_forms(self): + @property + def initial_forms(self): """Return a list of all the initial forms in this formset.""" return self.forms[:self.initial_form_count()] - initial_forms = property(_get_initial_forms) - def _get_extra_forms(self): + @property + def extra_forms(self): """Return a list of all the extra forms in this formset.""" return self.forms[self.initial_form_count():] - extra_forms = property(_get_extra_forms) - def _get_empty_form(self, **kwargs): + @property + def empty_form(self, **kwargs): defaults = { 'auto_id': self.auto_id, 'prefix': self.add_prefix('__prefix__'), @@ -160,19 +161,19 @@ class BaseFormSet(object): form = self.form(**defaults) self.add_fields(form, None) return form - empty_form = property(_get_empty_form) # Maybe this should just go away? - def _get_cleaned_data(self): + @property + def cleaned_data(self): """ Returns a list of form.cleaned_data dicts for every form in self.forms. """ if not self.is_valid(): raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__) return [form.cleaned_data for form in self.forms] - cleaned_data = property(_get_cleaned_data) - def _get_deleted_forms(self): + @property + def deleted_forms(self): """ Returns a list of forms that have been marked for deletion. Raises an AttributeError if deletion is not allowed. @@ -191,9 +192,9 @@ class BaseFormSet(object): if self._should_delete_form(form): self._deleted_form_indexes.append(i) return [self.forms[i] for i in self._deleted_form_indexes] - deleted_forms = property(_get_deleted_forms) - def _get_ordered_forms(self): + @property + def ordered_forms(self): """ Returns a list of form in the order specified by the incoming data. Raises an AttributeError if ordering is not allowed. @@ -228,7 +229,6 @@ class BaseFormSet(object): # Return a list of form.cleaned_data dicts in the order specified by # the form data. return [self.forms[i[0]] for i in self._ordering] - ordered_forms = property(_get_ordered_forms) @classmethod def get_default_prefix(cls): @@ -244,23 +244,20 @@ class BaseFormSet(object): return self._non_form_errors return self.error_class() - def _get_errors(self): + @property + def errors(self): """ Returns a list of form.errors for every form in self.forms. """ if self._errors is None: self.full_clean() return self._errors - errors = property(_get_errors) def _should_delete_form(self, form): - # The way we lookup the value of the deletion field here takes - # more code than we'd like, but the form's cleaned_data will - # not exist if the form is invalid. - field = form.fields[DELETION_FIELD_NAME] - raw_value = form._raw_value(DELETION_FIELD_NAME) - should_delete = field.clean(raw_value) - return should_delete + """ + Returns whether or not the form was marked for deletion. + """ + return form.cleaned_data.get(DELETION_FIELD_NAME, False) def is_valid(self): """ @@ -335,14 +332,14 @@ class BaseFormSet(object): """ return self.forms and self.forms[0].is_multipart() - def _get_media(self): + @property + def media(self): # All the forms on a FormSet are the same, so you only need to # interrogate the first form for media. if self.forms: return self.forms[0].media else: return Media() - media = property(_get_media) def as_table(self): "Returns this formset rendered as HTML s -- excluding the
." diff --git a/django/forms/models.py b/django/forms/models.py index a40d31ad9f..1aa49eaaec 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -18,7 +18,6 @@ from django.utils.datastructures import SortedDict from django.utils import six from django.utils.text import get_text_list, capfirst from django.utils.translation import ugettext_lazy as _, ugettext -from django.utils import six __all__ = ( @@ -592,6 +591,10 @@ class BaseModelFormSet(BaseFormSet): return [] saved_instances = [] + try: + forms_to_delete = self.deleted_forms + except AttributeError: + forms_to_delete = [] for form in self.initial_forms: pk_name = self._pk_field.name raw_pk_value = form._raw_value(pk_name) @@ -602,7 +605,7 @@ class BaseModelFormSet(BaseFormSet): pk_value = getattr(pk_value, 'pk', pk_value) obj = self._existing_object(pk_value) - if self.can_delete and self._should_delete_form(form): + if form in forms_to_delete: self.deleted_objects.append(obj) obj.delete() continue diff --git a/django/http/__init__.py b/django/http/__init__.py index 64abd6fa6e..2198f38cbb 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -61,14 +61,14 @@ else: if not _cookie_allows_colon_in_names: def load(self, rawdata): self.bad_cookies = set() - super(SimpleCookie, self).load(smart_str(rawdata)) + super(SimpleCookie, self).load(force_str(rawdata)) for key in self.bad_cookies: del self[key] # override private __set() method: # (needed for using our Morsel, and for laxness with CookieError def _BaseCookie__set(self, key, real_value, coded_value): - key = smart_str(key) + key = force_str(key) try: M = self.get(key, Morsel()) M.set(key, real_value, coded_value) @@ -85,7 +85,7 @@ from django.core.files import uploadhandler from django.http.multipartparser import MultiPartParser from django.http.utils import * from django.utils.datastructures import MultiValueDict, ImmutableList -from django.utils.encoding import smart_bytes, smart_str, iri_to_uri, force_text +from django.utils.encoding import force_bytes, force_str, force_text, iri_to_uri from django.utils.http import cookie_date from django.utils import six from django.utils import timezone @@ -137,7 +137,7 @@ def build_request_repr(request, path_override=None, GET_override=None, except Exception: meta = '' path = path_override if path_override is not None else request.path - return smart_str('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % + return force_str('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % (request.__class__.__name__, path, six.text_type(get), @@ -243,7 +243,12 @@ class HttpRequest(object): def is_ajax(self): return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest' - def _set_encoding(self, val): + @property + def encoding(self): + return self._encoding + + @encoding.setter + def encoding(self, val): """ Sets the encoding used for GET/POST accesses. If the GET or POST dictionary has already been created, it is removed and recreated on the @@ -255,27 +260,22 @@ class HttpRequest(object): if hasattr(self, '_post'): del self._post - def _get_encoding(self): - return self._encoding - - encoding = property(_get_encoding, _set_encoding) - def _initialize_handlers(self): self._upload_handlers = [uploadhandler.load_handler(handler, self) for handler in settings.FILE_UPLOAD_HANDLERS] - def _set_upload_handlers(self, upload_handlers): - if hasattr(self, '_files'): - raise AttributeError("You cannot set the upload handlers after the upload has been processed.") - self._upload_handlers = upload_handlers - - def _get_upload_handlers(self): + @property + def upload_handlers(self): if not self._upload_handlers: # If there are no upload handlers defined, initialize them from settings. self._initialize_handlers() return self._upload_handlers - upload_handlers = property(_get_upload_handlers, _set_upload_handlers) + @upload_handlers.setter + def upload_handlers(self, upload_handlers): + if hasattr(self, '_files'): + raise AttributeError("You cannot set the upload handlers after the upload has been processed.") + self._upload_handlers = upload_handlers def parse_file_upload(self, META, post_data): """Returns a tuple of (POST QueryDict, FILES MultiValueDict).""" @@ -397,16 +397,16 @@ class QueryDict(MultiValueDict): force_text(value, encoding, errors='replace')) self._mutable = mutable - def _get_encoding(self): + @property + def encoding(self): if self._encoding is None: self._encoding = settings.DEFAULT_CHARSET return self._encoding - def _set_encoding(self, value): + @encoding.setter + def encoding(self, value): self._encoding = value - encoding = property(_get_encoding, _set_encoding) - def _assert_mutable(self): if not self._mutable: raise AttributeError("This QueryDict instance is immutable") @@ -489,13 +489,13 @@ class QueryDict(MultiValueDict): """ output = [] if safe: - safe = smart_bytes(safe, self.encoding) + safe = force_bytes(safe, self.encoding) encode = lambda k, v: '%s=%s' % ((quote(k, safe), quote(v, safe))) else: encode = lambda k, v: urlencode({k: v}) for k, list_ in self.lists(): - k = smart_bytes(k, self.encoding) - output.extend([encode(k, smart_bytes(v, self.encoding)) + k = force_bytes(k, self.encoding) + output.extend([encode(k, force_bytes(v, self.encoding)) for v in list_]) return '&'.join(output) @@ -539,7 +539,7 @@ class HttpResponse(object): if not content_type: content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, self._charset) - # content is a bytestring. See _get_content / _set_content. + # content is a bytestring. See the content property methods. self.content = content self.cookies = SimpleCookie() if status: @@ -669,7 +669,8 @@ class HttpResponse(object): self.set_cookie(key, max_age=0, path=path, domain=domain, expires='Thu, 01-Jan-1970 00:00:00 GMT') - def _get_content(self): + @property + def content(self): if self.has_header('Content-Encoding'): def make_bytes(value): if isinstance(value, int): @@ -679,9 +680,10 @@ class HttpResponse(object): # force conversion to bytes in case chunk is a subclass return bytes(value) return b''.join(make_bytes(e) for e in self._container) - return b''.join(smart_bytes(e, self._charset) for e in self._container) + return b''.join(force_bytes(e, self._charset) for e in self._container) - def _set_content(self, value): + @content.setter + def content(self, value): if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.string_types)): self._container = value self._base_content_is_iter = True @@ -689,8 +691,6 @@ class HttpResponse(object): self._container = [value] self._base_content_is_iter = False - content = property(_get_content, _set_content) - def __iter__(self): self._iterator = iter(self._container) return self @@ -728,11 +728,11 @@ class HttpResponse(object): class HttpResponseRedirectBase(HttpResponse): allowed_schemes = ['http', 'https', 'ftp'] - def __init__(self, redirect_to): + def __init__(self, redirect_to, *args, **kwargs): parsed = urlparse(redirect_to) if parsed.scheme and parsed.scheme not in self.allowed_schemes: raise SuspiciousOperation("Unsafe redirect to URL with protocol '%s'" % parsed.scheme) - super(HttpResponseRedirectBase, self).__init__() + super(HttpResponseRedirectBase, self).__init__(*args, **kwargs) self['Location'] = iri_to_uri(redirect_to) class HttpResponseRedirect(HttpResponseRedirectBase): @@ -744,6 +744,16 @@ class HttpResponsePermanentRedirect(HttpResponseRedirectBase): class HttpResponseNotModified(HttpResponse): status_code = 304 + def __init__(self, *args, **kwargs): + super(HttpResponseNotModified, self).__init__(*args, **kwargs) + del self['content-type'] + + @HttpResponse.content.setter + def content(self, value): + if value: + raise AttributeError("You cannot set content to a 304 (Not Modified) response") + self._container = [] + class HttpResponseBadRequest(HttpResponse): status_code = 400 @@ -756,8 +766,8 @@ class HttpResponseForbidden(HttpResponse): class HttpResponseNotAllowed(HttpResponse): status_code = 405 - def __init__(self, permitted_methods): - super(HttpResponseNotAllowed, self).__init__() + def __init__(self, permitted_methods, *args, **kwargs): + super(HttpResponseNotAllowed, self).__init__(*args, **kwargs) self['Allow'] = ', '.join(permitted_methods) class HttpResponseGone(HttpResponse): diff --git a/django/template/base.py b/django/template/base.py index 091d53421f..0a2b2c9437 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -11,7 +11,7 @@ from django.utils.importlib import import_module from django.utils.itercompat import is_iterable from django.utils.text import (smart_split, unescape_string_literal, get_text_list) -from django.utils.encoding import smart_text, force_text, smart_str +from django.utils.encoding import force_str, force_text from django.utils.translation import ugettext_lazy, pgettext_lazy from django.utils.safestring import (SafeData, EscapeData, mark_safe, mark_for_escaping) @@ -116,7 +116,7 @@ class Template(object): def __init__(self, template_string, origin=None, name=''): try: - template_string = smart_text(template_string) + template_string = force_text(template_string) except UnicodeDecodeError: raise TemplateEncodingError("Templates can only be constructed " "from unicode or UTF-8 strings.") @@ -848,7 +848,7 @@ class TextNode(Node): self.s = s def __repr__(self): - return "" % smart_str(self.s[:25], 'ascii', + return force_str("" % self.s[:25], 'ascii', errors='replace') def render(self, context): diff --git a/django/template/response.py b/django/template/response.py index 800e060c74..2cb44d127d 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -102,7 +102,7 @@ class SimpleTemplateResponse(HttpResponse): """ retval = self if not self._is_rendered: - self._set_content(self.rendered_content) + self.content = self.rendered_content for post_callback in self._post_render_callbacks: newretval = post_callback(retval) if newretval is not None: @@ -119,20 +119,20 @@ class SimpleTemplateResponse(HttpResponse): 'rendered before it can be iterated over.') return super(SimpleTemplateResponse, self).__iter__() - def _get_content(self): + @property + def content(self): if not self._is_rendered: raise ContentNotRenderedError('The response content must be ' 'rendered before it can be accessed.') - return super(SimpleTemplateResponse, self)._get_content() + return super(SimpleTemplateResponse, self).content - def _set_content(self, value): + @content.setter + def content(self, value): """Sets the content for the response """ - super(SimpleTemplateResponse, self)._set_content(value) + HttpResponse.content.fset(self, value) self._is_rendered = True - content = property(_get_content, _set_content) - class TemplateResponse(SimpleTemplateResponse): rendering_attrs = SimpleTemplateResponse.rendering_attrs + \ diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py index e431f99d0d..36db4807c7 100644 --- a/django/templatetags/cache.py +++ b/django/templatetags/cache.py @@ -4,7 +4,7 @@ import hashlib from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist from django.template import resolve_variable from django.core.cache import cache -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_bytes from django.utils.http import urlquote register = Library() @@ -26,8 +26,8 @@ class CacheNode(Node): except (ValueError, TypeError): raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) # Build a key for this fragment and all vary-on's. - key = smart_bytes(':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on])) - args = hashlib.md5(key) + key = ':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on]) + args = hashlib.md5(force_bytes(key)) cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest()) value = cache.get(cache_key) if value is None: diff --git a/django/test/client.py b/django/test/client.py index 2b61c51ce1..8fd765ec9a 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -21,7 +21,7 @@ from django.http import SimpleCookie, HttpRequest, QueryDict from django.template import TemplateDoesNotExist from django.test import signals from django.utils.functional import curry -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_bytes from django.utils.http import urlencode from django.utils.importlib import import_module from django.utils.itercompat import is_iterable @@ -110,7 +110,7 @@ def encode_multipart(boundary, data): as an application/octet-stream; otherwise, str(value) will be sent. """ lines = [] - to_bytes = lambda s: smart_bytes(s, settings.DEFAULT_CHARSET) + to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET) # Not by any means perfect, but good enough for our purposes. is_file = lambda thing: hasattr(thing, "read") and callable(thing.read) @@ -147,7 +147,7 @@ def encode_multipart(boundary, data): return b'\r\n'.join(lines) def encode_file(boundary, key, file): - to_bytes = lambda s: smart_bytes(s, settings.DEFAULT_CHARSET) + to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET) content_type = mimetypes.guess_type(file.name)[0] if content_type is None: content_type = 'application/octet-stream' @@ -222,7 +222,7 @@ class RequestFactory(object): charset = match.group(1) else: charset = settings.DEFAULT_CHARSET - return smart_bytes(data, encoding=charset) + return force_bytes(data, encoding=charset) def _get_path(self, parsed): # If there are parameters, add them @@ -293,7 +293,7 @@ class RequestFactory(object): def generic(self, method, path, data='', content_type='application/octet-stream', **extra): parsed = urlparse(path) - data = smart_bytes(data, settings.DEFAULT_CHARSET) + data = force_bytes(data, settings.DEFAULT_CHARSET) r = { 'PATH_INFO': self._get_path(parsed), 'QUERY_STRING': parsed[4], diff --git a/django/test/signals.py b/django/test/signals.py index 052b7dfa5c..5b0a9a19ca 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -4,7 +4,6 @@ import time from django.conf import settings from django.db import connections from django.dispatch import receiver, Signal -from django.template import context from django.utils import timezone template_rendered = Signal(providing_args=["template", "context"]) @@ -48,9 +47,17 @@ def update_connections_time_zone(**kwargs): @receiver(setting_changed) def clear_context_processors_cache(**kwargs): if kwargs['setting'] == 'TEMPLATE_CONTEXT_PROCESSORS': + from django.template import context context._standard_context_processors = None +@receiver(setting_changed) +def clear_serializers_cache(**kwargs): + if kwargs['setting'] == 'SERIALIZATION_MODULES': + from django.core import serializers + serializers._serializers = {} + + @receiver(setting_changed) def language_changed(**kwargs): if kwargs['setting'] in ('LOCALE_PATHS', 'LANGUAGE_CODE'): diff --git a/django/test/testcases.py b/django/test/testcases.py index e0f2655ee8..f12c431d3a 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -41,7 +41,7 @@ from django.test.utils import (get_warnings_state, restore_warnings_state, override_settings) from django.test.utils import ContextList from django.utils import unittest as ut2 -from django.utils.encoding import smart_bytes, force_text +from django.utils.encoding import force_text from django.utils import six from django.utils.unittest.util import safe_repr from django.views.static import serve diff --git a/django/utils/cache.py b/django/utils/cache.py index 3e99833aa6..91c4796988 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -24,7 +24,7 @@ import time from django.conf import settings from django.core.cache import get_cache -from django.utils.encoding import iri_to_uri, force_text, smart_bytes +from django.utils.encoding import iri_to_uri, force_bytes, force_text from django.utils.http import http_date from django.utils.timezone import get_current_timezone_name from django.utils.translation import get_language @@ -181,14 +181,14 @@ def _generate_cache_key(request, method, headerlist, key_prefix): value = request.META.get(header, None) if value is not None: ctx.update(value) - path = hashlib.md5(smart_bytes(iri_to_uri(request.get_full_path()))) + path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path()))) cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % ( key_prefix, method, path.hexdigest(), ctx.hexdigest()) return _i18n_cache_key_suffix(request, cache_key) def _generate_cache_header_key(key_prefix, request): """Returns a cache key for the header cache.""" - path = hashlib.md5(smart_bytes(iri_to_uri(request.get_full_path()))) + path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path()))) cache_key = 'views.decorators.cache.cache_header.%s.%s' % ( key_prefix, path.hexdigest()) return _i18n_cache_key_suffix(request, cache_key) diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 70a07e7fde..57bc60dc4f 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -23,7 +23,8 @@ except NotImplementedError: using_sysrandom = False from django.conf import settings -from django.utils.encoding import smart_bytes +from django.utils.encoding import force_bytes +from django.utils import six from django.utils.six.moves import xrange @@ -50,7 +51,7 @@ def salted_hmac(key_salt, value, secret=None): # line is redundant and could be replaced by key = key_salt + secret, since # the hmac module does the same thing for keys longer than the block size. # However, we need to ensure that we *always* do this. - return hmac.new(key, msg=smart_bytes(value), digestmod=hashlib.sha1) + return hmac.new(key, msg=force_bytes(value), digestmod=hashlib.sha1) def get_random_string(length=12, @@ -88,8 +89,12 @@ def constant_time_compare(val1, val2): if len(val1) != len(val2): return False result = 0 - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) + if six.PY3 and isinstance(val1, bytes) and isinstance(val2, bytes): + for x, y in zip(val1, val2): + result |= x ^ y + else: + for x, y in zip(val1, val2): + result |= ord(x) ^ ord(y) return result == 0 @@ -142,8 +147,8 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None): assert iterations > 0 if not digest: digest = hashlib.sha256 - password = smart_bytes(password) - salt = smart_bytes(salt) + password = force_bytes(password) + salt = force_bytes(salt) hlen = digest().digest_size if not dklen: dklen = hlen diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 7027b82a61..3b284f3ed0 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -174,7 +174,7 @@ def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'): # An Exception subclass containing non-ASCII data that doesn't # know how to print itself properly. We shouldn't raise a # further exception. - return ' '.join([smart_bytes(arg, encoding, strings_only, + return b' '.join([force_bytes(arg, encoding, strings_only, errors) for arg in s]) return six.text_type(s).encode(encoding, errors) else: @@ -225,7 +225,7 @@ def iri_to_uri(iri): # converted. if iri is None: return iri - return quote(smart_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~") + return quote(force_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~") def filepath_to_uri(path): """Convert an file system path to a URI portion that is suitable for @@ -244,7 +244,7 @@ def filepath_to_uri(path): return path # I know about `os.sep` and `os.altsep` but I want to leave # some flexibility for hardcoding separators. - return quote(smart_bytes(path.replace("\\", "/")), safe=b"/~!*()'") + return quote(force_bytes(path.replace("\\", "/")), safe=b"/~!*()'") # The encoding of the default system locale but falls back to the # given fallback encoding if the encoding is unsupported by python or could diff --git a/django/utils/formats.py b/django/utils/formats.py index 2e54d792fa..555982eede 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -4,7 +4,7 @@ import datetime from django.conf import settings from django.utils import dateformat, numberformat, datetime_safe from django.utils.importlib import import_module -from django.utils.encoding import smart_str +from django.utils.encoding import force_str from django.utils.functional import lazy from django.utils.safestring import mark_safe from django.utils import six @@ -66,7 +66,7 @@ def get_format(format_type, lang=None, use_l10n=None): If use_l10n is provided and is not None, that will force the value to be localized (or not), overriding the value of settings.USE_L10N. """ - format_type = smart_str(format_type) + format_type = force_str(format_type) if use_l10n or (use_l10n is None and settings.USE_L10N): if lang is None: lang = get_language() @@ -160,14 +160,14 @@ def localize_input(value, default=None): return number_format(value) elif isinstance(value, datetime.datetime): value = datetime_safe.new_datetime(value) - format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0]) + format = force_str(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 = force_str(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 = force_str(default or get_format('TIME_INPUT_FORMATS')[0]) return value.strftime(format) return value diff --git a/django/utils/functional.py b/django/utils/functional.py index 085ec40b63..085a8fce59 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -238,7 +238,6 @@ class LazyObject(object): raise NotImplementedError # introspection support: - __members__ = property(lambda self: self.__dir__()) __dir__ = new_method_proxy(dir) diff --git a/django/utils/html.py b/django/utils/html.py index 0ee789ebb5..2b669cc8ec 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -11,7 +11,7 @@ except ImportError: # Python 2 from urlparse import urlsplit, urlunsplit from django.utils.safestring import SafeData, mark_safe -from django.utils.encoding import smart_bytes, force_text +from django.utils.encoding import force_bytes, force_text from django.utils.functional import allow_lazy from django.utils import six from django.utils.text import normalize_newlines @@ -164,7 +164,7 @@ def smart_urlquote(url): # contains a % not followed by two hexadecimal digits. See #9655. if '%' not in url or unquoted_percents_re.search(url): # See http://bugs.python.org/issue2637 - url = quote(smart_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~') + url = quote(force_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~') return force_text(url) diff --git a/django/utils/http.py b/django/utils/http.py index cdd56bfed1..d3c70f1209 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -15,7 +15,7 @@ except ImportError: # Python 2 from email.utils import formatdate from django.utils.datastructures import MultiValueDict -from django.utils.encoding import force_text, smart_str +from django.utils.encoding import force_str, force_text from django.utils.functional import allow_lazy from django.utils import six @@ -39,7 +39,7 @@ def urlquote(url, safe='/'): can safely be used as part of an argument to a subsequent iri_to_uri() call without double-quoting occurring. """ - return force_text(urllib_parse.quote(smart_str(url), smart_str(safe))) + return force_text(urllib_parse.quote(force_str(url), force_str(safe))) urlquote = allow_lazy(urlquote, six.text_type) def urlquote_plus(url, safe=''): @@ -49,7 +49,7 @@ def urlquote_plus(url, safe=''): returned string can safely be used as part of an argument to a subsequent iri_to_uri() call without double-quoting occurring. """ - return force_text(urllib_parse.quote_plus(smart_str(url), smart_str(safe))) + return force_text(urllib_parse.quote_plus(force_str(url), force_str(safe))) urlquote_plus = allow_lazy(urlquote_plus, six.text_type) def urlunquote(quoted_url): @@ -57,7 +57,7 @@ def urlunquote(quoted_url): A wrapper for Python's urllib.unquote() function that can operate on the result of django.utils.http.urlquote(). """ - return force_text(urllib_parse.unquote(smart_str(quoted_url))) + return force_text(urllib_parse.unquote(force_str(quoted_url))) urlunquote = allow_lazy(urlunquote, six.text_type) def urlunquote_plus(quoted_url): @@ -65,7 +65,7 @@ def urlunquote_plus(quoted_url): A wrapper for Python's urllib.unquote_plus() function that can operate on the result of django.utils.http.urlquote_plus(). """ - return force_text(urllib_parse.unquote_plus(smart_str(quoted_url))) + return force_text(urllib_parse.unquote_plus(force_str(quoted_url))) urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type) def urlencode(query, doseq=0): @@ -79,8 +79,8 @@ def urlencode(query, doseq=0): elif hasattr(query, 'items'): query = query.items() return urllib_parse.urlencode( - [(smart_str(k), - [smart_str(i) for i in v] if isinstance(v, (list,tuple)) else smart_str(v)) + [(force_str(k), + [force_str(i) for i in v] if isinstance(v, (list,tuple)) else force_str(v)) for k, v in query], doseq) diff --git a/django/utils/six.py b/django/utils/six.py index 767fe0bbe9..e4ce939844 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -5,7 +5,7 @@ import sys import types __author__ = "Benjamin Peterson " -__version__ = "1.1.0" +__version__ = "1.2.0" # True if we are running on Python 3. @@ -26,19 +26,23 @@ else: text_type = unicode binary_type = str - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit + if sys.platform == "java": + # Jython always uses 32 bits. MAXSIZE = int((1 << 31) - 1) else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X def _add_doc(func, doc): @@ -201,12 +205,19 @@ else: _iteritems = "iteritems" +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + if PY3: def get_unbound_function(unbound): return unbound - - advance_iterator = next + Iterator = object def callable(obj): return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) @@ -214,9 +225,10 @@ else: def get_unbound_function(unbound): return unbound.im_func + class Iterator(object): - def advance_iterator(it): - return it.next() + def next(self): + return type(self).__next__(self) callable = callable _add_doc(get_unbound_function, @@ -231,15 +243,15 @@ get_function_defaults = operator.attrgetter(_func_defaults) def iterkeys(d): """Return an iterator over the keys of a dictionary.""" - return getattr(d, _iterkeys)() + return iter(getattr(d, _iterkeys)()) def itervalues(d): """Return an iterator over the values of a dictionary.""" - return getattr(d, _itervalues)() + return iter(getattr(d, _itervalues)()) def iteritems(d): """Return an iterator over the (key, value) pairs of a dictionary.""" - return getattr(d, _iteritems)() + return iter(getattr(d, _iteritems)()) if PY3: diff --git a/django/utils/text.py b/django/utils/text.py index ca79a24f7c..c19708458b 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -1,22 +1,22 @@ from __future__ import unicode_literals import re -from django.utils import six import unicodedata import warnings from gzip import GzipFile -from django.utils.six.moves import html_entities from io import BytesIO from django.utils.encoding import force_text +from django.utils.functional import allow_lazy, SimpleLazyObject +from django.utils import six +from django.utils.six.moves import html_entities +from django.utils.translation import ugettext_lazy, ugettext as _, pgettext +from django.utils.safestring import mark_safe + if not six.PY3: # Import force_unicode even though this module doesn't use it, because some # people rely on it being here. from django.utils.encoding import force_unicode -from django.utils.functional import allow_lazy, SimpleLazyObject -from django.utils import six -from django.utils.translation import ugettext_lazy, ugettext as _, pgettext -from django.utils.safestring import mark_safe # Capitalizes the first letter of a string. capfirst = lambda x: x and force_text(x)[0].upper() + force_text(x)[1:] diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 71282ff2b3..9fd33a7ea8 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -9,7 +9,7 @@ import gettext as gettext_module from threading import local from django.utils.importlib import import_module -from django.utils.encoding import smart_str, smart_text +from django.utils.encoding import force_str, force_text from django.utils.safestring import mark_safe, SafeData from django.utils import six from django.utils.six import StringIO @@ -454,7 +454,7 @@ def templatize(src, origin=None): from django.conf import settings from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK, TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK) - src = smart_text(src, settings.FILE_CHARSET) + src = force_text(src, settings.FILE_CHARSET) out = StringIO() message_context = None intrans = False @@ -469,7 +469,7 @@ def templatize(src, origin=None): content = ''.join(comment) translators_comment_start = None for lineno, line in enumerate(content.splitlines(True)): - if line.lstrip().startswith(smart_text(TRANSLATOR_COMMENT_MARK)): + if line.lstrip().startswith(TRANSLATOR_COMMENT_MARK): translators_comment_start = lineno for lineno, line in enumerate(content.splitlines(True)): if translators_comment_start is not None and lineno >= translators_comment_start: @@ -584,7 +584,7 @@ def templatize(src, origin=None): out.write(' # %s' % t.contents) else: out.write(blankout(t.contents, 'X')) - return smart_str(out.getvalue()) + return force_str(out.getvalue()) def parse_accept_lang_header(lang_string): """ diff --git a/django/utils/tzinfo.py b/django/utils/tzinfo.py index 208b7e7191..654c99778e 100644 --- a/django/utils/tzinfo.py +++ b/django/utils/tzinfo.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import time from datetime import timedelta, tzinfo -from django.utils.encoding import smart_text, smart_str, DEFAULT_LOCALE_ENCODING +from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING # Python's doc say: "A tzinfo subclass must have an __init__() method that can # be called with no arguments". FixedOffset and LocalTimezone don't honor this @@ -53,7 +53,7 @@ class LocalTimezone(tzinfo): self._tzname = self.tzname(dt) def __repr__(self): - return smart_str(self._tzname) + return force_str(self._tzname) def __getinitargs__(self): return self.__dt, @@ -72,7 +72,7 @@ class LocalTimezone(tzinfo): def tzname(self, dt): try: - return smart_text(time.tzname[self._isdst(dt)], + return force_text(time.tzname[self._isdst(dt)], DEFAULT_LOCALE_ENCODING) except UnicodeDecodeError: return None diff --git a/django/views/debug.py b/django/views/debug.py index b275ef9e73..ed99d8dfe6 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -14,7 +14,7 @@ from django.template import Template, Context, TemplateDoesNotExist from django.template.defaultfilters import force_escape, pprint from django.utils.html import escape from django.utils.importlib import import_module -from django.utils.encoding import smart_text, smart_bytes +from django.utils.encoding import force_bytes, smart_text from django.utils import six HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE') @@ -440,7 +440,7 @@ def technical_404_response(request, exception): 'root_urlconf': settings.ROOT_URLCONF, 'request_path': request.path_info[1:], # Trim leading slash 'urlpatterns': tried, - 'reason': smart_bytes(exception, errors='replace'), + 'reason': force_bytes(exception, errors='replace'), 'request': request, 'settings': get_safe_settings(), }) diff --git a/django/views/static.py b/django/views/static.py index bcac9475e2..2ff22ce13f 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -61,7 +61,7 @@ def serve(request, path, document_root=None, show_indexes=False): mimetype = mimetype or 'application/octet-stream' if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), statobj.st_mtime, statobj.st_size): - return HttpResponseNotModified(content_type=mimetype) + return HttpResponseNotModified() with open(fullpath, 'rb') as f: response = HttpResponse(f.read(), content_type=mimetype) response["Last-Modified"] = http_date(statobj.st_mtime) diff --git a/docs/conf.py b/docs/conf.py index 9ac93d7cbc..433fd679a1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -191,7 +191,8 @@ modindex_common_prefix = ["django."] # -- Options for LaTeX output -------------------------------------------------- latex_elements = { - 'preamble': '\\DeclareUnicodeCharacter{2265}{\\ensuremath{\\ge}}' + 'preamble': ('\\DeclareUnicodeCharacter{2264}{\\ensuremath{\\le}}' + '\\DeclareUnicodeCharacter{2265}{\\ensuremath{\\ge}}') } # Grouping the document tree into LaTeX files. List of tuples diff --git a/docs/faq/general.txt b/docs/faq/general.txt index 771af8a2cc..659a8c5ad9 100644 --- a/docs/faq/general.txt +++ b/docs/faq/general.txt @@ -185,6 +185,6 @@ would be happy to help you. You might also be interested in posting a job to http://djangogigs.com/ . If you want to find Django-capable people in your local area, try -http://djangopeople.net/ . +https://people.djangoproject.com/ . .. _developers for hire page: https://code.djangoproject.com/wiki/DevelopersForHire diff --git a/docs/index.txt b/docs/index.txt index ca08301dad..3f4e30385c 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -108,7 +108,8 @@ to know about views via the links below: :doc:`Built-in display views ` | :doc:`Built-in editing views ` | :doc:`Using mixins ` | - :doc:`API reference ` + :doc:`API reference ` | + :doc:`Flattened index` * **Advanced:** :doc:`Generating CSV ` | diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 5567c26fa1..2faace99a5 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -77,7 +77,7 @@ record of being helpful on the mailing lists, and a proven desire to dedicate serious time to Django. In return, they've been granted the coveted commit bit, and have free rein to hack on all parts of Django. -`Malcolm Tredinnick`_ +Malcolm Tredinnick Malcolm originally wanted to be a mathematician, somehow ended up a software developer. He's contributed to many Open Source projects, has served on the board of the GNOME foundation, and will kick your ass at chess. @@ -85,8 +85,6 @@ and have free rein to hack on all parts of Django. When he's not busy being an International Man of Mystery, Malcolm lives in Sydney, Australia. -.. _malcolm tredinnick: http://www.pointy-stick.com/ - `Russell Keith-Magee`_ Russell studied physics as an undergraduate, and studied neural networks for his PhD. His first job was with a startup in the defense industry developing diff --git a/docs/ref/class-based-views/flattened-index.txt b/docs/ref/class-based-views/flattened-index.txt new file mode 100644 index 0000000000..cbce3690e2 --- /dev/null +++ b/docs/ref/class-based-views/flattened-index.txt @@ -0,0 +1,556 @@ +=========================================== +Class-based generic views - flattened index +=========================================== + +This index provides an alternate organization of the reference documentation +for class-based views. For each view, the effective attributes and methods from +the class tree are represented under that view. For the reference +documentation organized by the class which defines the behavior, see +:doc:`Class-based views` + +Simple generic views +-------------------- + +View +~~~~ +.. class:: View() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.base.View.http_method_names` + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` + +TemplateView +~~~~~~~~~~~~ +.. class:: TemplateView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.base.TemplateView.get` +* :meth:`~django.views.generic.base.TemplateView.get_context_data` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +RedirectView +~~~~~~~~~~~~ +.. class:: RedirectView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.base.RedirectView.permanent` +* :attr:`~django.views.generic.base.RedirectView.query_string` +* :attr:`~django.views.generic.base.RedirectView.url` + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.RedirectView.delete` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.base.RedirectView.get` +* :meth:`~django.views.generic.base.RedirectView.get_redirect_url` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.base.RedirectView.options` +* :meth:`~django.views.generic.base.RedirectView.post` +* :meth:`~django.views.generic.base.RedirectView.put` + +Detail Views +------------ + +DetailView +~~~~~~~~~~ +.. class:: DetailView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.detail.SingleObjectMixin.model` +* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` +* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.detail.BaseDetailView.get` +* :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data` +* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +List Views +---------- + +ListView +~~~~~~~~ +.. class:: ListView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.list.MultipleObjectMixin.model` +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` +* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.list.BaseListView.get` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +Editing views +------------- + +FormView +~~~~~~~~ +.. class:: FormView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.edit.FormMixin.form_invalid` +* :meth:`~django.views.generic.edit.FormMixin.form_valid` +* :meth:`~django.views.generic.edit.ProcessFormView.get` +* :meth:`~django.views.generic.edit.FormMixin.get_context_data` +* :meth:`~django.views.generic.edit.FormMixin.get_form` +* :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.edit.ProcessFormView.post` +* :meth:`~django.views.generic.edit.ProcessFormView.put` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +CreateView +~~~~~~~~~~ +.. class:: CreateView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.model` +* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` +* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` +* :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.edit.FormMixin.form_invalid` +* :meth:`~django.views.generic.edit.FormMixin.form_valid` +* :meth:`~django.views.generic.edit.ProcessFormView.get` +* :meth:`~django.views.generic.edit.FormMixin.get_context_data` +* :meth:`~django.views.generic.edit.FormMixin.get_form` +* :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs` +* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.edit.ProcessFormView.post` +* :meth:`~django.views.generic.edit.ProcessFormView.put` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +UpdateView +~~~~~~~~~~ +.. class:: UpdateView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.model` +* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` +* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` +* :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.edit.FormMixin.form_invalid` +* :meth:`~django.views.generic.edit.FormMixin.form_valid` +* :meth:`~django.views.generic.edit.ProcessFormView.get` +* :meth:`~django.views.generic.edit.FormMixin.get_context_data` +* :meth:`~django.views.generic.edit.FormMixin.get_form` +* :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs` +* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.edit.ProcessFormView.post` +* :meth:`~django.views.generic.edit.ProcessFormView.put` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +DeleteView +~~~~~~~~~~ +.. class:: DeleteView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.detail.SingleObjectMixin.model` +* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` +* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` +* :attr:`~django.views.generic.edit.DeletionMixin.success_url` [:meth:`~django.views.generic.edit.DeletionMixin.get_success_url`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.edit.DeletionMixin.delete` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.detail.BaseDetailView.get` +* :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data` +* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.edit.DeletionMixin.post` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +Date-based views +---------------- + +ArchiveIndexView +~~~~~~~~~~~~~~~~ +.. class:: ArchiveIndexView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`] +* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.list.MultipleObjectMixin.model` +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` +* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.dates.BaseDateListView.get` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data` +* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +YearArchiveView +~~~~~~~~~~~~~~~ +.. class:: YearArchiveView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`] +* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.dates.BaseYearArchiveView.make_object_list` [:meth:`~django.views.generic.dates.BaseYearArchiveView.get_make_object_list`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.model` +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` +* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` +* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] +* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`] + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.dates.BaseDateListView.get` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data` +* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +MonthArchiveView +~~~~~~~~~~~~~~~~ +.. class:: MonthArchiveView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`] +* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.list.MultipleObjectMixin.model` +* :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] +* :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` +* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` +* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] +* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`] + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.dates.BaseDateListView.get` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data` +* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset` +* :meth:`~django.views.generic.dates.MonthMixin.get_next_month` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator` +* :meth:`~django.views.generic.dates.MonthMixin.get_previous_month` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +WeekArchiveView +~~~~~~~~~~~~~~~ +.. class:: WeekArchiveView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`] +* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.list.MultipleObjectMixin.model` +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` +* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` +* :attr:`~django.views.generic.dates.WeekMixin.week` [:meth:`~django.views.generic.dates.WeekMixin.get_week`] +* :attr:`~django.views.generic.dates.WeekMixin.week_format` [:meth:`~django.views.generic.dates.WeekMixin.get_week_format`] +* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] +* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`] + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.dates.BaseDateListView.get` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data` +* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +DayArchiveView +~~~~~~~~~~~~~~ +.. class:: DayArchiveView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`] +* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`] +* :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.list.MultipleObjectMixin.model` +* :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] +* :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` +* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` +* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] +* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`] + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.dates.BaseDateListView.get` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data` +* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset` +* :meth:`~django.views.generic.dates.DayMixin.get_next_day` +* :meth:`~django.views.generic.dates.MonthMixin.get_next_month` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator` +* :meth:`~django.views.generic.dates.DayMixin.get_previous_day` +* :meth:`~django.views.generic.dates.MonthMixin.get_previous_month` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +TodayArchiveView +~~~~~~~~~~~~~~~~ +.. class:: TodayArchiveView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_allow_empty`] +* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.context_object_name` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`] +* :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.list.MultipleObjectMixin.model` +* :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] +* :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` +* :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` +* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] +* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`] + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.dates.BaseDateListView.get` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_context_data` +* :meth:`~django.views.generic.dates.BaseDateListView.get_date_list` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_items` +* :meth:`~django.views.generic.dates.BaseDateListView.get_dated_queryset` +* :meth:`~django.views.generic.dates.DayMixin.get_next_day` +* :meth:`~django.views.generic.dates.MonthMixin.get_next_month` +* :meth:`~django.views.generic.list.MultipleObjectMixin.get_paginator` +* :meth:`~django.views.generic.dates.DayMixin.get_previous_day` +* :meth:`~django.views.generic.dates.MonthMixin.get_previous_month` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.list.MultipleObjectMixin.paginate_queryset` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` + +DateDetailView +~~~~~~~~~~~~~~ +.. class:: DateDetailView() + +**Attributes** (with optional accessor): + +* :attr:`~django.views.generic.dates.DateMixin.allow_future` [:meth:`~django.views.generic.dates.DateMixin.get_allow_future`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] +* :attr:`~django.views.generic.dates.DateMixin.date_field` [:meth:`~django.views.generic.dates.DateMixin.get_date_field`] +* :attr:`~django.views.generic.dates.DayMixin.day` [:meth:`~django.views.generic.dates.DayMixin.get_day`] +* :attr:`~django.views.generic.dates.DayMixin.day_format` [:meth:`~django.views.generic.dates.DayMixin.get_day_format`] +* :attr:`~django.views.generic.base.View.http_method_names` +* :attr:`~django.views.generic.detail.SingleObjectMixin.model` +* :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] +* :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` +* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` +* :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` +* :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] +* :attr:`~django.views.generic.dates.YearMixin.year_format` [:meth:`~django.views.generic.dates.YearMixin.get_year_format`] + +**Methods** + +* :meth:`~django.views.generic.base.View.as_view` +* :meth:`~django.views.generic.base.View.dispatch` +* :meth:`~django.views.generic.detail.BaseDetailView.get` +* :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data` +* :meth:`~django.views.generic.dates.DayMixin.get_next_day` +* :meth:`~django.views.generic.dates.MonthMixin.get_next_month` +* :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` +* :meth:`~django.views.generic.dates.DayMixin.get_previous_day` +* :meth:`~django.views.generic.dates.MonthMixin.get_previous_month` +* :meth:`~django.views.generic.base.View.head` +* :meth:`~django.views.generic.base.View.http_method_not_allowed` +* :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt index 9ed0762533..f0e7bbc6c1 100644 --- a/docs/ref/class-based-views/index.txt +++ b/docs/ref/class-based-views/index.txt @@ -13,6 +13,7 @@ Class-based views API reference. For introductory material, see generic-editing generic-date-based mixins + flattened-index Specification ------------- diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 7ca102c529..66a5a2cc4f 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -561,8 +561,6 @@ subclass:: .. attribute:: ModelAdmin.list_filter - .. versionchanged:: 1.4 - Set ``list_filter`` to activate filters in the right sidebar of the change list page of the admin, as illustrated in the following screenshot: @@ -586,6 +584,8 @@ subclass:: class PersonAdmin(UserAdmin): list_filter = ('company__name',) + .. versionadded:: 1.4 + * a class inheriting from :mod:`django.contrib.admin.SimpleListFilter`, which you need to provide the ``title`` and ``parameter_name`` attributes to and override the ``lookups`` and ``queryset`` methods, @@ -671,6 +671,8 @@ subclass:: birthday__lte=date(1999, 12, 31)).exists(): yield ('90s', _('in the nineties')) + .. versionadded:: 1.4 + * a tuple, where the first element is a field name and the second element is a class inheriting from :mod:`django.contrib.admin.FieldListFilter`, for example:: diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index af937e036e..4b1dd96280 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -158,11 +158,13 @@ For example:: .. warning:: - There's a known bug in Safari/Webkit which causes the named anchor to be + There's a `known bug`_ in Safari/Webkit which causes the named anchor to be forgotten following a redirect. The practical impact for comments is that the Safari/webkit browsers will arrive at the correct page but will not scroll to the named anchor. +.. _`known bug`: https://bugs.webkit.org/show_bug.cgi?id=24175 + .. templatetag:: get_comment_count Counting comments diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index f25cb31e4b..8d352ff8b2 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -84,47 +84,94 @@ AJAX While the above method can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. For this reason, there is an alternative method: on each -XMLHttpRequest, set a custom `X-CSRFToken` header to the value of the CSRF +XMLHttpRequest, set a custom ``X-CSRFToken`` header to the value of the CSRF token. This is often easier, because many javascript frameworks provide hooks -that allow headers to be set on every request. In jQuery, you can use the -``ajaxSend`` event as follows: +that allow headers to be set on every request. + +As a first step, you must get the CSRF token itself. The recommended source for +the token is the ``csrftoken`` cookie, which will be set if you've enabled CSRF +protection for your views as outlined above. + +.. note:: + + The CSRF token cookie is named ``csrftoken`` by default, but you can control + the cookie name via the :setting:`CSRF_COOKIE_NAME` setting. + +Acquiring the token is straightforward: .. code-block:: javascript - jQuery(document).ajaxSend(function(event, xhr, settings) { - function getCookie(name) { - var cookieValue = null; - if (document.cookie && document.cookie != '') { - var cookies = document.cookie.split(';'); - for (var i = 0; i < cookies.length; i++) { - var cookie = jQuery.trim(cookies[i]); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) == (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } + // using jQuery + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; } } - return cookieValue; - } - function sameOrigin(url) { - // url could be relative or scheme relative or absolute - var host = document.location.host; // host + port - var protocol = document.location.protocol; - var sr_origin = '//' + host; - var origin = protocol + sr_origin; - // Allow absolute or scheme relative URLs to same origin - return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || - (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || - // or any other URL that isn't scheme relative or absolute i.e relative. - !(/^(\/\/|http:|https:).*/.test(url)); - } - function safeMethod(method) { - return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } + return cookieValue; + } + var csrftoken = getCookie('csrftoken'); - if (!safeMethod(settings.type) && sameOrigin(settings.url)) { - xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); +The above code could be simplified by using the `jQuery cookie plugin +`_ to replace ``getCookie``: + +.. code-block:: javascript + + var csrftoken = $.cookie('csrftoken'); + +.. note:: + + The CSRF token is also present in the DOM, but only if explicitly included + using :ttag:`csrf_token` in a template. The cookie contains the canonical + token; the ``CsrfViewMiddleware`` will prefer the cookie to the token in + the DOM. Regardless, you're guaranteed to have the cookie if the token is + present in the DOM, so you should use the cookie! + +.. warning:: + + If your view is not rendering a template containing the :ttag:`csrf_token` + template tag, Django might not set the CSRF token cookie. This is common in + cases where forms are dynamically added to the page. To address this case, + Django provides a view decorator which forces setting of the cookie: + :func:`~django.views.decorators.csrf.ensure_csrf_cookie`. + +Finally, you'll have to actually set the header on your AJAX request, while +protecting the CSRF token from being sent to other domains. + +.. code-block:: javascript + + function csrfSafeMethod(method) { + // these HTTP methods do not require CSRF protection + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); + } + function sameOrigin(url) { + // test that a given url is a same-origin URL + // url could be relative or scheme relative or absolute + var host = document.location.host; // host + port + var protocol = document.location.protocol; + var sr_origin = '//' + host; + var origin = protocol + sr_origin; + // Allow absolute or scheme relative URLs to same origin + return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || + (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || + // or any other URL that isn't scheme relative or absolute i.e relative. + !(/^(\/\/|http:|https:).*/.test(url)); + } + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) { + // Send the token to same-origin, relative URLs only. + // Send the token only if the method warrants CSRF protection + // Using the CSRFToken value acquired earlier + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } } }); @@ -133,18 +180,32 @@ that allow headers to be set on every request. In jQuery, you can use the Due to a bug introduced in jQuery 1.5, the example above will not work correctly on that version. Make sure you are running at least jQuery 1.5.1. -Adding this to a javascript file that is included on your site will ensure that -AJAX POST requests that are made via jQuery will not be caught by the CSRF -protection. +You can use `settings.crossDomain `_ in +jQuery 1.5 and newer in order to replace the `sameOrigin` logic above: -The above code could be simplified by using the `jQuery cookie plugin -`_ to replace ``getCookie``, and -`settings.crossDomain `_ in jQuery 1.5 and -later to replace ``sameOrigin``. +.. code-block:: javascript -In addition, if the CSRF cookie has not been sent to the client by use of -:ttag:`csrf_token`, you may need to ensure the client receives the cookie by -using :func:`~django.views.decorators.csrf.ensure_csrf_cookie`. + function csrfSafeMethod(method) { + // these HTTP methods do not require CSRF protection + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); + } + $.ajaxSetup({ + crossDomain: false, // obviates need for sameOrigin test + beforeSend: function(xhr, settings) { + if (!csrfSafeMethod(settings.type)) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + +.. note:: + + In a `security release blogpost`_, a simpler "same origin test" example + was provided which only checked for a relative URL. The ``sameOrigin`` + test above supersedes that example—it works for edge cases like + scheme-relative or absolute URLs for the same domain. + +.. _security release blogpost: https://www.djangoproject.com/weblog/2011/feb/08/security/ Other template engines ---------------------- diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index 6b5773b5a4..3de449708f 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/docs/ref/contrib/flatpages.txt @@ -42,6 +42,16 @@ To install the flatpages app, follow these steps: 2. Add ``'django.contrib.flatpages'`` to your :setting:`INSTALLED_APPS` setting. +Then either: + +3. Add an entry in your URLconf. For example:: + + urlpatterns = patterns('', + ('^pages/', include('django.contrib.flatpages.urls')), + ) + +or: + 3. Add ``'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'`` to your :setting:`MIDDLEWARE_CLASSES` setting. @@ -57,8 +67,38 @@ and ``django_flatpage_sites``. ``django_flatpage`` is a simple lookup table that simply maps a URL to a title and bunch of text content. ``django_flatpage_sites`` associates a flatpage with a site. +Using the URLconf +----------------- + +There are several ways to include the flat pages in your URLconf. You can +dedicate a particular path to flat pages:: + + urlpatterns = patterns('', + ('^pages/', include('django.contrib.flatpages.urls')), + ) + +You can also set it up as a "catchall" pattern. In this case, it is important +to place the pattern at the end of the other urlpatterns:: + + # Your other patterns here + urlpatterns += patterns('django.contrib.flatpages.views', + (r'^(?P.*)$', 'flatpage'), + ) + +Another common setup is to use flat pages for a limited set of known pages and +to hard code the urls, so you can reference them with the :ttag:`url` template +tag:: + + urlpatterns += patterns('django.contrib.flatpages.views', + url(r'^about-us/$', 'flatpage', {'url': '/about-us/'}, name='about'), + url(r'^license/$', 'flatpage', {'url': '/license/'}, name='license'), + ) + +Using the middleware +-------------------- + The :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware` -does all of the work. +can do all of the work. .. class:: FlatpageFallbackMiddleware @@ -255,4 +295,3 @@ For example: {% get_flatpages '/about/' as about_pages %} {% get_flatpages about_prefix as about_pages %} {% get_flatpages '/about/' for someuser as about_pages %} - diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 3cd790212c..3e952c173b 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -582,7 +582,7 @@ Creating a spatial database for SpatiaLite After you've installed SpatiaLite, you'll need to create a number of spatial metadata tables in your database in order to perform spatial queries. -If you're using SpatiaLite 3.0 or newer, use the ``spatialite`` utility to +If you're using SpatiaLite 2.4 or newer, use the ``spatialite`` utility to call the ``InitSpatialMetaData()`` function, like this:: $ spatialite geodjango.db "SELECT InitSpatialMetaData();" @@ -593,12 +593,10 @@ call the ``InitSpatialMetaData()`` function, like this:: You can safely ignore the error messages shown. When you've done this, you can skip the rest of this section. -If you're using a version of SpatiaLite older than 3.0, you'll need to download -a database-initialization file and execute its SQL queries in your database. +If you're using SpatiaLite 2.3, you'll need to download a +database-initialization file and execute its SQL queries in your database. -First, get it from the appropriate SpatiaLite Resources page ( -http://www.gaia-gis.it/spatialite-2.3.1/resources.html for 2.3 or -http://www.gaia-gis.it/spatialite-2.4.0/ for 2.4):: +First, get it from the `SpatiaLite Resources`__ page:: $ wget http://www.gaia-gis.it/spatialite-2.3.1/init_spatialite-2.3.sql.gz $ gunzip init_spatialite-2.3.sql.gz @@ -613,6 +611,8 @@ Then, use the ``spatialite`` command to initialize a spatial database:: you want to use. Use the same in the :setting:`DATABASES` ``"name"`` key inside your ``settings.py``. +__ http://www.gaia-gis.it/spatialite-2.3.1/resources.html + Add ``django.contrib.gis`` to :setting:`INSTALLED_APPS` ------------------------------------------------------- @@ -820,8 +820,10 @@ Download the framework packages for: * GDAL Install the packages in the order they are listed above, as the GDAL and SQLite -packages require the packages listed before them. Afterwards, you can also -install the KyngChaos binary packages for `PostgreSQL and PostGIS`__. +packages require the packages listed before them. + +Afterwards, you can also install the KyngChaos binary packages for `PostgreSQL +and PostGIS`__. After installing the binary packages, you'll want to add the following to your ``.profile`` to be able to run the package programs from the command-line:: diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt index 18ffbdd7d8..d12c884a1b 100644 --- a/docs/ref/contrib/gis/testing.txt +++ b/docs/ref/contrib/gis/testing.txt @@ -119,7 +119,7 @@ Settings ``SPATIALITE_SQL`` ^^^^^^^^^^^^^^^^^^ -Only relevant when using a SpatiaLite version older than 3.0. +Only relevant when using a SpatiaLite version 2.3. By default, the GeoDjango test runner looks for the :ref:`file containing the SpatiaLite dababase-initialization SQL code ` in the diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 3a63493137..15863aee7b 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -671,6 +671,17 @@ of abstraction:: __ http://spatialreference.org/ref/epsg/32140/ +.. admonition:: Raw queries + + When using :doc:`raw queries `, you should generally wrap + your geometry fields with the ``asText()`` SQL function so as the field + value will be recognized by GEOS:: + + City.objects.raw('SELECT id, name, asText(point) from myapp_city') + + This is not absolutely required by PostGIS, but generally you should only + use raw queries when you know exactly what you are doing. + Lazy Geometries --------------- Geometries come to GeoDjango in a standardized textual representation. Upon diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt index 99547f1c9e..ada614df45 100644 --- a/docs/ref/files/file.txt +++ b/docs/ref/files/file.txt @@ -91,14 +91,18 @@ The ``ContentFile`` Class .. class:: ContentFile(File) The ``ContentFile`` class inherits from :class:`~django.core.files.File`, - but unlike :class:`~django.core.files.File` it operates on string content, - rather than an actual file. For example:: + but unlike :class:`~django.core.files.File` it operates on string content + (bytes also supported), rather than an actual file. For example:: from __future__ import unicode_literals from django.core.files.base import ContentFile - f1 = ContentFile(b"my string content") - f2 = ContentFile("my unicode content encoded as UTF-8".encode('UTF-8')) + f1 = ContentFile("esta sentencia está en español") + f2 = ContentFile(b"these are bytes") + + .. versionchanged:: 1.5 + + ContentFile also accepts Unicode strings. .. currentmodule:: django.core.files.images diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index a43163c5e9..275c696230 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -70,8 +70,8 @@ If ``True``, the field is allowed to be blank. Default is ``False``. Note that this is different than :attr:`~Field.null`. :attr:`~Field.null` is purely database-related, whereas :attr:`~Field.blank` is validation-related. If -a field has ``blank=True``, validation on Django's admin site will allow entry -of an empty value. If a field has ``blank=False``, the field will be required. +a field has ``blank=True``, form validation will allow entry of an empty value. +If a field has ``blank=False``, the field will be required. .. _field-choices: @@ -81,14 +81,11 @@ of an empty value. If a field has ``blank=False``, the field will be required. .. attribute:: Field.choices An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this -field. +field. If this is given, the default form widget will be a select box with +these choices instead of the standard text field. -If this is given, Django's admin will use a select box instead of the standard -text field and will limit choices to the choices given. - -A choices list is an iterable of 2-tuples; the first element in each -tuple is the actual value to be stored, and the second element is the -human-readable name. For example:: +The first element in each tuple is the actual value to be stored, and the +second element is the human-readable name. For example:: YEAR_IN_SCHOOL_CHOICES = ( ('FR', 'Freshman'), @@ -176,7 +173,7 @@ scenes. .. attribute:: Field.db_index -If ``True``, djadmin:`django-admin.py sqlindexes ` will output a +If ``True``, :djadmin:`django-admin.py sqlindexes ` will output a ``CREATE INDEX`` statement for this field. ``db_tablespace`` @@ -203,8 +200,8 @@ callable it will be called every time a new object is created. .. attribute:: Field.editable -If ``False``, the field will not be editable in the admin or via forms -automatically generated from the model class. Default is ``True``. +If ``False``, the field will not be displayed in the admin or any other +:class:`~django.forms.ModelForm`. Default is ``True``. ``error_messages`` ------------------ @@ -224,11 +221,11 @@ the `Field types`_ section below. .. attribute:: Field.help_text -Extra "help" text to be displayed under the field on the object's admin form. -It's useful for documentation even if your object doesn't have an admin form. +Extra "help" text to be displayed with the form widget. It's useful for +documentation even if your field isn't used on a form. -Note that this value is *not* HTML-escaped when it's displayed in the admin -interface. This lets you include HTML in :attr:`~Field.help_text` if you so +Note that this value is *not* HTML-escaped in automatically-generated +forms. This lets you include HTML in :attr:`~Field.help_text` if you so desire. For example:: help_text="Please use the following format: YYYY-MM-DD." @@ -259,7 +256,7 @@ Only one primary key is allowed on an object. If ``True``, this field must be unique throughout the table. -This is enforced at the database level and at the Django admin-form level. If +This is enforced at the database level and by model validation. If you try to save a model with a duplicate value in a :attr:`~Field.unique` field, a :exc:`django.db.IntegrityError` will be raised by the model's :meth:`~django.db.models.Model.save` method. @@ -279,7 +276,7 @@ For example, if you have a field ``title`` that has ``unique_for_date="pub_date"``, then Django wouldn't allow the entry of two records with the same ``title`` and ``pub_date``. -This is enforced at the Django admin-form level but not at the database level. +This is enforced by model validation but not at the database level. ``unique_for_month`` -------------------- @@ -337,7 +334,7 @@ otherwise. See :ref:`automatic-primary-key-fields`. A 64 bit integer, much like an :class:`IntegerField` except that it is guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The -admin represents this as an ```` (a single-line input). +default form widget for this field is a :class:`~django.forms.TextInput`. ``BooleanField`` @@ -347,7 +344,8 @@ admin represents this as an ```` (a single-line input). A true/false field. -The admin represents this as a checkbox. +The default form widget for this field is a +:class:`~django.forms.CheckboxInput`. If you need to accept :attr:`~Field.null` values then use :class:`NullBooleanField` instead. @@ -361,7 +359,7 @@ A string field, for small- to large-sized strings. For large amounts of text, use :class:`~django.db.models.TextField`. -The admin represents this as an ```` (a single-line input). +The default form widget for this field is a :class:`~django.forms.TextInput`. :class:`CharField` has one extra required argument: @@ -414,9 +412,10 @@ optional arguments: for creation of timestamps. Note that the current date is *always* used; it's not just a default value that you can override. -The admin represents this as an ```` with a JavaScript -calendar, and a shortcut for "Today". Includes an additional ``invalid_date`` -error message key. +The default form widget for this field is a +:class:`~django.forms.TextInput`. The admin adds a JavaScript calendar, +and a shortcut for "Today". Includes an additional ``invalid_date`` error +message key. .. note:: As currently implemented, setting ``auto_now`` or ``auto_now_add`` to @@ -431,8 +430,9 @@ error message key. A date and time, represented in Python by a ``datetime.datetime`` instance. Takes the same extra arguments as :class:`DateField`. -The admin represents this as two ```` fields, with -JavaScript shortcuts. +The default form widget for this field is a single +:class:`~django.forms.TextInput`. The admin uses two separate +:class:`~django.forms.TextInput` widgets with JavaScript shortcuts. ``DecimalField`` ---------------- @@ -461,7 +461,7 @@ decimal places:: models.DecimalField(..., max_digits=19, decimal_places=10) -The admin represents this as an ```` (a single-line input). +The default form widget for this field is a :class:`~django.forms.TextInput`. .. note:: @@ -539,8 +539,8 @@ Also has one optional argument: Optional. A storage object, which handles the storage and retrieval of your files. See :doc:`/topics/files` for details on how to provide this object. -The admin represents this field as an ```` (a file-upload -widget). +The default form widget for this field is a +:class:`~django.forms.widgets.FileInput`. Using a :class:`FileField` or an :class:`ImageField` (see below) in a model takes a few steps: @@ -725,7 +725,7 @@ can change the maximum length using the :attr:`~CharField.max_length` argument. A floating-point number represented in Python by a ``float`` instance. -The admin represents this as an ```` (a single-line input). +The default form widget for this field is a :class:`~django.forms.TextInput`. .. _floatfield_vs_decimalfield: @@ -776,16 +776,16 @@ length using the :attr:`~CharField.max_length` argument. .. class:: IntegerField([**options]) -An integer. The admin represents this as an ```` (a -single-line input). +An integer. The default form widget for this field is a +:class:`~django.forms.TextInput`. ``IPAddressField`` ------------------ .. class:: IPAddressField([**options]) -An IP address, in string format (e.g. "192.0.2.30"). The admin represents this -as an ```` (a single-line input). +An IP address, in string format (e.g. "192.0.2.30"). The default form widget +for this field is a :class:`~django.forms.TextInput`. ``GenericIPAddressField`` ------------------------- @@ -795,8 +795,8 @@ as an ```` (a single-line input). .. versionadded:: 1.4 An IPv4 or IPv6 address, in string format (e.g. ``192.0.2.30`` or -``2a02:42fe::4``). The admin represents this as an ```` -(a single-line input). +``2a02:42fe::4``). The default form widget for this field is a +:class:`~django.forms.TextInput`. The IPv6 address normalization follows :rfc:`4291#section-2.2` section 2.2, including using the IPv4 format suggested in paragraph 3 of that section, like @@ -823,8 +823,8 @@ are converted to lowercase. .. class:: NullBooleanField([**options]) Like a :class:`BooleanField`, but allows ``NULL`` as one of the options. Use -this instead of a :class:`BooleanField` with ``null=True``. The admin represents -this as a ``