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 ```.
+Absolute filesystem path to the directory that will hold :doc:`user-uploaded
+files `.
Example: ``"/var/www/example.com/media/"``
diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt
index c52194e6b7..48bd346788 100644
--- a/docs/ref/templates/api.txt
+++ b/docs/ref/templates/api.txt
@@ -125,6 +125,10 @@ dot in a variable name, it tries the following lookups, in this order:
* Attribute lookup. Example: ``foo.bar``
* List-index lookup. Example: ``foo[bar]``
+Note that "bar" in a template expression like ``{{ foo.bar }}`` will be
+interpreted as a literal string and not using the value of the variable "bar",
+if one exists in the template context.
+
The template system uses the first lookup type that works. It's short-circuit
logic. Here are a few examples::
@@ -424,6 +428,10 @@ optional, third positional argument, ``processors``. In this example, the
my_data_dictionary,
context_instance=RequestContext(request))
+ Alternatively, use the :meth:`~django.shortcuts.render()` shortcut which is
+ the same as a call to :func:`~django.shortcuts.render_to_response()` with a
+ context_instance argument that forces the use of a ``RequestContext``.
+
Here's what each of the default processors does:
django.contrib.auth.context_processors.auth
diff --git a/docs/releases/1.3.2.txt b/docs/releases/1.3.2.txt
new file mode 100644
index 0000000000..16e00f9732
--- /dev/null
+++ b/docs/releases/1.3.2.txt
@@ -0,0 +1,14 @@
+==========================
+Django 1.3.2 release notes
+==========================
+
+*July 30, 2012*
+
+This is the second security release in the Django 1.3 series, fixing several
+security issues in Django 1.3. Django 1.3.2 is a recommended upgrade for
+all users of Django 1.3.
+
+For a full list of issues addressed in this release, see the `security
+advisory`_.
+
+.. _security advisory: https://www.djangoproject.com/weblog/2012/jul/30/security-releases-issued/
diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt
index 772dbdb2e7..c507f1bed6 100644
--- a/docs/releases/1.3.txt
+++ b/docs/releases/1.3.txt
@@ -587,7 +587,7 @@ gettext domain):
ones listed later.
* The ``locale`` subdirectory of the directory containing the settings, that
- usually coincides with and is know as the *project directory* is being
+ usually coincides with and is known as the *project directory* is being
deprecated in this release as a source of translations. (the precedence of
these translations is intermediate between applications and :setting:`LOCALE_PATHS`
translations). See the `corresponding deprecated features section`_
diff --git a/docs/releases/1.4.1.txt b/docs/releases/1.4.1.txt
new file mode 100644
index 0000000000..7c6568e5b5
--- /dev/null
+++ b/docs/releases/1.4.1.txt
@@ -0,0 +1,14 @@
+==========================
+Django 1.4.1 release notes
+==========================
+
+*July 30, 2012*
+
+This is the first security release in the Django 1.4 series, fixing several
+security issues in Django 1.4. Django 1.4.1 is a recommended upgrade for
+all users of Django 1.4.
+
+For a full list of issues addressed in this release, see the `security
+advisory`_.
+
+.. _security advisory: https://www.djangoproject.com/weblog/2012/jul/30/security-releases-issued/
diff --git a/docs/releases/1.4.2.txt b/docs/releases/1.4.2.txt
new file mode 100644
index 0000000000..6f2e9aca2e
--- /dev/null
+++ b/docs/releases/1.4.2.txt
@@ -0,0 +1,14 @@
+==========================
+Django 1.4.2 release notes
+==========================
+
+*TO BE RELEASED*
+
+This is the second security release in the Django 1.4 series.
+
+Backwards incompatible changes
+==============================
+
+* The newly introduced :class:`~django.db.models.GenericIPAddressField`
+ constructor arguments have been adapted to match those of all other model
+ fields. The first two keyword arguments are now verbose_name and name.
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
index 4f25919d79..5728d8559a 100644
--- a/docs/releases/1.5.txt
+++ b/docs/releases/1.5.txt
@@ -172,6 +172,54 @@ If you were using the ``data`` parameter in a PUT request without a
``content_type``, you must encode your data before passing it to the test
client and set the ``content_type`` argument.
+.. _simplejson-incompatibilities:
+
+System version of :mod:`simplejson` no longer used
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:ref:`As explained below `, Django 1.5 deprecates
+:mod:`django.utils.simplejson` in favor of Python 2.6's built-in :mod:`json`
+module. In theory, this change is harmless. Unfortunately, because of
+incompatibilities between versions of :mod:`simplejson`, it may trigger errors
+in some circumstances.
+
+JSON-related features in Django 1.4 always used :mod:`django.utils.simplejson`.
+This module was actually:
+
+- A system version of :mod:`simplejson`, if one was available (ie. ``import
+ simplejson`` works), if it was more recent than Django's built-in copy or it
+ had the C speedups, or
+- The :mod:`json` module from the standard library, if it was available (ie.
+ Python 2.6 or greater), or
+- A built-in copy of version 2.0.7 of :mod:`simplejson`.
+
+In Django 1.5, those features use Python's :mod:`json` module, which is based
+on version 2.0.9 of :mod:`simplejson`.
+
+There are no known incompatibilities between Django's copy of version 2.0.7 and
+Python's copy of version 2.0.9. However, there are some incompatibilities
+between other versions of :mod:`simplejson`:
+
+- While the :mod:`simplejson` API is documented as always returning unicode
+ strings, the optional C implementation can return a byte string. This was
+ fixed in Python 2.7.
+- :class:`simplejson.JSONEncoder` gained a ``namedtuple_as_object`` keyword
+ argument in version 2.2.
+
+More information on these incompatibilities is available in `ticket #18023`_.
+
+The net result is that, if you have installed :mod:`simplejson` and your code
+uses Django's serialization internals directly -- for instance
+:class:`django.core.serializers.json.DjangoJSONEncoder`, the switch from
+:mod:`simplejson` to :mod:`json` could break your code. (In general, changes to
+internals aren't documented; we're making an exception here.)
+
+At this point, the maintainers of Django believe that using :mod:`json` from
+the standard library offers the strongest guarantee of backwards-compatibility.
+They recommend to use it from now on.
+
+.. _ticket #18023: https://code.djangoproject.com/ticket/18023#comment:10
+
String types of hasher method parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -179,7 +227,7 @@ If you have written a :ref:`custom password hasher `,
your ``encode()``, ``verify()`` or ``safe_summary()`` methods should accept
Unicode parameters (``password``, ``salt`` or ``encoded``). If any of the
hashing methods need byte strings, you can use the
-:func:`~django.utils.encoding.smart_bytes` utility to encode the strings.
+:func:`~django.utils.encoding.force_bytes` utility to encode the strings.
Validation of previous_page_number and next_page_number
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -282,13 +330,21 @@ Miscellaneous
Features deprecated in 1.5
==========================
+.. _simplejson-deprecation:
+
``django.utils.simplejson``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since Django 1.5 drops support for Python 2.5, we can now rely on the
-:mod:`json` module being in Python's standard library -- so we've removed
-our own copy of ``simplejson``. You can safely change any use of
-:mod:`django.utils.simplejson` to :mod:`json`.
+:mod:`json` module being available in Python's standard library, so we've
+removed our own copy of :mod:`simplejson`. You should now import :mod:`json`
+instead :mod:`django.utils.simplejson`.
+
+Unfortunately, this change might have unwanted side-effects, because of
+incompatibilities between versions of :mod:`simplejson` -- see the
+:ref:`backwards-incompatible changes ` section.
+If you rely on features added to :mod:`simplejson` after it became Python's
+:mod:`json`, you should import :mod:`simplejson` explicitly.
``itercompat.product``
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index e38ab646d6..fa55a4d206 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -27,6 +27,8 @@ Final releases
.. toctree::
:maxdepth: 1
+ .. 1.4.2 (uncomment on release)
+ 1.4.1
1.4
1.3 release
@@ -34,6 +36,7 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.3.2
1.3.1
1.3
diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
index 99e36b34d7..29efc3f7e8 100644
--- a/docs/topics/auth.txt
+++ b/docs/topics/auth.txt
@@ -460,7 +460,7 @@ algorithm.
Increasing the work factor
~~~~~~~~~~~~~~~~~~~~~~~~~~
-The PDKDF2 and bcrypt algorithms use a number of iterations or rounds of
+The PBKDF2 and bcrypt algorithms use a number of iterations or rounds of
hashing. This deliberately slows down attackers, making attacks against hashed
passwords harder. However, as computing power increases, the number of
iterations needs to be increased. We've chosen a reasonable default (and will
@@ -468,7 +468,7 @@ increase it with each release of Django), but you may wish to tune it up or
down, depending on your security needs and available processing power. To do so,
you'll subclass the appropriate algorithm and override the ``iterations``
parameters. For example, to increase the number of iterations used by the
-default PDKDF2 algorithm:
+default PBKDF2 algorithm:
1. Create a subclass of ``django.contrib.auth.hashers.PBKDF2PasswordHasher``::
@@ -1170,24 +1170,25 @@ includes a few other useful built-in views located in
:file:`registration/password_reset_form.html` if not supplied.
* ``email_template_name``: The full name of a template to use for
- generating the email with the new password. Defaults to
+ generating the email with the reset password link. Defaults to
:file:`registration/password_reset_email.html` if not supplied.
* ``subject_template_name``: The full name of a template to use for
- the subject of the email with the new password. Defaults
+ the subject of the email with the reset password link. Defaults
to :file:`registration/password_reset_subject.txt` if not supplied.
.. versionadded:: 1.4
- * ``password_reset_form``: Form that will be used to set the password.
- Defaults to :class:`~django.contrib.auth.forms.PasswordResetForm`.
+ * ``password_reset_form``: Form that will be used to get the email of
+ the user to reset the password for. Defaults to
+ :class:`~django.contrib.auth.forms.PasswordResetForm`.
- * ``token_generator``: Instance of the class to check the password. This
- will default to ``default_token_generator``, it's an instance of
+ * ``token_generator``: Instance of the class to check the one time link.
+ This will default to ``default_token_generator``, it's an instance of
``django.contrib.auth.tokens.PasswordResetTokenGenerator``.
* ``post_reset_redirect``: The URL to redirect to after a successful
- password change.
+ password reset request.
* ``from_email``: A valid email address. By default Django uses
the :setting:`DEFAULT_FROM_EMAIL`.
@@ -1218,7 +1219,7 @@ includes a few other useful built-in views located in
* ``uid``: The user's id encoded in base 36.
- * ``token``: Token to check that the password is valid.
+ * ``token``: Token to check that the reset link is valid.
Sample ``registration/password_reset_email.html`` (email body template):
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 219b6c7795..f13238e342 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -864,7 +864,7 @@ key version to provide a final cache key. By default, the three parts
are joined using colons to produce a final string::
def make_key(key, key_prefix, version):
- return ':'.join([key_prefix, str(version), smart_bytes(key)])
+ return ':'.join([key_prefix, str(version), key])
If you want to combine the parts in different ways, or apply other
processing to the final key (e.g., taking a hash digest of the key
diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt
index c2900ecc01..6637bd5fcb 100644
--- a/docs/topics/class-based-views/index.txt
+++ b/docs/topics/class-based-views/index.txt
@@ -11,8 +11,7 @@ to structure your views and reuse code by harnessing inheritance and
mixins. There are also some generic views for simple tasks which we'll
get to later, but you may want to design your own structure of
reusable views which suits your use case. For full details, see the
-:doc:`class-based views reference
-documentation`.
+:doc:`class-based views reference documentation`.
.. toctree::
:maxdepth: 1
@@ -32,19 +31,35 @@ redirect, and :class:`~django.views.generic.base.TemplateView` extends the base
to make it also render a template.
-Simple usage
-============
+Simple usage in your URLconf
+============================
-Class-based generic views (and any class-based views that inherit from
-the base classes Django provides) can be configured in two
-ways: subclassing, or passing in arguments directly in the URLconf.
+The simplest way to use generic views is to create them directly in your
+URLconf. If you're only changing a few simple attributes on a class-based view,
+you can simply pass them into the ``as_view`` method call itself::
-When you subclass a class-based view, you can override attributes
-(such as the ``template_name``) or methods (such as ``get_context_data``)
-in your subclass to provide new values or methods. Consider, for example,
-a view that just displays one template, ``about.html``. Django has a
-generic view to do this - :class:`~django.views.generic.base.TemplateView` -
-so we can just subclass it, and override the template name::
+ from django.conf.urls import patterns, url, include
+ from django.views.generic import TemplateView
+
+ urlpatterns = patterns('',
+ (r'^about/', TemplateView.as_view(template_name="about.html")),
+ )
+
+Any arguments given will override the ``template_name`` on the
+A similar overriding pattern can be used for the ``url`` attribute on
+:class:`~django.views.generic.base.RedirectView`.
+
+
+Subclassing generic views
+=========================
+
+The second, more powerful way to use generic views is to inherit from an
+existing view and override attributes (such as the ``template_name``) or
+methods (such as ``get_context_data``) in your subclass to provide new values
+or methods. Consider, for example, a view that just displays one template,
+``about.html``. Django has a generic view to do this -
+:class:`~django.views.generic.base.TemplateView` - so we can just subclass it,
+and override the template name::
# some_app/views.py
from django.views.generic import TemplateView
@@ -52,9 +67,10 @@ so we can just subclass it, and override the template name::
class AboutView(TemplateView):
template_name = "about.html"
-Then, we just need to add this new view into our URLconf. As the class-based
-views themselves are classes, we point the URL to the ``as_view`` class method
-instead, which is the entry point for class-based views::
+Then we just need to add this new view into our URLconf.
+`~django.views.generic.base.TemplateView` is a class, not a function, so we
+point the URL to the ``as_view`` class method instead, which provides a
+function-like entry to class-based views::
# urls.py
from django.conf.urls import patterns, url, include
@@ -64,104 +80,6 @@ instead, which is the entry point for class-based views::
(r'^about/', AboutView.as_view()),
)
-Alternatively, if you're only changing a few simple attributes on a
-class-based view, you can simply pass the new attributes into the ``as_view``
-method call itself::
-
- from django.conf.urls import patterns, url, include
- from django.views.generic import TemplateView
-
- urlpatterns = patterns('',
- (r'^about/', TemplateView.as_view(template_name="about.html")),
- )
-
-A similar overriding pattern can be used for the ``url`` attribute on
-:class:`~django.views.generic.base.RedirectView`.
-
-.. _jsonresponsemixin-example:
-
-More than just HTML
--------------------
-
-Where class based views shine is when you want to do the same thing many times.
-Suppose you're writing an API, and every view should return JSON instead of
-rendered HTML.
-
-We can create a mixin class to use in all of our views, handling the
-conversion to JSON once.
-
-For example, a simple JSON mixin might look something like this::
-
- import json
- from django.http import HttpResponse
-
- class JSONResponseMixin(object):
- """
- A mixin that can be used to render a JSON response.
- """
- response_class = HttpResponse
-
- def render_to_response(self, context, **response_kwargs):
- """
- Returns a JSON response, transforming 'context' to make the payload.
- """
- response_kwargs['content_type'] = 'application/json'
- return self.response_class(
- self.convert_context_to_json(context),
- **response_kwargs
- )
-
- def convert_context_to_json(self, context):
- "Convert the context dictionary into a JSON object"
- # Note: This is *EXTREMELY* naive; in reality, you'll need
- # to do much more complex handling to ensure that arbitrary
- # objects -- such as Django model instances or querysets
- # -- can be serialized as JSON.
- return json.dumps(context)
-
-Now we mix this into the base TemplateView::
-
- from django.views.generic import TemplateView
-
- class JSONView(JSONResponseMixin, TemplateView):
- pass
-
-Equally we could use our mixin with one of the generic views. We can make our
-own version of :class:`~django.views.generic.detail.DetailView` by mixing
-:class:`JSONResponseMixin` with the
-:class:`~django.views.generic.detail.BaseDetailView` -- (the
-:class:`~django.views.generic.detail.DetailView` before template
-rendering behavior has been mixed in)::
-
- class JSONDetailView(JSONResponseMixin, BaseDetailView):
- pass
-
-This view can then be deployed in the same way as any other
-:class:`~django.views.generic.detail.DetailView`, with exactly the
-same behavior -- except for the format of the response.
-
-If you want to be really adventurous, you could even mix a
-:class:`~django.views.generic.detail.DetailView` subclass that is able
-to return *both* HTML and JSON content, depending on some property of
-the HTTP request, such as a query argument or a HTTP header. Just mix
-in both the :class:`JSONResponseMixin` and a
-:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
-and override the implementation of :func:`render_to_response()` to defer
-to the appropriate subclass depending on the type of response that the user
-requested::
-
- class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
- def render_to_response(self, context):
- # Look for a 'format=json' GET argument
- if self.request.GET.get('format','html') == 'json':
- return JSONResponseMixin.render_to_response(self, context)
- else:
- return SingleObjectTemplateResponseMixin.render_to_response(self, context)
-
-Because of the way that Python resolves method overloading, the local
-``render_to_response()`` implementation will override the versions provided by
-:class:`JSONResponseMixin` and
-:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
For more information on how to use the built in generic views, consult the next
topic on :doc:`generic class based views`.
@@ -171,16 +89,15 @@ Decorating class-based views
.. highlightlang:: python
-The extension of class-based views isn't limited to using mixins. You
-can use also use decorators.
+Since class-based views aren't functions, decorating them works differently
+depending on if you're using ``as_view`` or creating a subclass.
Decorating in URLconf
---------------------
The simplest way of decorating class-based views is to decorate the
result of the :meth:`~django.views.generic.base.View.as_view` method.
-The easiest place to do this is in the URLconf where you deploy your
-view::
+The easiest place to do this is in the URLconf where you deploy your view::
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt
index 0c19d60e35..f07769fb8a 100644
--- a/docs/topics/class-based-views/mixins.txt
+++ b/docs/topics/class-based-views/mixins.txt
@@ -69,7 +69,7 @@ interface to working with templates in class-based views.
add more members to the dictionary.
Building up Django's generic class-based views
-===============================================
+==============================================
Let's look at how two of Django's generic class-based views are built
out of mixins providing discrete functionality. We'll consider
@@ -222,8 +222,7 @@ we'll want the functionality provided by
:class:`~django.views.generic.detail.SingleObjectMixin`.
We'll demonstrate this with the publisher modelling we used in the
-:doc:`generic class-based views
-introduction`.
+:doc:`generic class-based views introduction`.
.. code-block:: python
@@ -233,11 +232,11 @@ introduction`.
from django.views.generic import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
-
+
class RecordInterest(View, SingleObjectMixin):
"""Records the current user's interest in an author."""
model = Author
-
+
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseForbidden()
@@ -256,9 +255,7 @@ we're interested in, which it just does with a simple call to
``self.get_object()``. Everything else is taken care of for us by the
mixin.
-We can hook this into our URLs easily enough:
-
-.. code-block:: python
+We can hook this into our URLs easily enough::
# urls.py
from books.views import RecordInterest
@@ -294,8 +291,6 @@ object. In order to do this, we need to have two different querysets:
We'll figure that out ourselves in :meth:`get_queryset()` so we
can take into account the Publisher we're looking at.
-.. highlightlang:: python
-
.. note::
We have to think carefully about :meth:`get_context_data()`.
@@ -311,15 +306,15 @@ Now we can write a new :class:`PublisherDetail`::
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
-
+
class PublisherDetail(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
-
+
def get_context_data(self, **kwargs):
kwargs['publisher'] = self.object
return super(PublisherDetail, self).get_context_data(**kwargs)
-
+
def get_queryset(self):
self.object = self.get_object(Publisher.objects.all())
return self.object.book_set.all()
@@ -339,26 +334,26 @@ have to create lots of books to see the pagination working! Here's the
template you'd want to use::
{% extends "base.html" %}
-
+
{% block content %}
Publisher {{ publisher.name }}
-
+
{% for book in page_obj %}
{{ book.title }}
{% endfor %}
-
+
{% if page_obj.has_previous %}
previous
{% endif %}
-
+
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
-
+
{% if page_obj.has_next %}
next
{% endif %}
@@ -428,9 +423,9 @@ code so that on ``POST`` the form gets called appropriately.
the views implement :meth:`get()`, and things would get much more
confusing.
-Our new :class:`AuthorDetail` looks like this:
+.. highlightlang:: python
-.. code-block:: python
+Our new :class:`AuthorDetail` looks like this::
# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
@@ -455,7 +450,7 @@ Our new :class:`AuthorDetail` looks like this:
'author-detail',
kwargs = {'pk': self.object.pk},
)
-
+
def get_context_data(self, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
@@ -464,7 +459,7 @@ Our new :class:`AuthorDetail` looks like this:
}
context.update(kwargs)
return super(AuthorDetail, self).get_context_data(**context)
-
+
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
@@ -472,14 +467,14 @@ Our new :class:`AuthorDetail` looks like this:
return self.form_valid(form)
else:
return self.form_invalid(form)
-
+
def form_valid(self, form):
if not self.request.user.is_authenticated():
return HttpResponseForbidden()
self.object = self.get_object()
# record the interest using the message in form.cleaned_data
return super(AuthorDetail, self).form_valid(form)
-
+
:meth:`get_success_url()` is just providing somewhere to redirect to,
which gets used in the default implementation of
:meth:`form_valid()`. We have to provide our own :meth:`post()` as
@@ -525,21 +520,21 @@ write our own :meth:`get_context_data()` to make the
from django.views.generic import DetailView
from django import forms
from books.models import Author
-
+
class AuthorInterestForm(forms.Form):
message = forms.CharField()
-
+
class AuthorDisplay(DetailView):
-
+
queryset = Author.objects.all()
-
+
def get_context_data(self, **kwargs):
context = {
'form': AuthorInterestForm(),
}
context.update(kwargs)
return super(AuthorDisplay, self).get_context_data(**context)
-
+
Then the :class:`AuthorInterest` is a simple :class:`FormView`, but we
have to bring in :class:`SingleObjectMixin` so we can find the author
we're talking about, and we have to remember to set
@@ -550,7 +545,7 @@ template as :class:`AuthorDisplay` is using on ``GET``.
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
-
+
class AuthorInterest(FormView, SingleObjectMixin):
template_name = 'books/author_detail.html'
form_class = AuthorInterestForm
@@ -561,13 +556,13 @@ template as :class:`AuthorDisplay` is using on ``GET``.
'object': self.get_object(),
}
return super(AuthorInterest, self).get_context_data(**context)
-
+
def get_success_url(self):
return reverse(
'author-detail',
kwargs = {'pk': self.object.pk},
)
-
+
def form_valid(self, form):
if not self.request.user.is_authenticated():
return HttpResponseForbidden()
@@ -588,13 +583,13 @@ using a different template.
.. code-block:: python
from django.views.generic import View
-
+
class AuthorDetail(View):
-
+
def get(self, request, *args, **kwargs):
view = AuthorDisplay.as_view()
return view(request, *args, **kwargs)
-
+
def post(self, request, *args, **kwargs):
view = AuthorInterest.as_view()
return view(request, *args, **kwargs)
@@ -603,3 +598,88 @@ This approach can also be used with any other generic class-based
views or your own class-based views inheriting directly from
:class:`View` or :class:`TemplateView`, as it keeps the different
views as separate as possible.
+
+.. _jsonresponsemixin-example:
+
+More than just HTML
+===================
+
+Where class based views shine is when you want to do the same thing many times.
+Suppose you're writing an API, and every view should return JSON instead of
+rendered HTML.
+
+We can create a mixin class to use in all of our views, handling the
+conversion to JSON once.
+
+For example, a simple JSON mixin might look something like this::
+
+ import json
+ from django.http import HttpResponse
+
+ class JSONResponseMixin(object):
+ """
+ A mixin that can be used to render a JSON response.
+ """
+ response_class = HttpResponse
+
+ def render_to_response(self, context, **response_kwargs):
+ """
+ Returns a JSON response, transforming 'context' to make the payload.
+ """
+ response_kwargs['content_type'] = 'application/json'
+ return self.response_class(
+ self.convert_context_to_json(context),
+ **response_kwargs
+ )
+
+ def convert_context_to_json(self, context):
+ "Convert the context dictionary into a JSON object"
+ # Note: This is *EXTREMELY* naive; in reality, you'll need
+ # to do much more complex handling to ensure that arbitrary
+ # objects -- such as Django model instances or querysets
+ # -- can be serialized as JSON.
+ return json.dumps(context)
+
+Now we mix this into the base TemplateView::
+
+ from django.views.generic import TemplateView
+
+ class JSONView(JSONResponseMixin, TemplateView):
+ pass
+
+Equally we could use our mixin with one of the generic views. We can make our
+own version of :class:`~django.views.generic.detail.DetailView` by mixing
+:class:`JSONResponseMixin` with the
+:class:`~django.views.generic.detail.BaseDetailView` -- (the
+:class:`~django.views.generic.detail.DetailView` before template
+rendering behavior has been mixed in)::
+
+ class JSONDetailView(JSONResponseMixin, BaseDetailView):
+ pass
+
+This view can then be deployed in the same way as any other
+:class:`~django.views.generic.detail.DetailView`, with exactly the
+same behavior -- except for the format of the response.
+
+If you want to be really adventurous, you could even mix a
+:class:`~django.views.generic.detail.DetailView` subclass that is able
+to return *both* HTML and JSON content, depending on some property of
+the HTTP request, such as a query argument or a HTTP header. Just mix
+in both the :class:`JSONResponseMixin` and a
+:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
+and override the implementation of :func:`render_to_response()` to defer
+to the appropriate subclass depending on the type of response that the user
+requested::
+
+ class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
+ def render_to_response(self, context):
+ # Look for a 'format=json' GET argument
+ if self.request.GET.get('format','html') == 'json':
+ return JSONResponseMixin.render_to_response(self, context)
+ else:
+ return SingleObjectTemplateResponseMixin.render_to_response(self, context)
+
+Because of the way that Python resolves method overloading, the local
+``render_to_response()`` implementation will override the versions provided by
+:class:`JSONResponseMixin` and
+:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
index ce15dc9535..f29cc28332 100644
--- a/docs/topics/db/models.txt
+++ b/docs/topics/db/models.txt
@@ -108,8 +108,8 @@ determine a few things:
* The database column type (e.g. ``INTEGER``, ``VARCHAR``).
-* The :doc:`widget ` to use in Django's admin interface,
- if you care to use it (e.g. ````, ``