Merged master changes.

This commit is contained in:
Russell Keith-Magee 2012-09-07 23:45:03 +08:00
commit 7e82e83d67
146 changed files with 2278 additions and 875 deletions

View File

@ -1,4 +1,4 @@
include README include README.rst
include AUTHORS include AUTHORS
include INSTALL include INSTALL
include LICENSE include LICENSE

View File

@ -152,17 +152,25 @@ class UserSettingsHolder(BaseSettings):
Requests for configuration variables not in this class are satisfied Requests for configuration variables not in this class are satisfied
from the module specified in default_settings (if possible). from the module specified in default_settings (if possible).
""" """
self.__dict__['_deleted'] = set()
self.default_settings = default_settings self.default_settings = default_settings
def __getattr__(self, name): def __getattr__(self, name):
if name in self._deleted:
raise AttributeError
return getattr(self.default_settings, name) 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): def __dir__(self):
return list(self.__dict__) + dir(self.default_settings) return list(self.__dict__) + dir(self.default_settings)
# For Python < 2.6:
__members__ = property(lambda self: self.__dir__())
settings = LazySettings() settings = LazySettings()

View File

@ -13,10 +13,11 @@ DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '', # Or path to database file if using sqlite3. 'NAME': '', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3. # The following settings are not used with sqlite3:
'PASSWORD': '', # Not used with sqlite3. 'USER': '',
'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 'PASSWORD': '',
'PORT': '', # Set to empty string for default. Not used with sqlite3. 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
'PORT': '', # Set to empty string for default.
} }
} }

View File

@ -29,7 +29,7 @@
{% if change %}{% if not is_popup %} {% if change %}{% if not is_popup %}
<ul class="object-tools"> <ul class="object-tools">
{% block object-tools-items %} {% block object-tools-items %}
<li><a href="history/" class="historylink">{% trans "History" %}</a></li> <li><a href="{% url opts|admin_urlname:'history' original.pk %}" class="historylink">{% trans "History" %}</a></li>
{% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%} {% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
{% endblock %} {% endblock %}
</ul> </ul>

View File

@ -12,7 +12,7 @@ from django.utils import formats
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils import timezone 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 import six
from django.utils.translation import ungettext from django.utils.translation import ungettext
from django.core.urlresolvers import reverse 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) label = force_text(model._meta.verbose_name)
attr = six.text_type attr = six.text_type
elif name == "__str__": elif name == "__str__":
label = smart_bytes(model._meta.verbose_name) label = force_str(model._meta.verbose_name)
attr = bytes attr = bytes
else: else:
if callable(name): if callable(name):

View File

@ -6,7 +6,7 @@ from django.core.paginator import InvalidPage
from django.db import models from django.db import models
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
from django.utils.datastructures import SortedDict 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.translation import ugettext, ugettext_lazy
from django.utils.http import urlencode from django.utils.http import urlencode
@ -94,7 +94,7 @@ class ChangeList(object):
# 'key' will be used as a keyword argument later, so Python # 'key' will be used as a keyword argument later, so Python
# requires it to be a string. # requires it to be a string.
del lookup_params[key] 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): if not self.model_admin.lookup_allowed(key, value):
raise SuspiciousOperation("Filtering by %s not allowed" % key) raise SuspiciousOperation("Filtering by %s not allowed" % key)

View File

@ -6,7 +6,7 @@ from email.errors import HeaderParseError
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.encoding import smart_bytes from django.utils.encoding import force_bytes
try: try:
import docutils.core import docutils.core
import docutils.nodes 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('/') "link_base" : reverse('django-admindocs-docroot').rstrip('/')
} }
if thing_being_parsed: 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, parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
destination_path=None, writer_name='html', destination_path=None, writer_name='html',
settings_overrides=overrides) settings_overrides=overrides)

View File

@ -8,7 +8,7 @@ from django.conf import settings
from django.test.signals import setting_changed from django.test.signals import setting_changed
from django.utils import importlib from django.utils import importlib
from django.utils.datastructures import SortedDict 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.core.exceptions import ImproperlyConfigured
from django.utils.crypto import ( from django.utils.crypto import (
pbkdf2, constant_time_compare, get_random_string) pbkdf2, constant_time_compare, get_random_string)
@ -299,7 +299,7 @@ class SHA1PasswordHasher(BasePasswordHasher):
def encode(self, password, salt): def encode(self, password, salt):
assert password assert password
assert salt and '$' not in salt 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) return "%s$%s$%s" % (self.algorithm, salt, hash)
def verify(self, password, encoded): def verify(self, password, encoded):
@ -327,7 +327,7 @@ class MD5PasswordHasher(BasePasswordHasher):
def encode(self, password, salt): def encode(self, password, salt):
assert password assert password
assert salt and '$' not in salt 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) return "%s$%s$%s" % (self.algorithm, salt, hash)
def verify(self, password, encoded): def verify(self, password, encoded):
@ -361,7 +361,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
return '' return ''
def encode(self, password, salt): def encode(self, password, salt):
return hashlib.md5(smart_bytes(password)).hexdigest() return hashlib.md5(force_bytes(password)).hexdigest()
def verify(self, password, encoded): def verify(self, password, encoded):
encoded_2 = self.encode(password, '') encoded_2 = self.encode(password, '')

View File

@ -10,6 +10,7 @@ import unicodedata
from django.contrib.auth import models as auth_app, get_user_model from django.contrib.auth import models as auth_app, get_user_model
from django.core import exceptions from django.core import exceptions
from django.db.models import get_models, signals from django.db.models import get_models, signals
from django.utils import six
from django.utils.six.moves import input 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 :returns: The username as a unicode string, or an empty string if the
username could not be determined. username could not be determined.
""" """
default_locale = locale.getdefaultlocale()[1]
if default_locale:
try: try:
return getpass.getuser().decode(default_locale) result = getpass.getuser()
except (ImportError, KeyError, UnicodeDecodeError): except (ImportError, KeyError):
# KeyError will be raised by os.getpwuid() (called by getuser()) # KeyError will be raised by os.getpwuid() (called by getuser())
# if there is no corresponding entry in the /etc/passwd file # if there is no corresponding entry in the /etc/passwd file
# (a very restricted chroot environment, for example). # (a very restricted chroot environment, for example).
# UnicodeDecodeError - preventive treatment for non-latin Windows.
pass
return '' return ''
if not six.PY3:
default_locale = locale.getdefaultlocale()[1]
if not default_locale:
return ''
try:
result = result.decode(default_locale)
except UnicodeDecodeError:
# UnicodeDecodeError - preventive treatment for non-latin Windows.
return ''
return result
def get_default_username(check_db=True): def get_default_username(check_db=True):

View File

@ -9,16 +9,20 @@ from django.core.management import call_command
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils import six
from django.utils.six import StringIO from django.utils.six import StringIO
class GetDefaultUsernameTestCase(TestCase): class GetDefaultUsernameTestCase(TestCase):
def setUp(self): def setUp(self):
self._getpass_getuser = management.get_system_username self.old_get_system_username = management.get_system_username
def tearDown(self): 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): def test_simple(self):
management.get_system_username = lambda: 'joe' management.get_system_username = lambda: 'joe'

View File

@ -7,7 +7,7 @@ from __future__ import unicode_literals
from django.db import models from django.db import models
from django.utils import formats from django.utils import formats
from django.utils.text import capfirst 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.db.models.query import QuerySet
from django.utils.encoding import python_2_unicode_compatible 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 self.verbose_name_plural = model._meta.verbose_name_plural
def __repr__(self): def __repr__(self):
return smart_str('<EasyModel for %s>' % self.model._meta.object_name) return force_str('<EasyModel for %s>' % self.model._meta.object_name)
def model_databrowse(self): def model_databrowse(self):
"Returns the ModelDatabrowse class for this model." "Returns the ModelDatabrowse class for this model."
@ -62,7 +62,7 @@ class EasyField(object):
self.model, self.field = easy_model, field self.model, self.field = easy_model, field
def __repr__(self): def __repr__(self):
return smart_str('<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name)) return force_str('<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def choices(self): def choices(self):
for value, label in self.field.choices: for value, label in self.field.choices:
@ -80,7 +80,7 @@ class EasyChoice(object):
self.value, self.label = value, label self.value, self.label = value, label
def __repr__(self): def __repr__(self):
return smart_str('<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name)) return force_str('<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def url(self): 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)) 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 self.model, self.instance = easy_model, instance
def __repr__(self): def __repr__(self):
return smart_str('<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val())) return force_str('<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val()))
def __str__(self): def __str__(self):
val = smart_text(self.instance) val = smart_text(self.instance)
@ -135,7 +135,7 @@ class EasyInstanceField(object):
self.raw_value = getattr(instance.instance, field.name) self.raw_value = getattr(instance.instance, field.name)
def __repr__(self): def __repr__(self):
return smart_str('<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name)) return force_str('<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def values(self): def values(self):
""" """

View File

@ -1,16 +1,15 @@
from django.db import connections from django.db import connections
from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet 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 import aggregates
from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField 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.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery
from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Area, Distance from django.contrib.gis.measure import Area, Distance
from django.utils import six
from django.utils import six from django.utils import six
class GeoQuerySet(QuerySet): class GeoQuerySet(QuerySet):
"The Geographic QuerySet." "The Geographic QuerySet."

View File

@ -8,7 +8,6 @@ from django.core.paginator import EmptyPage, PageNotAnInteger
from django.contrib.gis.db.models.fields import GeometryField from django.contrib.gis.db.models.fields import GeometryField
from django.db import connections, DEFAULT_DB_ALIAS from django.db import connections, DEFAULT_DB_ALIAS
from django.db.models import get_model from django.db.models import get_model
from django.utils.encoding import smart_bytes
from django.utils import six from django.utils import six
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -61,7 +60,7 @@ def sitemap(request, sitemaps, section=None):
raise Http404(_("Page %s empty") % page) raise Http404(_("Page %s empty") % page)
except PageNotAnInteger: except PageNotAnInteger:
raise Http404(_("No page '%s'") % page) 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') return HttpResponse(xml, content_type='application/xml')
def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS): def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS):

View File

@ -186,6 +186,15 @@ class GeoModelTest(TestCase):
self.assertEqual(1, qs.count()) self.assertEqual(1, qs.count())
for pc in qs: self.assertEqual(32128, pc.point.srid) 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): class GeoLookupTest(TestCase):

View File

@ -13,7 +13,7 @@ markup syntaxes to HTML; currently there is support for:
from django import template from django import template
from django.conf import settings 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 from django.utils.safestring import mark_safe
register = template.Library() register = template.Library()
@ -27,7 +27,7 @@ def textile(value):
raise template.TemplateSyntaxError("Error in 'textile' filter: The Python textile library isn't installed.") raise template.TemplateSyntaxError("Error in 'textile' filter: The Python textile library isn't installed.")
return force_text(value) return force_text(value)
else: 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) @register.filter(is_safe=True)
def markdown(value, arg=''): def markdown(value, arg=''):
@ -80,5 +80,5 @@ def restructuredtext(value):
return force_text(value) return force_text(value)
else: else:
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {}) 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"])) return mark_safe(force_text(parts["fragment"]))

View File

@ -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 get_random_string
from django.utils.crypto import salted_hmac from django.utils.crypto import salted_hmac
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import smart_bytes from django.utils.encoding import force_bytes
class CreateError(Exception): class CreateError(Exception):
""" """
@ -84,7 +84,7 @@ class SessionBase(object):
return base64.b64encode(hash.encode() + b":" + pickled).decode('ascii') return base64.b64encode(hash.encode() + b":" + pickled).decode('ascii')
def decode(self, session_data): def decode(self, session_data):
encoded_data = base64.b64decode(smart_bytes(session_data)) encoded_data = base64.b64decode(force_bytes(session_data))
try: try:
# could produce ValueError if there is no ':' # could produce ValueError if there is no ':'
hash, pickled = encoded_data.split(b':', 1) hash, pickled = encoded_data.split(b':', 1)

View File

@ -192,7 +192,7 @@ Type 'yes' to continue, or 'no' to cancel: """
def clear_dir(self, path): 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) dirs, files = self.storage.listdir(path)
for f in files: for f in files:

View File

@ -16,7 +16,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage, get_storage_class from django.core.files.storage import FileSystemStorage, get_storage_class
from django.utils.datastructures import SortedDict 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.functional import LazyObject
from django.utils.importlib import import_module from django.utils.importlib import import_module
@ -118,7 +118,7 @@ class CachedFilesMixin(object):
return urlunsplit(unparsed_name) return urlunsplit(unparsed_name)
def cache_key(self, 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): def url(self, name, force=False):
""" """
@ -254,7 +254,7 @@ class CachedFilesMixin(object):
if hashed_file_exists: if hashed_file_exists:
self.delete(hashed_name) self.delete(hashed_name)
# then save the processed result # 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) saved_name = self._save(hashed_name, content_file)
hashed_name = force_text(saved_name.replace('\\', '/')) hashed_name = force_text(saved_name.replace('\\', '/'))
processed = True processed = True

View File

@ -12,7 +12,7 @@ from django.conf import settings
from django.core.cache.backends.base import BaseCache from django.core.cache.backends.base import BaseCache
from django.db import connections, router, transaction, DatabaseError from django.db import connections, router, transaction, DatabaseError
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import smart_bytes from django.utils.encoding import force_bytes
class Options(object): class Options(object):
@ -73,7 +73,7 @@ class DatabaseCache(BaseDatabaseCache):
transaction.commit_unless_managed(using=db) transaction.commit_unless_managed(using=db)
return default return default
value = connections[db].ops.process_clob(row[1]) 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): def set(self, key, value, timeout=None, version=None):
key = self.make_key(key, version=version) key = self.make_key(key, version=version)

View File

@ -10,7 +10,7 @@ except ImportError:
import pickle import pickle
from django.core.cache.backends.base import BaseCache 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): class FileBasedCache(BaseCache):
def __init__(self, dir, params): def __init__(self, dir, params):
@ -137,7 +137,7 @@ class FileBasedCache(BaseCache):
Thus, a cache key of "foo" gets turnned into a file named Thus, a cache key of "foo" gets turnned into a file named
``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``. ``{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:]) path = os.path.join(path[:2], path[2:4], path[4:])
return os.path.join(self._dir, path) return os.path.join(self._dir, path)

View File

@ -6,7 +6,7 @@ from threading import local
from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
from django.utils import six from django.utils import six
from django.utils.encoding import smart_str from django.utils.encoding import force_str
class BaseMemcachedCache(BaseCache): class BaseMemcachedCache(BaseCache):
def __init__(self, server, params, library, value_not_found_exception): def __init__(self, server, params, library, value_not_found_exception):
@ -53,7 +53,7 @@ class BaseMemcachedCache(BaseCache):
def make_key(self, key, version=None): def make_key(self, key, version=None):
# Python 2 memcache requires the key to be a byte string. # 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): def add(self, key, value, timeout=0, version=None):
key = self.make_key(key, version=version) key = self.make_key(key, version=version)

View File

@ -1,10 +1,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os 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.core.files.utils import FileProxyMixin
from django.utils import six
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible @python_2_unicode_compatible
@ -132,7 +133,8 @@ class ContentFile(File):
""" """
def __init__(self, content, name=None): def __init__(self, content, name=None):
content = content or b'' 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) self.size = len(content)
def __str__(self): def __str__(self):

View File

@ -195,10 +195,17 @@ class FileSystemStorage(Storage):
fd = os.open(full_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0)) fd = os.open(full_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0))
try: try:
locks.lock(fd, locks.LOCK_EX) locks.lock(fd, locks.LOCK_EX)
_file = None
for chunk in content.chunks(): 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: finally:
locks.unlock(fd) locks.unlock(fd)
if _file is not None:
_file.close()
else:
os.close(fd) os.close(fd)
except OSError as e: except OSError as e:
if e.errno == errno.EEXIST: if e.errno == errno.EEXIST:

View File

@ -8,7 +8,7 @@ from io import BytesIO
from django.conf import settings from django.conf import settings
from django.core.files.base import File from django.core.files.base import File
from django.core.files import temp as tempfile 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', __all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile',
'SimpleUploadedFile') 'SimpleUploadedFile')
@ -30,7 +30,7 @@ class UploadedFile(File):
self.charset = charset self.charset = charset
def __repr__(self): def __repr__(self):
return smart_str("<%s: %s (%s)>" % ( return force_str("<%s: %s (%s)>" % (
self.__class__.__name__, self.name, self.content_type)) self.__class__.__name__, self.name, self.content_type))
def _get_name(self): def _get_name(self):

View File

@ -9,7 +9,7 @@ from django.core import signals
from django.core.handlers import base from django.core.handlers import base
from django.core.urlresolvers import set_script_prefix from django.core.urlresolvers import set_script_prefix
from django.utils import datastructures 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 from django.utils.log import getLogger
logger = getLogger('django.request') logger = getLogger('django.request')
@ -246,5 +246,5 @@ class WSGIHandler(base.BaseHandler):
response_headers = [(str(k), str(v)) for k, v in response.items()] response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values(): for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header='')))) 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 return response

View File

@ -12,7 +12,7 @@ import traceback
import django import django
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import color_style 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 from django.utils.six import StringIO
@ -65,7 +65,7 @@ class OutputWrapper(object):
msg += ending msg += ending
style_func = [f for f in (style_func, self.style_func, lambda x:x) style_func = [f for f in (style_func, self.style_func, lambda x:x)
if f is not None][0] 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): class BaseCommand(object):

View File

@ -1,4 +1,7 @@
from __future__ import unicode_literals
import keyword import keyword
import re
from optparse import make_option from optparse import make_option
from django.core.management.base import NoArgsCommand, CommandError from django.core.management.base import NoArgsCommand, CommandError
@ -31,6 +34,7 @@ class Command(NoArgsCommand):
table_name_filter = options.get('table_name_filter') table_name_filter = options.get('table_name_filter')
table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') 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() cursor = connection.cursor()
yield "# This is an auto-generated Django model module." yield "# This is an auto-generated Django model module."
@ -41,6 +45,7 @@ class Command(NoArgsCommand):
yield "#" yield "#"
yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'" yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'"
yield "# into your database." yield "# into your database."
yield "from __future__ import unicode_literals"
yield '' yield ''
yield 'from %s import models' % self.db_module yield 'from %s import models' % self.db_module
yield '' yield ''
@ -59,16 +64,19 @@ class Command(NoArgsCommand):
indexes = connection.introspection.get_indexes(cursor, table_name) indexes = connection.introspection.get_indexes(cursor, table_name)
except NotImplementedError: except NotImplementedError:
indexes = {} 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)): 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. comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
extra_params = {} # Holds Field parameters such as 'db_column'. 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 att_name, params, notes = self.normalize_col_name(
# attribute, set the "db_column" for this Field. column_name, used_column_names, is_relation)
if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name: extra_params.update(params)
extra_params['db_column'] = column_name comment_notes.extend(notes)
used_column_names.append(att_name)
# Add primary_key and unique, if necessary. # Add primary_key and unique, if necessary.
if column_name in indexes: if column_name in indexes:
@ -77,30 +85,12 @@ class Command(NoArgsCommand):
elif indexes[column_name]['unique']: elif indexes[column_name]['unique']:
extra_params['unique'] = True extra_params['unique'] = True
# Modify the field name to make it Python-compatible. if is_relation:
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:
rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1]) rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
if rel_to in known_models: if rel_to in known_models:
field_type = 'ForeignKey(%s' % rel_to field_type = 'ForeignKey(%s' % rel_to
else: else:
field_type = "ForeignKey('%s'" % rel_to field_type = "ForeignKey('%s'" % rel_to
if att_name.endswith('_id'):
att_name = att_name[:-3]
else:
extra_params['db_column'] = column_name
else: else:
# Calling `get_field_type` to get the field type string and any # Calling `get_field_type` to get the field type string and any
# additional paramters and notes. # additional paramters and notes.
@ -110,16 +100,6 @@ class Command(NoArgsCommand):
field_type += '(' 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 # Don't output 'id = meta.AutoField(primary_key=True)', because
# that's assumed if it doesn't exist. # that's assumed if it doesn't exist.
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}: 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 extra_params:
if not field_desc.endswith('('): if not field_desc.endswith('('):
field_desc += ', ' 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 += ')' field_desc += ')'
if comment_notes: if comment_notes:
field_desc += ' # ' + ' '.join(comment_notes) field_desc += ' # ' + ' '.join(comment_notes)
@ -144,6 +126,62 @@ class Command(NoArgsCommand):
for meta_line in self.get_meta(table_name): for meta_line in self.get_meta(table_name):
yield meta_line 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): def get_field_type(self, connection, table_name, row):
""" """
Given the database connection, the table name, and the cursor 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 construct the inner Meta class for the model corresponding
to the given database table name. to the given database table name.
""" """
return [' class Meta:', return [" class Meta:",
' db_table = %r' % table_name, " db_table = '%s'" % table_name,
''] ""]

View File

@ -196,6 +196,10 @@ class Command(BaseCommand):
loaded_object_count += loaded_objects_in_fixture loaded_object_count += loaded_objects_in_fixture
fixture_object_count += objects_in_fixture fixture_object_count += objects_in_fixture
label_found = True label_found = True
except Exception as e:
if not isinstance(e, CommandError):
e.args = ("Problem installing fixture '%s': %s" % (full_path, e),)
raise
finally: finally:
fixture.close() fixture.close()
@ -209,7 +213,11 @@ class Command(BaseCommand):
# Since we disabled constraint checks, we must manually check for # Since we disabled constraint checks, we must manually check for
# any invalid keys that might have been added # any invalid keys that might have been added
table_names = [model._meta.db_table for model in models] table_names = [model._meta.db_table for model in models]
try:
connection.check_constraints(table_names=table_names) connection.check_constraints(table_names=table_names)
except Exception as e:
e.args = ("Problem installing fixtures: %s" % e,)
raise
except (SystemExit, KeyboardInterrupt): except (SystemExit, KeyboardInterrupt):
raise raise
@ -217,8 +225,6 @@ class Command(BaseCommand):
if commit: if commit:
transaction.rollback(using=using) transaction.rollback(using=using)
transaction.leave_transaction_management(using=using) transaction.leave_transaction_management(using=using)
if not isinstance(e, CommandError):
e.args = ("Problem installing fixture '%s': %s" % (full_path, e),)
raise raise
# If we found even one object in a fixture, we need to reset the # If we found even one object in a fixture, we need to reset the

View File

@ -47,31 +47,27 @@ def _popen(cmd):
output, errors = p.communicate() output, errors = p.communicate()
return output, errors, p.returncode return output, errors, p.returncode
def walk(root, topdown=True, onerror=None, followlinks=False, def find_files(root, ignore_patterns, verbosity, stdout=sys.stdout, symlinks=False):
ignore_patterns=None, verbosity=0, stdout=sys.stdout):
""" """
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 dir_suffix = '%s*' % os.sep
norm_patterns = [p[:-len(dir_suffix)] if p.endswith(dir_suffix) else p for p in ignore_patterns] 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): all_files = []
remove_dirs = [] for dirpath, dirnames, filenames in os.walk(root, topdown=True, followlinks=symlinks):
for dirname in dirnames: for dirname in dirnames[:]:
if is_ignored(os.path.normpath(os.path.join(dirpath, dirname)), norm_patterns): 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) dirnames.remove(dirname)
if verbosity > 1: if verbosity > 1:
stdout.write('ignoring directory %s\n' % dirname) stdout.write('ignoring directory %s\n' % dirname)
yield (dirpath, dirnames, filenames) for filename in filenames:
if followlinks: if is_ignored(os.path.normpath(os.path.join(dirpath, filename)), ignore_patterns):
for d in dirnames: if verbosity > 1:
p = os.path.join(dirpath, d) stdout.write('ignoring file %s in %s\n' % (filename, dirpath))
if os.path.islink(p): else:
for link_dirpath, link_dirnames, link_filenames in walk(p): all_files.extend([(dirpath, filename)])
yield (link_dirpath, link_dirnames, link_filenames) all_files.sort()
return all_files
def is_ignored(path, ignore_patterns): def is_ignored(path, ignore_patterns):
""" """
@ -82,23 +78,6 @@ def is_ignored(path, ignore_patterns):
return True return True
return False 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): def copy_plural_forms(msgs, locale, domain, verbosity, stdout=sys.stdout):
""" """
Copies plural forms header contents from a Django catalog of locale to Copies plural forms header contents from a Django catalog of locale to

View File

@ -80,14 +80,14 @@ class Command(NoArgsCommand):
readline.parse_and_bind("tab:complete") readline.parse_and_bind("tab:complete")
# We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system # 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: if not use_plain:
pythonrc = os.environ.get("PYTHONSTARTUP") for pythonrc in (os.environ.get("PYTHONSTARTUP"),
os.path.expanduser('~/.pythonrc.py')):
if pythonrc and os.path.isfile(pythonrc): if pythonrc and os.path.isfile(pythonrc):
try: try:
execfile(pythonrc) with open(pythonrc) as handle:
exec(compile(handle.read(), pythonrc, 'exec'))
except NameError: except NameError:
pass pass
# This will import .pythonrc.py as a side-effect
import user
code.interact(local=imported_objects) code.interact(local=imported_objects)

View File

@ -8,6 +8,8 @@ import shutil
import stat import stat
import sys import sys
import tempfile import tempfile
import codecs
try: try:
from urllib.request import urlretrieve from urllib.request import urlretrieve
except ImportError: # Python 2 except ImportError: # Python 2
@ -154,12 +156,12 @@ class TemplateCommand(BaseCommand):
# Only render the Python files, as we don't want to # Only render the Python files, as we don't want to
# accidentally render Django templates files # 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() content = template_file.read()
if filename.endswith(extensions) or filename in extra_files: if filename.endswith(extensions) or filename in extra_files:
template = Template(content) template = Template(content)
content = template.render(context) 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) new_file.write(content)
if self.verbosity >= 2: if self.verbosity >= 2:

View File

@ -1,7 +1,7 @@
import sys import sys
from django.core.management.color import color_style 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.itercompat import is_iterable
from django.utils import six from django.utils import six
@ -14,7 +14,7 @@ class ModelErrorCollection:
def add(self, context, error): def add(self, context, error):
self.errors.append((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): def get_validation_errors(outfile, app=None):

View File

@ -12,7 +12,6 @@ import json
from django.core.serializers.base import DeserializationError from django.core.serializers.base import DeserializationError
from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer from django.core.serializers.python import Deserializer as PythonDeserializer
from django.utils.encoding import smart_bytes
from django.utils import six from django.utils import six
from django.utils.timezone import is_aware from django.utils.timezone import is_aware

View File

@ -12,7 +12,6 @@ from django.db import models
from django.core.serializers.base import DeserializationError from django.core.serializers.base import DeserializationError
from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer from django.core.serializers.python import Deserializer as PythonDeserializer
from django.utils.encoding import smart_bytes
from django.utils import six from django.utils import six

View File

@ -32,6 +32,7 @@ start of the base64 JSON.
There are 65 url-safe characters: the 64 used by url-safe base64 and the ':'. There are 65 url-safe characters: the 64 used by url-safe base64 and the ':'.
These functions make use of all of them. These functions make use of all of them.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import base64 import base64
@ -43,7 +44,7 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils import baseconv from django.utils import baseconv
from django.utils.crypto import constant_time_compare, salted_hmac 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 from django.utils.importlib import import_module
@ -62,12 +63,12 @@ class SignatureExpired(BadSignature):
def b64_encode(s): 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): def b64_decode(s):
pad = '=' * (-len(s) % 4) pad = b'=' * (-len(s) % 4)
return base64.urlsafe_b64decode(smart_bytes(s + pad)).decode('ascii') return base64.urlsafe_b64decode(s + pad)
def base64_hmac(salt, value, key): 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 value or re-using a salt value across different parts of your
application without good cause is a security risk. 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 # Flag for if it's been compressed or not
is_compressed = False is_compressed = False
if compress: if compress:
# Avoid zlib dependency unless compress is being used # Avoid zlib dependency unless compress is being used
compressed = zlib.compress(smart_bytes(data)) compressed = zlib.compress(data)
if len(compressed) < (len(data) - 1): if len(compressed) < (len(data) - 1):
data = compressed data = compressed
is_compressed = True is_compressed = True
base64d = b64_encode(data) base64d = b64_encode(data)
if is_compressed: if is_compressed:
base64d = '.' + base64d base64d = b'.' + base64d
return TimestampSigner(key, salt=salt).sign(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 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 decompress = False
if base64d[0] == '.': if base64d[0] == b'.':
# It's compressed; uncompress it first # It's compressed; uncompress it first
base64d = base64d[1:] base64d = base64d[1:]
decompress = True decompress = True
data = b64_decode(base64d) data = b64_decode(base64d)
if decompress: if decompress:
data = zlib.decompress(data) data = zlib.decompress(data)
return serializer().loads(data) return serializer().loads(force_str(data))
class Signer(object): class Signer(object):
def __init__(self, key=None, sep=':', salt=None): def __init__(self, key=None, sep=':', salt=None):
self.sep = sep # Use of native strings in all versions of Python
self.key = key or settings.SECRET_KEY self.sep = str(sep)
self.salt = salt or ('%s.%s' % self.key = str(key or settings.SECRET_KEY)
(self.__class__.__module__, self.__class__.__name__)) self.salt = str(salt or
'%s.%s' % (self.__class__.__module__, self.__class__.__name__))
def signature(self, value): 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): 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): def unsign(self, signed_value):
signed_value = force_str(signed_value)
if not self.sep in signed_value: if not self.sep in signed_value:
raise BadSignature('No "%s" found in value' % self.sep) raise BadSignature('No "%s" found in value' % self.sep)
value, sig = signed_value.rsplit(self.sep, 1) value, sig = signed_value.rsplit(self.sep, 1)
if constant_time_compare(sig, self.signature(value)): if constant_time_compare(sig, self.signature(value)):
return value return force_text(value)
raise BadSignature('Signature "%s" does not match' % sig) raise BadSignature('Signature "%s" does not match' % sig)
@ -177,8 +186,9 @@ class TimestampSigner(Signer):
return baseconv.base62.encode(int(time.time())) return baseconv.base62.encode(int(time.time()))
def sign(self, value): def sign(self, value):
value = '%s%s%s' % (value, self.sep, self.timestamp()) value = force_str(value)
return '%s%s%s' % (value, self.sep, self.signature(value)) value = str('%s%s%s') % (value, self.sep, self.timestamp())
return super(TimestampSigner, self).sign(value)
def unsign(self, value, max_age=None): def unsign(self, value, max_age=None):
result = super(TimestampSigner, self).unsign(value) result = super(TimestampSigner, self).unsign(value)

View File

@ -14,7 +14,7 @@ from threading import local
from django.http import Http404 from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.utils.datastructures import MultiValueDict 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.functional import memoize, lazy
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule from django.utils.module_loading import module_has_submodule
@ -195,7 +195,7 @@ class RegexURLPattern(LocaleRegexProvider):
self.name = name self.name = name
def __repr__(self): 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): def add_prefix(self, prefix):
""" """
@ -245,8 +245,13 @@ class RegexURLResolver(LocaleRegexProvider):
self._app_dict = {} self._app_dict = {}
def __repr__(self): def __repr__(self):
return smart_str('<%s %s (%s:%s) %s>' % ( if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
self.__class__.__name__, self.urlconf_name, self.app_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)) self.namespace, self.regex.pattern))
def _populate(self): def _populate(self):

View File

@ -8,7 +8,7 @@ except ImportError: # Python 2
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ 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.ipv6 import is_valid_ipv6_address
from django.utils import six from django.utils import six
@ -37,7 +37,7 @@ class RegexValidator(object):
""" """
Validates that the input matches the regular expression. 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) raise ValidationError(self.message, code=self.code)
@ -57,7 +57,7 @@ class URLValidator(RegexValidator):
except ValidationError as e: except ValidationError as e:
# Trivial case failed. Try for possible IDN domain # Trivial case failed. Try for possible IDN domain
if value: if value:
value = smart_text(value) value = force_text(value)
scheme, netloc, path, query, fragment = urlsplit(value) scheme, netloc, path, query, fragment = urlsplit(value)
try: try:
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE

View File

@ -609,7 +609,7 @@ class BaseDatabaseOperations(object):
exists for database backends to provide a better implementation exists for database backends to provide a better implementation
according to their own quoting schemes. 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. # Convert params to contain Unicode values.
to_unicode = lambda s: force_text(s, strings_only=True, errors='replace') to_unicode = lambda s: force_text(s, strings_only=True, errors='replace')
@ -618,7 +618,7 @@ class BaseDatabaseOperations(object):
else: else:
u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()]) 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): def last_insert_id(self, cursor, table_name, pk_name):
""" """
@ -802,8 +802,8 @@ class BaseDatabaseOperations(object):
def prep_for_like_query(self, x): def prep_for_like_query(self, x):
"""Prepares a value for use in a LIKE query.""" """Prepares a value for use in a LIKE query."""
from django.utils.encoding import smart_text from django.utils.encoding import force_text
return smart_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") return force_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
# Same as prep_for_like_query(), but called for "iexact" matches, which # Same as prep_for_like_query(), but called for "iexact" matches, which
# need not necessarily be implemented using "LIKE" in the backend. # need not necessarily be implemented using "LIKE" in the backend.

View File

@ -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 from django.db.models.sql import compiler
class SQLCompiler(compiler.SQLCompiler): class SQLCompiler(compiler.SQLCompiler):
def resolve_columns(self, row, fields=()): def resolve_columns(self, row, fields=()):
values = [] values = []
index_extra_select = len(self.query.extra_select) 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 if (field and field.get_internal_type() in ("BooleanField", "NullBooleanField") and
value in (0, 1)): value in (0, 1)):
value = bool(value) value = bool(value)

View File

@ -1,8 +1,9 @@
import re
from .base import FIELD_TYPE
from django.db.backends import BaseDatabaseIntrospection from django.db.backends import BaseDatabaseIntrospection
from django.utils import six 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 `([^`]*)` \(`([^`]*)`\)") 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()] return [row[0] for row in cursor.fetchall()]
def get_table_description(self, cursor, table_name): 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)) 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): def _name_to_index(self, cursor, table_name):
""" """

View File

@ -10,8 +10,6 @@ import decimal
import sys import sys
import warnings import warnings
from django.utils import six
def _setup_environment(environ): def _setup_environment(environ):
import platform import platform
# Cygwin requires some special voodoo to set the environment variables # 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.client import DatabaseClient
from django.db.backends.oracle.creation import DatabaseCreation from django.db.backends.oracle.creation import DatabaseCreation
from django.db.backends.oracle.introspection import DatabaseIntrospection 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 six
from django.utils import timezone 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'): if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'):
convert_unicode = force_text convert_unicode = force_text
else: else:
convert_unicode = smart_bytes convert_unicode = force_bytes
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
@ -604,9 +602,9 @@ class OracleParam(object):
elif param is False: elif param is False:
param = "0" param = "0"
if hasattr(param, 'bind_parameter'): if hasattr(param, 'bind_parameter'):
self.smart_bytes = param.bind_parameter(cursor) self.force_bytes = param.bind_parameter(cursor)
else: else:
self.smart_bytes = convert_unicode(param, cursor.charset, self.force_bytes = convert_unicode(param, cursor.charset,
strings_only) strings_only)
if hasattr(param, 'input_size'): if hasattr(param, 'input_size'):
# If parameter has `input_size` attribute, use that. # If parameter has `input_size` attribute, use that.
@ -685,7 +683,7 @@ class FormatStylePlaceholderCursor(object):
self.setinputsizes(*sizes) self.setinputsizes(*sizes)
def _param_generator(self, params): 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): def execute(self, query, params=None):
if params is None: if params is None:

View File

@ -45,7 +45,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
WHERE table_name = %s""", [table_name]) WHERE table_name = %s""", [table_name])
null_map = dict(cursor.fetchall()) null_map = dict(cursor.fetchall())
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)) 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']) return [line[:6] + (null_map[line[0]]=='YES',)
for line in cursor.description] for line in cursor.description]
def get_relations(self, cursor, table_name): def get_relations(self, cursor, table_name):

View File

@ -1,6 +1,14 @@
import re import re
from django.db.backends import BaseDatabaseIntrospection 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 # 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 # types include variables in them -- e.g. "varchar(30)" -- and can't be matched
# as a simple dictionary lookup. # as a simple dictionary lookup.
@ -32,10 +40,9 @@ class FlexibleFieldLookupDict(object):
try: try:
return self.base_data_types_reverse[key] return self.base_data_types_reverse[key]
except KeyError: except KeyError:
import re size = get_field_size(key)
m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key) if size is not None:
if m: return ('CharField', {'max_length': size})
return ('CharField', {'max_length': int(m.group(1))})
raise KeyError raise KeyError
class DatabaseIntrospection(BaseDatabaseIntrospection): class DatabaseIntrospection(BaseDatabaseIntrospection):
@ -53,7 +60,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_table_description(self, cursor, table_name): 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."
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)] info['null_ok']) for info in self._table_info(cursor, table_name)]
def get_relations(self, cursor, table_name): def get_relations(self, cursor, table_name):
@ -171,6 +178,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
# cid, name, type, notnull, dflt_value, pk # cid, name, type, notnull, dflt_value, pk
return [{'name': field[1], return [{'name': field[1],
'type': field[2], 'type': field[2],
'size': get_field_size(field[2]),
'null_ok': not field[3], 'null_ok': not field[3],
'pk': field[5] # undocumented 'pk': field[5] # undocumented
} for field in cursor.fetchall()] } for field in cursor.fetchall()]

View File

@ -6,7 +6,7 @@ import hashlib
from time import time from time import time
from django.conf import settings 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.log import getLogger
from django.utils.timezone import utc 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: if length is None or len(name) <= length:
return name 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) return '%s%s' % (name[:length-hash_len], hsh)
def format_number(value, max_digits, decimal_places): def format_number(value, max_digits, decimal_places):

View File

@ -23,7 +23,7 @@ from django.db.models import signals
from django.db.models.loading import register_models, get_model from django.db.models.loading import register_models, get_model
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.functional import curry 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 import six
from django.utils.text import get_text_list, capfirst 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) u = six.text_type(self)
except (UnicodeEncodeError, UnicodeDecodeError): except (UnicodeEncodeError, UnicodeDecodeError):
u = '[Bad Unicode data]' 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): def __str__(self):
if not six.PY3 and hasattr(self, '__unicode__'): if not six.PY3 and hasattr(self, '__unicode__'):

View File

@ -1047,13 +1047,14 @@ class GenericIPAddressField(Field):
description = _("IP address") description = _("IP address")
default_error_messages = {} 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.unpack_ipv4 = unpack_ipv4
self.default_validators, invalid_error_message = \ self.default_validators, invalid_error_message = \
validators.ip_address_validators(protocol, unpack_ipv4) validators.ip_address_validators(protocol, unpack_ipv4)
self.default_error_messages['invalid'] = invalid_error_message self.default_error_messages['invalid'] = invalid_error_message
kwargs['max_length'] = 39 kwargs['max_length'] = 39
Field.__init__(self, *args, **kwargs) Field.__init__(self, verbose_name, name, *args, **kwargs)
def get_internal_type(self): def get_internal_type(self):
return "GenericIPAddressField" return "GenericIPAddressField"

View File

@ -8,7 +8,7 @@ from django.core.files.base import File
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.core.files.images import ImageFile from django.core.files.images import ImageFile
from django.db.models import signals 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 import six
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -280,7 +280,7 @@ class FileField(Field):
setattr(cls, self.name, self.descriptor_class(self)) setattr(cls, self.name, self.descriptor_class(self))
def get_directory_name(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): def get_filename(self, filename):
return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename))) return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))

View File

@ -498,9 +498,7 @@ class QuerySet(object):
"Cannot use 'limit' or 'offset' with in_bulk" "Cannot use 'limit' or 'offset' with in_bulk"
if not id_list: if not id_list:
return {} return {}
qs = self._clone() qs = self.filter(pk__in=id_list).order_by()
qs.query.add_filter(('pk__in', id_list))
qs.query.clear_ordering(force_empty=True)
return dict([(obj._get_pk_val(), obj) for obj in qs]) return dict([(obj._get_pk_val(), obj) for obj in qs])
def delete(self): def delete(self):

View File

@ -470,9 +470,7 @@ class SQLCompiler(object):
# Must use left outer joins for nullable fields and their relations. # Must use left outer joins for nullable fields and their relations.
# Ordering or distinct must not affect the returned set, and INNER # Ordering or distinct must not affect the returned set, and INNER
# JOINS for nullable fields could do this. # JOINS for nullable fields could do this.
if joins_to_promote: self.query.promote_joins(joins_to_promote)
self.query.promote_alias_chain(joins_to_promote,
self.query.alias_map[joins_to_promote[0]].join_type == self.query.LOUTER)
return field, col, alias, joins, opts return field, col, alias, joins, opts
def _final_join_removal(self, col, alias): def _final_join_removal(self, col, alias):
@ -645,8 +643,6 @@ class SQLCompiler(object):
alias_chain.append(alias) alias_chain.append(alias)
for (dupe_opts, dupe_col) in dupe_set: for (dupe_opts, dupe_col) in dupe_set:
self.query.update_dupe_avoidance(dupe_opts, dupe_col, alias) 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: else:
alias = root_alias alias = root_alias
@ -663,8 +659,6 @@ class SQLCompiler(object):
columns, aliases = self.get_default_columns(start_alias=alias, columns, aliases = self.get_default_columns(start_alias=alias,
opts=f.rel.to._meta, as_pairs=True) opts=f.rel.to._meta, as_pairs=True)
self.query.related_select_cols.extend(columns) 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) self.query.related_select_fields.extend(f.rel.to._meta.fields)
if restricted: if restricted:
next = requested.get(f.name, {}) next = requested.get(f.name, {})
@ -738,7 +732,9 @@ class SQLCompiler(object):
self.query.related_select_fields.extend(model._meta.fields) self.query.related_select_fields.extend(model._meta.fields)
next = requested.get(f.related_query_name(), {}) 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, self.fill_related_selections(model._meta, table, cur_depth+1,
used, next, restricted, new_nullable) used, next, restricted, new_nullable)

View File

@ -1,7 +1,7 @@
from collections import namedtuple from collections import namedtuple
import re 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([ QUERY_TERMS = set([
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in', 'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year', 'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',

View File

@ -505,7 +505,7 @@ class Query(object):
# Again, some of the tables won't have aliases due to # Again, some of the tables won't have aliases due to
# the trimming of unnecessary tables. # the trimming of unnecessary tables.
if self.alias_refcount.get(alias) or rhs.alias_refcount.get(alias): 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 # Now relabel a copy of the rhs where-clause and add it to the current
# one. # one.
@ -682,32 +682,38 @@ class Query(object):
""" Decreases the reference count for this alias. """ """ Decreases the reference count for this alias. """
self.alias_refcount[alias] -= amount 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 Promotes recursively the join type of given aliases and its children to
for the join to contain NULL values on the left. If 'unconditional' is an outer join. If 'unconditional' is False, the join is only promoted if
False, the join is only promoted if it is nullable, otherwise it is it is nullable or the parent join is an outer join.
always promoted.
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 aliases = list(aliases)
self.alias_map[alias].join_type != self.LOUTER): while aliases:
data = self.alias_map[alias] alias = aliases.pop(0)
data = data._replace(join_type=self.LOUTER) 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 self.alias_map[alias] = data
return True # Join type of 'alias' changed, so re-examine all aliases that
return False # refer to this one.
aliases.extend(
def promote_alias_chain(self, chain, must_promote=False): join for join in self.alias_map.keys()
""" if (self.alias_map[join].lhs_alias == alias
Walks along a chain of aliases, promoting the first nullable join and and join not in aliases))
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
def reset_refcounts(self, to_counts): 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 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. 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: for alias in self.tables:
if alias not in used_aliases: if alias in used_aliases and (alias not in initial_refcounts or
continue
if (alias not in initial_refcounts or
self.alias_refcount[alias] == initial_refcounts[alias]): self.alias_refcount[alias] == initial_refcounts[alias]):
parent = self.alias_map[alias].lhs_alias self.promote_joins([alias])
must_promote = considered.get(parent, False)
promoted = self.promote_alias(alias, must_promote)
considered[alias] = must_promote or promoted
def change_aliases(self, change_map): 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 LOUTER join type. This is used when joining certain types of querysets
and Q-objects together. 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 If 'nullable' is True, the join can potentially involve NULL values and
is a candidate for promotion (to "left outer") when combining querysets. 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: if self.alias_map[alias].lhs_alias != lhs:
continue continue
self.ref_alias(alias) self.ref_alias(alias)
if promote: if promote or (lhs and self.alias_map[lhs].join_type == self.LOUTER):
self.promote_alias(alias) self.promote_joins([alias])
return alias return alias
# No reuse is possible, so we need a new 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 # Not all tables need to be joined to anything. No join type
# means the later columns are ignored. # means the later columns are ignored.
join_type = None 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 join_type = self.LOUTER
else: else:
join_type = self.INNER join_type = self.INNER
@ -1004,8 +1009,7 @@ class Query(object):
# If the aggregate references a model or field that requires a join, # If the aggregate references a model or field that requires a join,
# those joins must be LEFT OUTER - empty join rows must be returned # those joins must be LEFT OUTER - empty join rows must be returned
# in order for zeros to be returned for those aggregates. # in order for zeros to be returned for those aggregates.
for column_alias in join_list: self.promote_joins(join_list, True)
self.promote_alias(column_alias, unconditional=True)
col = (join_list[-1], col) col = (join_list[-1], col)
else: else:
@ -1124,7 +1128,7 @@ class Query(object):
# If the comparison is against NULL, we may need to use some left # 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 # outer joins when creating the join chain. This is only done when
# needed, as it's less efficient at the database level. # needed, as it's less efficient at the database level.
self.promote_alias_chain(join_list) self.promote_joins(join_list)
join_promote = True join_promote = True
# Process the join list to see if we can remove any inner joins from # 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 # This means that we are dealing with two different query
# subtrees, so we don't need to do any join promotion. # subtrees, so we don't need to do any join promotion.
continue continue
join_promote = join_promote or self.promote_alias(join, unconditional) join_promote = join_promote or self.promote_joins([join], unconditional)
if table != join: 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 # We only get here if we have found a table that exists
# in the join list, but isn't on the original tables list. # in the join list, but isn't on the original tables list.
# This means we've reached the point where we only have # This means we've reached the point where we only have
# new tables, so we can break out of this promotion loop. # new tables, so we can break out of this promotion loop.
break break
self.promote_alias_chain(join_it, join_promote) self.promote_joins(join_it, join_promote)
self.promote_alias_chain(table_it, table_promote or join_promote) self.promote_joins(table_it, table_promote or join_promote)
if having_clause or force_having: if having_clause or force_having:
if (alias, col) not in self.group_by: if (alias, col) not in self.group_by:
@ -1176,7 +1180,7 @@ class Query(object):
connector) connector)
if negate: if negate:
self.promote_alias_chain(join_list) self.promote_joins(join_list)
if lookup_type != 'isnull': if lookup_type != 'isnull':
if len(join_list) > 1: if len(join_list) > 1:
for alias in join_list: for alias in join_list:
@ -1650,7 +1654,7 @@ class Query(object):
final_alias = join.lhs_alias final_alias = join.lhs_alias
col = join.lhs_join_col col = join.lhs_join_col
joins = joins[:-1] joins = joins[:-1]
self.promote_alias_chain(joins[1:]) self.promote_joins(joins[1:])
self.select.append((final_alias, col)) self.select.append((final_alias, col))
self.select_fields.append(field) self.select_fields.append(field)
except MultiJoin: except MultiJoin:

View File

@ -71,7 +71,8 @@ class BaseFormSet(object):
return True return True
__nonzero__ = __bool__ # Python 2 __nonzero__ = __bool__ # Python 2
def _management_form(self): @property
def management_form(self):
"""Returns the ManagementForm instance for this FormSet.""" """Returns the ManagementForm instance for this FormSet."""
if self.is_bound: if self.is_bound:
form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix) 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 MAX_NUM_FORM_COUNT: self.max_num
}) })
return form return form
management_form = property(_management_form)
def total_form_count(self): def total_form_count(self):
"""Returns the total number of forms in this FormSet.""" """Returns the total number of forms in this FormSet."""
@ -140,17 +140,18 @@ class BaseFormSet(object):
self.add_fields(form, i) self.add_fields(form, i)
return form return form
def _get_initial_forms(self): @property
def initial_forms(self):
"""Return a list of all the initial forms in this formset.""" """Return a list of all the initial forms in this formset."""
return self.forms[:self.initial_form_count()] 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 a list of all the extra forms in this formset."""
return self.forms[self.initial_form_count():] 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 = { defaults = {
'auto_id': self.auto_id, 'auto_id': self.auto_id,
'prefix': self.add_prefix('__prefix__'), 'prefix': self.add_prefix('__prefix__'),
@ -160,19 +161,19 @@ class BaseFormSet(object):
form = self.form(**defaults) form = self.form(**defaults)
self.add_fields(form, None) self.add_fields(form, None)
return form return form
empty_form = property(_get_empty_form)
# Maybe this should just go away? # 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. Returns a list of form.cleaned_data dicts for every form in self.forms.
""" """
if not self.is_valid(): if not self.is_valid():
raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__) raise AttributeError("'%s' object has no attribute 'cleaned_data'" % self.__class__.__name__)
return [form.cleaned_data for form in self.forms] 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 Returns a list of forms that have been marked for deletion. Raises an
AttributeError if deletion is not allowed. AttributeError if deletion is not allowed.
@ -191,9 +192,9 @@ class BaseFormSet(object):
if self._should_delete_form(form): if self._should_delete_form(form):
self._deleted_form_indexes.append(i) self._deleted_form_indexes.append(i)
return [self.forms[i] for i in self._deleted_form_indexes] 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. Returns a list of form in the order specified by the incoming data.
Raises an AttributeError if ordering is not allowed. 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 # Return a list of form.cleaned_data dicts in the order specified by
# the form data. # the form data.
return [self.forms[i[0]] for i in self._ordering] return [self.forms[i[0]] for i in self._ordering]
ordered_forms = property(_get_ordered_forms)
@classmethod @classmethod
def get_default_prefix(cls): def get_default_prefix(cls):
@ -244,23 +244,20 @@ class BaseFormSet(object):
return self._non_form_errors return self._non_form_errors
return self.error_class() return self.error_class()
def _get_errors(self): @property
def errors(self):
""" """
Returns a list of form.errors for every form in self.forms. Returns a list of form.errors for every form in self.forms.
""" """
if self._errors is None: if self._errors is None:
self.full_clean() self.full_clean()
return self._errors return self._errors
errors = property(_get_errors)
def _should_delete_form(self, form): 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 Returns whether or not the form was marked for deletion.
# not exist if the form is invalid. """
field = form.fields[DELETION_FIELD_NAME] return form.cleaned_data.get(DELETION_FIELD_NAME, False)
raw_value = form._raw_value(DELETION_FIELD_NAME)
should_delete = field.clean(raw_value)
return should_delete
def is_valid(self): def is_valid(self):
""" """
@ -335,14 +332,14 @@ class BaseFormSet(object):
""" """
return self.forms and self.forms[0].is_multipart() 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 # All the forms on a FormSet are the same, so you only need to
# interrogate the first form for media. # interrogate the first form for media.
if self.forms: if self.forms:
return self.forms[0].media return self.forms[0].media
else: else:
return Media() return Media()
media = property(_get_media)
def as_table(self): def as_table(self):
"Returns this formset rendered as HTML <tr>s -- excluding the <table></table>." "Returns this formset rendered as HTML <tr>s -- excluding the <table></table>."

View File

@ -18,7 +18,6 @@ from django.utils.datastructures import SortedDict
from django.utils import six from django.utils import six
from django.utils.text import get_text_list, capfirst from django.utils.text import get_text_list, capfirst
from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.translation import ugettext_lazy as _, ugettext
from django.utils import six
__all__ = ( __all__ = (
@ -592,6 +591,10 @@ class BaseModelFormSet(BaseFormSet):
return [] return []
saved_instances = [] saved_instances = []
try:
forms_to_delete = self.deleted_forms
except AttributeError:
forms_to_delete = []
for form in self.initial_forms: for form in self.initial_forms:
pk_name = self._pk_field.name pk_name = self._pk_field.name
raw_pk_value = form._raw_value(pk_name) raw_pk_value = form._raw_value(pk_name)
@ -602,7 +605,7 @@ class BaseModelFormSet(BaseFormSet):
pk_value = getattr(pk_value, 'pk', pk_value) pk_value = getattr(pk_value, 'pk', pk_value)
obj = self._existing_object(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) self.deleted_objects.append(obj)
obj.delete() obj.delete()
continue continue

View File

@ -61,14 +61,14 @@ else:
if not _cookie_allows_colon_in_names: if not _cookie_allows_colon_in_names:
def load(self, rawdata): def load(self, rawdata):
self.bad_cookies = set() self.bad_cookies = set()
super(SimpleCookie, self).load(smart_str(rawdata)) super(SimpleCookie, self).load(force_str(rawdata))
for key in self.bad_cookies: for key in self.bad_cookies:
del self[key] del self[key]
# override private __set() method: # override private __set() method:
# (needed for using our Morsel, and for laxness with CookieError # (needed for using our Morsel, and for laxness with CookieError
def _BaseCookie__set(self, key, real_value, coded_value): def _BaseCookie__set(self, key, real_value, coded_value):
key = smart_str(key) key = force_str(key)
try: try:
M = self.get(key, Morsel()) M = self.get(key, Morsel())
M.set(key, real_value, coded_value) 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.multipartparser import MultiPartParser
from django.http.utils import * from django.http.utils import *
from django.utils.datastructures import MultiValueDict, ImmutableList 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.http import cookie_date
from django.utils import six from django.utils import six
from django.utils import timezone from django.utils import timezone
@ -137,7 +137,7 @@ def build_request_repr(request, path_override=None, GET_override=None,
except Exception: except Exception:
meta = '<could not parse>' meta = '<could not parse>'
path = path_override if path_override is not None else request.path 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__, (request.__class__.__name__,
path, path,
six.text_type(get), six.text_type(get),
@ -243,7 +243,12 @@ class HttpRequest(object):
def is_ajax(self): def is_ajax(self):
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest' 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 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 dictionary has already been created, it is removed and recreated on the
@ -255,27 +260,22 @@ class HttpRequest(object):
if hasattr(self, '_post'): if hasattr(self, '_post'):
del self._post del self._post
def _get_encoding(self):
return self._encoding
encoding = property(_get_encoding, _set_encoding)
def _initialize_handlers(self): def _initialize_handlers(self):
self._upload_handlers = [uploadhandler.load_handler(handler, self) self._upload_handlers = [uploadhandler.load_handler(handler, self)
for handler in settings.FILE_UPLOAD_HANDLERS] for handler in settings.FILE_UPLOAD_HANDLERS]
def _set_upload_handlers(self, upload_handlers): @property
if hasattr(self, '_files'): def upload_handlers(self):
raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
self._upload_handlers = upload_handlers
def _get_upload_handlers(self):
if not self._upload_handlers: if not self._upload_handlers:
# If there are no upload handlers defined, initialize them from settings. # If there are no upload handlers defined, initialize them from settings.
self._initialize_handlers() self._initialize_handlers()
return self._upload_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): def parse_file_upload(self, META, post_data):
"""Returns a tuple of (POST QueryDict, FILES MultiValueDict).""" """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
@ -397,16 +397,16 @@ class QueryDict(MultiValueDict):
force_text(value, encoding, errors='replace')) force_text(value, encoding, errors='replace'))
self._mutable = mutable self._mutable = mutable
def _get_encoding(self): @property
def encoding(self):
if self._encoding is None: if self._encoding is None:
self._encoding = settings.DEFAULT_CHARSET self._encoding = settings.DEFAULT_CHARSET
return self._encoding return self._encoding
def _set_encoding(self, value): @encoding.setter
def encoding(self, value):
self._encoding = value self._encoding = value
encoding = property(_get_encoding, _set_encoding)
def _assert_mutable(self): def _assert_mutable(self):
if not self._mutable: if not self._mutable:
raise AttributeError("This QueryDict instance is immutable") raise AttributeError("This QueryDict instance is immutable")
@ -489,13 +489,13 @@ class QueryDict(MultiValueDict):
""" """
output = [] output = []
if safe: 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))) encode = lambda k, v: '%s=%s' % ((quote(k, safe), quote(v, safe)))
else: else:
encode = lambda k, v: urlencode({k: v}) encode = lambda k, v: urlencode({k: v})
for k, list_ in self.lists(): for k, list_ in self.lists():
k = smart_bytes(k, self.encoding) k = force_bytes(k, self.encoding)
output.extend([encode(k, smart_bytes(v, self.encoding)) output.extend([encode(k, force_bytes(v, self.encoding))
for v in list_]) for v in list_])
return '&'.join(output) return '&'.join(output)
@ -539,7 +539,7 @@ class HttpResponse(object):
if not content_type: if not content_type:
content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
self._charset) self._charset)
# content is a bytestring. See _get_content / _set_content. # content is a bytestring. See the content property methods.
self.content = content self.content = content
self.cookies = SimpleCookie() self.cookies = SimpleCookie()
if status: if status:
@ -669,7 +669,8 @@ class HttpResponse(object):
self.set_cookie(key, max_age=0, path=path, domain=domain, self.set_cookie(key, max_age=0, path=path, domain=domain,
expires='Thu, 01-Jan-1970 00:00:00 GMT') expires='Thu, 01-Jan-1970 00:00:00 GMT')
def _get_content(self): @property
def content(self):
if self.has_header('Content-Encoding'): if self.has_header('Content-Encoding'):
def make_bytes(value): def make_bytes(value):
if isinstance(value, int): if isinstance(value, int):
@ -679,9 +680,10 @@ class HttpResponse(object):
# force conversion to bytes in case chunk is a subclass # force conversion to bytes in case chunk is a subclass
return bytes(value) return bytes(value)
return b''.join(make_bytes(e) for e in self._container) 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)): if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.string_types)):
self._container = value self._container = value
self._base_content_is_iter = True self._base_content_is_iter = True
@ -689,8 +691,6 @@ class HttpResponse(object):
self._container = [value] self._container = [value]
self._base_content_is_iter = False self._base_content_is_iter = False
content = property(_get_content, _set_content)
def __iter__(self): def __iter__(self):
self._iterator = iter(self._container) self._iterator = iter(self._container)
return self return self
@ -728,11 +728,11 @@ class HttpResponse(object):
class HttpResponseRedirectBase(HttpResponse): class HttpResponseRedirectBase(HttpResponse):
allowed_schemes = ['http', 'https', 'ftp'] allowed_schemes = ['http', 'https', 'ftp']
def __init__(self, redirect_to): def __init__(self, redirect_to, *args, **kwargs):
parsed = urlparse(redirect_to) parsed = urlparse(redirect_to)
if parsed.scheme and parsed.scheme not in self.allowed_schemes: if parsed.scheme and parsed.scheme not in self.allowed_schemes:
raise SuspiciousOperation("Unsafe redirect to URL with protocol '%s'" % parsed.scheme) 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) self['Location'] = iri_to_uri(redirect_to)
class HttpResponseRedirect(HttpResponseRedirectBase): class HttpResponseRedirect(HttpResponseRedirectBase):
@ -744,6 +744,16 @@ class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
class HttpResponseNotModified(HttpResponse): class HttpResponseNotModified(HttpResponse):
status_code = 304 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): class HttpResponseBadRequest(HttpResponse):
status_code = 400 status_code = 400
@ -756,8 +766,8 @@ class HttpResponseForbidden(HttpResponse):
class HttpResponseNotAllowed(HttpResponse): class HttpResponseNotAllowed(HttpResponse):
status_code = 405 status_code = 405
def __init__(self, permitted_methods): def __init__(self, permitted_methods, *args, **kwargs):
super(HttpResponseNotAllowed, self).__init__() super(HttpResponseNotAllowed, self).__init__(*args, **kwargs)
self['Allow'] = ', '.join(permitted_methods) self['Allow'] = ', '.join(permitted_methods)
class HttpResponseGone(HttpResponse): class HttpResponseGone(HttpResponse):

View File

@ -11,7 +11,7 @@ from django.utils.importlib import import_module
from django.utils.itercompat import is_iterable from django.utils.itercompat import is_iterable
from django.utils.text import (smart_split, unescape_string_literal, from django.utils.text import (smart_split, unescape_string_literal,
get_text_list) 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.translation import ugettext_lazy, pgettext_lazy
from django.utils.safestring import (SafeData, EscapeData, mark_safe, from django.utils.safestring import (SafeData, EscapeData, mark_safe,
mark_for_escaping) mark_for_escaping)
@ -116,7 +116,7 @@ class Template(object):
def __init__(self, template_string, origin=None, def __init__(self, template_string, origin=None,
name='<Unknown Template>'): name='<Unknown Template>'):
try: try:
template_string = smart_text(template_string) template_string = force_text(template_string)
except UnicodeDecodeError: except UnicodeDecodeError:
raise TemplateEncodingError("Templates can only be constructed " raise TemplateEncodingError("Templates can only be constructed "
"from unicode or UTF-8 strings.") "from unicode or UTF-8 strings.")
@ -848,7 +848,7 @@ class TextNode(Node):
self.s = s self.s = s
def __repr__(self): def __repr__(self):
return "<Text Node: '%s'>" % smart_str(self.s[:25], 'ascii', return force_str("<Text Node: '%s'>" % self.s[:25], 'ascii',
errors='replace') errors='replace')
def render(self, context): def render(self, context):

View File

@ -102,7 +102,7 @@ class SimpleTemplateResponse(HttpResponse):
""" """
retval = self retval = self
if not self._is_rendered: if not self._is_rendered:
self._set_content(self.rendered_content) self.content = self.rendered_content
for post_callback in self._post_render_callbacks: for post_callback in self._post_render_callbacks:
newretval = post_callback(retval) newretval = post_callback(retval)
if newretval is not None: if newretval is not None:
@ -119,20 +119,20 @@ class SimpleTemplateResponse(HttpResponse):
'rendered before it can be iterated over.') 'rendered before it can be iterated over.')
return super(SimpleTemplateResponse, self).__iter__() return super(SimpleTemplateResponse, self).__iter__()
def _get_content(self): @property
def content(self):
if not self._is_rendered: if not self._is_rendered:
raise ContentNotRenderedError('The response content must be ' raise ContentNotRenderedError('The response content must be '
'rendered before it can be accessed.') '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 """Sets the content for the response
""" """
super(SimpleTemplateResponse, self)._set_content(value) HttpResponse.content.fset(self, value)
self._is_rendered = True self._is_rendered = True
content = property(_get_content, _set_content)
class TemplateResponse(SimpleTemplateResponse): class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + \ rendering_attrs = SimpleTemplateResponse.rendering_attrs + \

View File

@ -4,7 +4,7 @@ import hashlib
from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
from django.template import resolve_variable from django.template import resolve_variable
from django.core.cache import cache 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 from django.utils.http import urlquote
register = Library() register = Library()
@ -26,8 +26,8 @@ class CacheNode(Node):
except (ValueError, TypeError): except (ValueError, TypeError):
raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
# Build a key for this fragment and all vary-on's. # 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])) key = ':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on])
args = hashlib.md5(key) args = hashlib.md5(force_bytes(key))
cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest()) cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest())
value = cache.get(cache_key) value = cache.get(cache_key)
if value is None: if value is None:

View File

@ -21,7 +21,7 @@ from django.http import SimpleCookie, HttpRequest, QueryDict
from django.template import TemplateDoesNotExist from django.template import TemplateDoesNotExist
from django.test import signals from django.test import signals
from django.utils.functional import curry 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.http import urlencode
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.itercompat import is_iterable 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. as an application/octet-stream; otherwise, str(value) will be sent.
""" """
lines = [] 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. # Not by any means perfect, but good enough for our purposes.
is_file = lambda thing: hasattr(thing, "read") and callable(thing.read) 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) return b'\r\n'.join(lines)
def encode_file(boundary, key, file): 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] content_type = mimetypes.guess_type(file.name)[0]
if content_type is None: if content_type is None:
content_type = 'application/octet-stream' content_type = 'application/octet-stream'
@ -222,7 +222,7 @@ class RequestFactory(object):
charset = match.group(1) charset = match.group(1)
else: else:
charset = settings.DEFAULT_CHARSET charset = settings.DEFAULT_CHARSET
return smart_bytes(data, encoding=charset) return force_bytes(data, encoding=charset)
def _get_path(self, parsed): def _get_path(self, parsed):
# If there are parameters, add them # If there are parameters, add them
@ -293,7 +293,7 @@ class RequestFactory(object):
def generic(self, method, path, def generic(self, method, path,
data='', content_type='application/octet-stream', **extra): data='', content_type='application/octet-stream', **extra):
parsed = urlparse(path) parsed = urlparse(path)
data = smart_bytes(data, settings.DEFAULT_CHARSET) data = force_bytes(data, settings.DEFAULT_CHARSET)
r = { r = {
'PATH_INFO': self._get_path(parsed), 'PATH_INFO': self._get_path(parsed),
'QUERY_STRING': parsed[4], 'QUERY_STRING': parsed[4],

View File

@ -4,7 +4,6 @@ import time
from django.conf import settings from django.conf import settings
from django.db import connections from django.db import connections
from django.dispatch import receiver, Signal from django.dispatch import receiver, Signal
from django.template import context
from django.utils import timezone from django.utils import timezone
template_rendered = Signal(providing_args=["template", "context"]) template_rendered = Signal(providing_args=["template", "context"])
@ -48,9 +47,17 @@ def update_connections_time_zone(**kwargs):
@receiver(setting_changed) @receiver(setting_changed)
def clear_context_processors_cache(**kwargs): def clear_context_processors_cache(**kwargs):
if kwargs['setting'] == 'TEMPLATE_CONTEXT_PROCESSORS': if kwargs['setting'] == 'TEMPLATE_CONTEXT_PROCESSORS':
from django.template import context
context._standard_context_processors = None 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) @receiver(setting_changed)
def language_changed(**kwargs): def language_changed(**kwargs):
if kwargs['setting'] in ('LOCALE_PATHS', 'LANGUAGE_CODE'): if kwargs['setting'] in ('LOCALE_PATHS', 'LANGUAGE_CODE'):

View File

@ -41,7 +41,7 @@ from django.test.utils import (get_warnings_state, restore_warnings_state,
override_settings) override_settings)
from django.test.utils import ContextList from django.test.utils import ContextList
from django.utils import unittest as ut2 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 import six
from django.utils.unittest.util import safe_repr from django.utils.unittest.util import safe_repr
from django.views.static import serve from django.views.static import serve

View File

@ -24,7 +24,7 @@ import time
from django.conf import settings from django.conf import settings
from django.core.cache import get_cache 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.http import http_date
from django.utils.timezone import get_current_timezone_name from django.utils.timezone import get_current_timezone_name
from django.utils.translation import get_language 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) value = request.META.get(header, None)
if value is not None: if value is not None:
ctx.update(value) 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' % ( cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
key_prefix, method, path.hexdigest(), ctx.hexdigest()) key_prefix, method, path.hexdigest(), ctx.hexdigest())
return _i18n_cache_key_suffix(request, cache_key) return _i18n_cache_key_suffix(request, cache_key)
def _generate_cache_header_key(key_prefix, request): def _generate_cache_header_key(key_prefix, request):
"""Returns a cache key for the header cache.""" """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' % ( cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
key_prefix, path.hexdigest()) key_prefix, path.hexdigest())
return _i18n_cache_key_suffix(request, cache_key) return _i18n_cache_key_suffix(request, cache_key)

View File

@ -23,7 +23,8 @@ except NotImplementedError:
using_sysrandom = False using_sysrandom = False
from django.conf import settings 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 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 # 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. # the hmac module does the same thing for keys longer than the block size.
# However, we need to ensure that we *always* do this. # 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, def get_random_string(length=12,
@ -88,6 +89,10 @@ def constant_time_compare(val1, val2):
if len(val1) != len(val2): if len(val1) != len(val2):
return False return False
result = 0 result = 0
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): for x, y in zip(val1, val2):
result |= ord(x) ^ ord(y) result |= ord(x) ^ ord(y)
return result == 0 return result == 0
@ -142,8 +147,8 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
assert iterations > 0 assert iterations > 0
if not digest: if not digest:
digest = hashlib.sha256 digest = hashlib.sha256
password = smart_bytes(password) password = force_bytes(password)
salt = smart_bytes(salt) salt = force_bytes(salt)
hlen = digest().digest_size hlen = digest().digest_size
if not dklen: if not dklen:
dklen = hlen dklen = hlen

View File

@ -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 # An Exception subclass containing non-ASCII data that doesn't
# know how to print itself properly. We shouldn't raise a # know how to print itself properly. We shouldn't raise a
# further exception. # further exception.
return ' '.join([smart_bytes(arg, encoding, strings_only, return b' '.join([force_bytes(arg, encoding, strings_only,
errors) for arg in s]) errors) for arg in s])
return six.text_type(s).encode(encoding, errors) return six.text_type(s).encode(encoding, errors)
else: else:
@ -225,7 +225,7 @@ def iri_to_uri(iri):
# converted. # converted.
if iri is None: if iri is None:
return iri return iri
return quote(smart_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~") return quote(force_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
def filepath_to_uri(path): def filepath_to_uri(path):
"""Convert an file system path to a URI portion that is suitable for """Convert an file system path to a URI portion that is suitable for
@ -244,7 +244,7 @@ def filepath_to_uri(path):
return path return path
# I know about `os.sep` and `os.altsep` but I want to leave # I know about `os.sep` and `os.altsep` but I want to leave
# some flexibility for hardcoding separators. # 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 # The encoding of the default system locale but falls back to the
# given fallback encoding if the encoding is unsupported by python or could # given fallback encoding if the encoding is unsupported by python or could

View File

@ -4,7 +4,7 @@ import datetime
from django.conf import settings from django.conf import settings
from django.utils import dateformat, numberformat, datetime_safe from django.utils import dateformat, numberformat, datetime_safe
from django.utils.importlib import import_module 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.functional import lazy
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils import six 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 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. 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 use_l10n or (use_l10n is None and settings.USE_L10N):
if lang is None: if lang is None:
lang = get_language() lang = get_language()
@ -160,14 +160,14 @@ def localize_input(value, default=None):
return number_format(value) return number_format(value)
elif isinstance(value, datetime.datetime): elif isinstance(value, datetime.datetime):
value = datetime_safe.new_datetime(value) 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) return value.strftime(format)
elif isinstance(value, datetime.date): elif isinstance(value, datetime.date):
value = datetime_safe.new_date(value) 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) return value.strftime(format)
elif isinstance(value, datetime.time): 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.strftime(format)
return value return value

View File

@ -238,7 +238,6 @@ class LazyObject(object):
raise NotImplementedError raise NotImplementedError
# introspection support: # introspection support:
__members__ = property(lambda self: self.__dir__())
__dir__ = new_method_proxy(dir) __dir__ = new_method_proxy(dir)

View File

@ -11,7 +11,7 @@ except ImportError: # Python 2
from urlparse import urlsplit, urlunsplit from urlparse import urlsplit, urlunsplit
from django.utils.safestring import SafeData, mark_safe 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.functional import allow_lazy
from django.utils import six from django.utils import six
from django.utils.text import normalize_newlines 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. # contains a % not followed by two hexadecimal digits. See #9655.
if '%' not in url or unquoted_percents_re.search(url): if '%' not in url or unquoted_percents_re.search(url):
# See http://bugs.python.org/issue2637 # See http://bugs.python.org/issue2637
url = quote(smart_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~') url = quote(force_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~')
return force_text(url) return force_text(url)

View File

@ -15,7 +15,7 @@ except ImportError: # Python 2
from email.utils import formatdate from email.utils import formatdate
from django.utils.datastructures import MultiValueDict 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.functional import allow_lazy
from django.utils import six 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 can safely be used as part of an argument to a subsequent iri_to_uri() call
without double-quoting occurring. 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) urlquote = allow_lazy(urlquote, six.text_type)
def urlquote_plus(url, safe=''): 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 returned string can safely be used as part of an argument to a subsequent
iri_to_uri() call without double-quoting occurring. 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) urlquote_plus = allow_lazy(urlquote_plus, six.text_type)
def urlunquote(quoted_url): def urlunquote(quoted_url):
@ -57,7 +57,7 @@ def urlunquote(quoted_url):
A wrapper for Python's urllib.unquote() function that can operate on A wrapper for Python's urllib.unquote() function that can operate on
the result of django.utils.http.urlquote(). 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) urlunquote = allow_lazy(urlunquote, six.text_type)
def urlunquote_plus(quoted_url): 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 A wrapper for Python's urllib.unquote_plus() function that can operate on
the result of django.utils.http.urlquote_plus(). 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) urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type)
def urlencode(query, doseq=0): def urlencode(query, doseq=0):
@ -79,8 +79,8 @@ def urlencode(query, doseq=0):
elif hasattr(query, 'items'): elif hasattr(query, 'items'):
query = query.items() query = query.items()
return urllib_parse.urlencode( return urllib_parse.urlencode(
[(smart_str(k), [(force_str(k),
[smart_str(i) for i in v] if isinstance(v, (list,tuple)) else smart_str(v)) [force_str(i) for i in v] if isinstance(v, (list,tuple)) else force_str(v))
for k, v in query], for k, v in query],
doseq) doseq)

View File

@ -5,7 +5,7 @@ import sys
import types import types
__author__ = "Benjamin Peterson <benjamin@python.org>" __author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.1.0" __version__ = "1.2.0"
# True if we are running on Python 3. # True if we are running on Python 3.
@ -26,6 +26,10 @@ else:
text_type = unicode text_type = unicode
binary_type = str binary_type = str
if sys.platform == "java":
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t). # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object): class X(object):
def __len__(self): def __len__(self):
@ -201,12 +205,19 @@ else:
_iteritems = "iteritems" _iteritems = "iteritems"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
if PY3: if PY3:
def get_unbound_function(unbound): def get_unbound_function(unbound):
return unbound return unbound
Iterator = object
advance_iterator = next
def callable(obj): def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
@ -214,9 +225,10 @@ else:
def get_unbound_function(unbound): def get_unbound_function(unbound):
return unbound.im_func return unbound.im_func
class Iterator(object):
def advance_iterator(it): def next(self):
return it.next() return type(self).__next__(self)
callable = callable callable = callable
_add_doc(get_unbound_function, _add_doc(get_unbound_function,
@ -231,15 +243,15 @@ get_function_defaults = operator.attrgetter(_func_defaults)
def iterkeys(d): def iterkeys(d):
"""Return an iterator over the keys of a dictionary.""" """Return an iterator over the keys of a dictionary."""
return getattr(d, _iterkeys)() return iter(getattr(d, _iterkeys)())
def itervalues(d): def itervalues(d):
"""Return an iterator over the values of a dictionary.""" """Return an iterator over the values of a dictionary."""
return getattr(d, _itervalues)() return iter(getattr(d, _itervalues)())
def iteritems(d): def iteritems(d):
"""Return an iterator over the (key, value) pairs of a dictionary.""" """Return an iterator over the (key, value) pairs of a dictionary."""
return getattr(d, _iteritems)() return iter(getattr(d, _iteritems)())
if PY3: if PY3:

View File

@ -1,22 +1,22 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re import re
from django.utils import six
import unicodedata import unicodedata
import warnings import warnings
from gzip import GzipFile from gzip import GzipFile
from django.utils.six.moves import html_entities
from io import BytesIO from io import BytesIO
from django.utils.encoding import force_text 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: if not six.PY3:
# Import force_unicode even though this module doesn't use it, because some # Import force_unicode even though this module doesn't use it, because some
# people rely on it being here. # people rely on it being here.
from django.utils.encoding import force_unicode 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. # Capitalizes the first letter of a string.
capfirst = lambda x: x and force_text(x)[0].upper() + force_text(x)[1:] capfirst = lambda x: x and force_text(x)[0].upper() + force_text(x)[1:]

View File

@ -9,7 +9,7 @@ import gettext as gettext_module
from threading import local from threading import local
from django.utils.importlib import import_module 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.safestring import mark_safe, SafeData
from django.utils import six from django.utils import six
from django.utils.six import StringIO from django.utils.six import StringIO
@ -454,7 +454,7 @@ def templatize(src, origin=None):
from django.conf import settings from django.conf import settings
from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK, from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK) TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
src = smart_text(src, settings.FILE_CHARSET) src = force_text(src, settings.FILE_CHARSET)
out = StringIO() out = StringIO()
message_context = None message_context = None
intrans = False intrans = False
@ -469,7 +469,7 @@ def templatize(src, origin=None):
content = ''.join(comment) content = ''.join(comment)
translators_comment_start = None translators_comment_start = None
for lineno, line in enumerate(content.splitlines(True)): 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 translators_comment_start = lineno
for lineno, line in enumerate(content.splitlines(True)): for lineno, line in enumerate(content.splitlines(True)):
if translators_comment_start is not None and lineno >= translators_comment_start: 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) out.write(' # %s' % t.contents)
else: else:
out.write(blankout(t.contents, 'X')) out.write(blankout(t.contents, 'X'))
return smart_str(out.getvalue()) return force_str(out.getvalue())
def parse_accept_lang_header(lang_string): def parse_accept_lang_header(lang_string):
""" """

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import time import time
from datetime import timedelta, tzinfo 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 # 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 # be called with no arguments". FixedOffset and LocalTimezone don't honor this
@ -53,7 +53,7 @@ class LocalTimezone(tzinfo):
self._tzname = self.tzname(dt) self._tzname = self.tzname(dt)
def __repr__(self): def __repr__(self):
return smart_str(self._tzname) return force_str(self._tzname)
def __getinitargs__(self): def __getinitargs__(self):
return self.__dt, return self.__dt,
@ -72,7 +72,7 @@ class LocalTimezone(tzinfo):
def tzname(self, dt): def tzname(self, dt):
try: try:
return smart_text(time.tzname[self._isdst(dt)], return force_text(time.tzname[self._isdst(dt)],
DEFAULT_LOCALE_ENCODING) DEFAULT_LOCALE_ENCODING)
except UnicodeDecodeError: except UnicodeDecodeError:
return None return None

View File

@ -14,7 +14,7 @@ from django.template import Template, Context, TemplateDoesNotExist
from django.template.defaultfilters import force_escape, pprint from django.template.defaultfilters import force_escape, pprint
from django.utils.html import escape from django.utils.html import escape
from django.utils.importlib import import_module 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 from django.utils import six
HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE') 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, 'root_urlconf': settings.ROOT_URLCONF,
'request_path': request.path_info[1:], # Trim leading slash 'request_path': request.path_info[1:], # Trim leading slash
'urlpatterns': tried, 'urlpatterns': tried,
'reason': smart_bytes(exception, errors='replace'), 'reason': force_bytes(exception, errors='replace'),
'request': request, 'request': request,
'settings': get_safe_settings(), 'settings': get_safe_settings(),
}) })

View File

@ -61,7 +61,7 @@ def serve(request, path, document_root=None, show_indexes=False):
mimetype = mimetype or 'application/octet-stream' mimetype = mimetype or 'application/octet-stream'
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj.st_mtime, statobj.st_size): statobj.st_mtime, statobj.st_size):
return HttpResponseNotModified(content_type=mimetype) return HttpResponseNotModified()
with open(fullpath, 'rb') as f: with open(fullpath, 'rb') as f:
response = HttpResponse(f.read(), content_type=mimetype) response = HttpResponse(f.read(), content_type=mimetype)
response["Last-Modified"] = http_date(statobj.st_mtime) response["Last-Modified"] = http_date(statobj.st_mtime)

View File

@ -191,7 +191,8 @@ modindex_common_prefix = ["django."]
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
latex_elements = { 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 # Grouping the document tree into LaTeX files. List of tuples

View File

@ -185,6 +185,6 @@ would be happy to help you.
You might also be interested in posting a job to http://djangogigs.com/ . 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 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 .. _developers for hire page: https://code.djangoproject.com/wiki/DevelopersForHire

View File

@ -108,7 +108,8 @@ to know about views via the links below:
:doc:`Built-in display views <topics/class-based-views/generic-display>` | :doc:`Built-in display views <topics/class-based-views/generic-display>` |
:doc:`Built-in editing views <topics/class-based-views/generic-editing>` | :doc:`Built-in editing views <topics/class-based-views/generic-editing>` |
:doc:`Using mixins <topics/class-based-views/mixins>` | :doc:`Using mixins <topics/class-based-views/mixins>` |
:doc:`API reference <ref/class-based-views/index>` :doc:`API reference <ref/class-based-views/index>` |
:doc:`Flattened index<ref/class-based-views/flattened-index>`
* **Advanced:** * **Advanced:**
:doc:`Generating CSV <howto/outputting-csv>` | :doc:`Generating CSV <howto/outputting-csv>` |

View File

@ -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, 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. 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 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 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. 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 When he's not busy being an International Man of Mystery, Malcolm lives in
Sydney, Australia. Sydney, Australia.
.. _malcolm tredinnick: http://www.pointy-stick.com/
`Russell Keith-Magee`_ `Russell Keith-Magee`_
Russell studied physics as an undergraduate, and studied neural networks for 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 his PhD. His first job was with a startup in the defense industry developing

View File

@ -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</ref/class-based-views/index>`
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`

View File

@ -13,6 +13,7 @@ Class-based views API reference. For introductory material, see
generic-editing generic-editing
generic-date-based generic-date-based
mixins mixins
flattened-index
Specification Specification
------------- -------------

View File

@ -561,8 +561,6 @@ subclass::
.. attribute:: ModelAdmin.list_filter .. attribute:: ModelAdmin.list_filter
.. versionchanged:: 1.4
Set ``list_filter`` to activate filters in the right sidebar of the change Set ``list_filter`` to activate filters in the right sidebar of the change
list page of the admin, as illustrated in the following screenshot: list page of the admin, as illustrated in the following screenshot:
@ -586,6 +584,8 @@ subclass::
class PersonAdmin(UserAdmin): class PersonAdmin(UserAdmin):
list_filter = ('company__name',) list_filter = ('company__name',)
.. versionadded:: 1.4
* a class inheriting from :mod:`django.contrib.admin.SimpleListFilter`, * a class inheriting from :mod:`django.contrib.admin.SimpleListFilter`,
which you need to provide the ``title`` and ``parameter_name`` which you need to provide the ``title`` and ``parameter_name``
attributes to and override the ``lookups`` and ``queryset`` methods, attributes to and override the ``lookups`` and ``queryset`` methods,
@ -671,6 +671,8 @@ subclass::
birthday__lte=date(1999, 12, 31)).exists(): birthday__lte=date(1999, 12, 31)).exists():
yield ('90s', _('in the nineties')) yield ('90s', _('in the nineties'))
.. versionadded:: 1.4
* a tuple, where the first element is a field name and the second * a tuple, where the first element is a field name and the second
element is a class inheriting from element is a class inheriting from
:mod:`django.contrib.admin.FieldListFilter`, for example:: :mod:`django.contrib.admin.FieldListFilter`, for example::

View File

@ -158,11 +158,13 @@ For example::
.. warning:: .. 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 forgotten following a redirect. The practical impact for comments is that
the Safari/webkit browsers will arrive at the correct page but will not the Safari/webkit browsers will arrive at the correct page but will not
scroll to the named anchor. scroll to the named anchor.
.. _`known bug`: https://bugs.webkit.org/show_bug.cgi?id=24175
.. templatetag:: get_comment_count .. templatetag:: get_comment_count
Counting comments Counting comments

View File

@ -84,14 +84,24 @@ AJAX
While the above method can be used for AJAX POST requests, it has some 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 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 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 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 that allow headers to be set on every request.
``ajaxSend`` event as follows:
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 .. code-block:: javascript
jQuery(document).ajaxSend(function(event, xhr, settings) { // using jQuery
function getCookie(name) { function getCookie(name) {
var cookieValue = null; var cookieValue = null;
if (document.cookie && document.cookie != '') { if (document.cookie && document.cookie != '') {
@ -107,7 +117,42 @@ that allow headers to be set on every request. In jQuery, you can use the
} }
return cookieValue; return cookieValue;
} }
var csrftoken = getCookie('csrftoken');
The above code could be simplified by using the `jQuery cookie plugin
<http://plugins.jquery.com/project/Cookie>`_ 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) { function sameOrigin(url) {
// test that a given url is a same-origin URL
// url could be relative or scheme relative or absolute // url could be relative or scheme relative or absolute
var host = document.location.host; // host + port var host = document.location.host; // host + port
var protocol = document.location.protocol; var protocol = document.location.protocol;
@ -119,12 +164,14 @@ that allow headers to be set on every request. In jQuery, you can use the
// or any other URL that isn't scheme relative or absolute i.e relative. // or any other URL that isn't scheme relative or absolute i.e relative.
!(/^(\/\/|http:|https:).*/.test(url)); !(/^(\/\/|http:|https:).*/.test(url));
} }
function safeMethod(method) { $.ajaxSetup({
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 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);
} }
if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
xhr.setRequestHeader("X-CSRFToken", getCookie('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 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. 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 You can use `settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in
AJAX POST requests that are made via jQuery will not be caught by the CSRF jQuery 1.5 and newer in order to replace the `sameOrigin` logic above:
protection.
The above code could be simplified by using the `jQuery cookie plugin .. code-block:: javascript
<http://plugins.jquery.com/project/Cookie>`_ to replace ``getCookie``, and
`settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in jQuery 1.5 and
later to replace ``sameOrigin``.
In addition, if the CSRF cookie has not been sent to the client by use of function csrfSafeMethod(method) {
:ttag:`csrf_token`, you may need to ensure the client receives the cookie by // these HTTP methods do not require CSRF protection
using :func:`~django.views.decorators.csrf.ensure_csrf_cookie`. 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 Other template engines
---------------------- ----------------------

View File

@ -42,6 +42,16 @@ To install the flatpages app, follow these steps:
2. Add ``'django.contrib.flatpages'`` to your :setting:`INSTALLED_APPS` 2. Add ``'django.contrib.flatpages'`` to your :setting:`INSTALLED_APPS`
setting. 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'`` 3. Add ``'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'``
to your :setting:`MIDDLEWARE_CLASSES` setting. 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. that simply maps a URL to a title and bunch of text content.
``django_flatpage_sites`` associates a flatpage with a site. ``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<url>.*)$', '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` The :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`
does all of the work. can do all of the work.
.. class:: FlatpageFallbackMiddleware .. class:: FlatpageFallbackMiddleware
@ -255,4 +295,3 @@ For example:
{% get_flatpages '/about/' as about_pages %} {% get_flatpages '/about/' as about_pages %}
{% get_flatpages about_prefix as about_pages %} {% get_flatpages about_prefix as about_pages %}
{% get_flatpages '/about/' for someuser as about_pages %} {% get_flatpages '/about/' for someuser as about_pages %}

View File

@ -582,7 +582,7 @@ Creating a spatial database for SpatiaLite
After you've installed SpatiaLite, you'll need to create a number of spatial 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. 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:: call the ``InitSpatialMetaData()`` function, like this::
$ spatialite geodjango.db "SELECT InitSpatialMetaData();" $ 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 You can safely ignore the error messages shown. When you've done this, you can
skip the rest of this section. skip the rest of this section.
If you're using a version of SpatiaLite older than 3.0, you'll need to download If you're using SpatiaLite 2.3, you'll need to download a
a database-initialization file and execute its SQL queries in your database. database-initialization file and execute its SQL queries in your database.
First, get it from the appropriate SpatiaLite Resources page ( First, get it from the `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)::
$ wget http://www.gaia-gis.it/spatialite-2.3.1/init_spatialite-2.3.sql.gz $ wget http://www.gaia-gis.it/spatialite-2.3.1/init_spatialite-2.3.sql.gz
$ gunzip 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 you want to use. Use the same in the :setting:`DATABASES` ``"name"`` key
inside your ``settings.py``. inside your ``settings.py``.
__ http://www.gaia-gis.it/spatialite-2.3.1/resources.html
Add ``django.contrib.gis`` to :setting:`INSTALLED_APPS` Add ``django.contrib.gis`` to :setting:`INSTALLED_APPS`
------------------------------------------------------- -------------------------------------------------------
@ -820,8 +820,10 @@ Download the framework packages for:
* GDAL * GDAL
Install the packages in the order they are listed above, as the GDAL and SQLite 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 packages require the packages listed before them.
install the KyngChaos binary packages for `PostgreSQL and PostGIS`__.
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 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:: your ``.profile`` to be able to run the package programs from the command-line::

View File

@ -119,7 +119,7 @@ Settings
``SPATIALITE_SQL`` ``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 By default, the GeoDjango test runner looks for the :ref:`file containing the
SpatiaLite dababase-initialization SQL code <create_spatialite_db>` in the SpatiaLite dababase-initialization SQL code <create_spatialite_db>` in the

View File

@ -671,6 +671,17 @@ of abstraction::
__ http://spatialreference.org/ref/epsg/32140/ __ http://spatialreference.org/ref/epsg/32140/
.. admonition:: Raw queries
When using :doc:`raw queries </topics/db/sql>`, 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 Lazy Geometries
--------------- ---------------
Geometries come to GeoDjango in a standardized textual representation. Upon Geometries come to GeoDjango in a standardized textual representation. Upon

View File

@ -91,14 +91,18 @@ The ``ContentFile`` Class
.. class:: ContentFile(File) .. class:: ContentFile(File)
The ``ContentFile`` class inherits from :class:`~django.core.files.File`, The ``ContentFile`` class inherits from :class:`~django.core.files.File`,
but unlike :class:`~django.core.files.File` it operates on string content, but unlike :class:`~django.core.files.File` it operates on string content
rather than an actual file. For example:: (bytes also supported), rather than an actual file. For example::
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
f1 = ContentFile(b"my string content") f1 = ContentFile("esta sentencia está en español")
f2 = ContentFile("my unicode content encoded as UTF-8".encode('UTF-8')) f2 = ContentFile(b"these are bytes")
.. versionchanged:: 1.5
ContentFile also accepts Unicode strings.
.. currentmodule:: django.core.files.images .. currentmodule:: django.core.files.images

View File

@ -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 Note that this is different than :attr:`~Field.null`. :attr:`~Field.null` is
purely database-related, whereas :attr:`~Field.blank` is validation-related. If 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 a field has ``blank=True``, form validation will allow entry of an empty value.
of an empty value. If a field has ``blank=False``, the field will be required. If a field has ``blank=False``, the field will be required.
.. _field-choices: .. _field-choices:
@ -81,14 +81,11 @@ of an empty value. If a field has ``blank=False``, the field will be required.
.. attribute:: Field.choices .. attribute:: Field.choices
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this 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 The first element in each tuple is the actual value to be stored, and the
text field and will limit choices to the choices given. second element is the human-readable name. For example::
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::
YEAR_IN_SCHOOL_CHOICES = ( YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'), ('FR', 'Freshman'),
@ -176,7 +173,7 @@ scenes.
.. attribute:: Field.db_index .. attribute:: Field.db_index
If ``True``, djadmin:`django-admin.py sqlindexes <sqlindexes>` will output a If ``True``, :djadmin:`django-admin.py sqlindexes <sqlindexes>` will output a
``CREATE INDEX`` statement for this field. ``CREATE INDEX`` statement for this field.
``db_tablespace`` ``db_tablespace``
@ -203,8 +200,8 @@ callable it will be called every time a new object is created.
.. attribute:: Field.editable .. attribute:: Field.editable
If ``False``, the field will not be editable in the admin or via forms If ``False``, the field will not be displayed in the admin or any other
automatically generated from the model class. Default is ``True``. :class:`~django.forms.ModelForm`. Default is ``True``.
``error_messages`` ``error_messages``
------------------ ------------------
@ -224,11 +221,11 @@ the `Field types`_ section below.
.. attribute:: Field.help_text .. attribute:: Field.help_text
Extra "help" text to be displayed under the field on the object's admin form. Extra "help" text to be displayed with the form widget. It's useful for
It's useful for documentation even if your object doesn't have an admin form. 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 Note that this value is *not* HTML-escaped in automatically-generated
interface. This lets you include HTML in :attr:`~Field.help_text` if you so forms. This lets you include HTML in :attr:`~Field.help_text` if you so
desire. For example:: desire. For example::
help_text="Please use the following format: <em>YYYY-MM-DD</em>." help_text="Please use the following format: <em>YYYY-MM-DD</em>."
@ -259,7 +256,7 @@ Only one primary key is allowed on an object.
If ``True``, this field must be unique throughout the table. 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` 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 field, a :exc:`django.db.IntegrityError` will be raised by the model's
:meth:`~django.db.models.Model.save` method. :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 ``unique_for_date="pub_date"``, then Django wouldn't allow the entry of two
records with the same ``title`` and ``pub_date``. 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`` ``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 A 64 bit integer, much like an :class:`IntegerField` except that it is
guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The
admin represents this as an ``<input type="text">`` (a single-line input). default form widget for this field is a :class:`~django.forms.TextInput`.
``BooleanField`` ``BooleanField``
@ -347,7 +344,8 @@ admin represents this as an ``<input type="text">`` (a single-line input).
A true/false field. 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 If you need to accept :attr:`~Field.null` values then use
:class:`NullBooleanField` instead. :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`. For large amounts of text, use :class:`~django.db.models.TextField`.
The admin represents this as an ``<input type="text">`` (a single-line input). The default form widget for this field is a :class:`~django.forms.TextInput`.
:class:`CharField` has one extra required argument: :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; for creation of timestamps. Note that the current date is *always* used;
it's not just a default value that you can override. it's not just a default value that you can override.
The admin represents this as an ``<input type="text">`` with a JavaScript The default form widget for this field is a
calendar, and a shortcut for "Today". Includes an additional ``invalid_date`` :class:`~django.forms.TextInput`. The admin adds a JavaScript calendar,
error message key. and a shortcut for "Today". Includes an additional ``invalid_date`` error
message key.
.. note:: .. note::
As currently implemented, setting ``auto_now`` or ``auto_now_add`` to 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. A date and time, represented in Python by a ``datetime.datetime`` instance.
Takes the same extra arguments as :class:`DateField`. Takes the same extra arguments as :class:`DateField`.
The admin represents this as two ``<input type="text">`` fields, with The default form widget for this field is a single
JavaScript shortcuts. :class:`~django.forms.TextInput`. The admin uses two separate
:class:`~django.forms.TextInput` widgets with JavaScript shortcuts.
``DecimalField`` ``DecimalField``
---------------- ----------------
@ -461,7 +461,7 @@ decimal places::
models.DecimalField(..., max_digits=19, decimal_places=10) models.DecimalField(..., max_digits=19, decimal_places=10)
The admin represents this as an ``<input type="text">`` (a single-line input). The default form widget for this field is a :class:`~django.forms.TextInput`.
.. note:: .. note::
@ -539,8 +539,8 @@ Also has one optional argument:
Optional. A storage object, which handles the storage and retrieval of your 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. files. See :doc:`/topics/files` for details on how to provide this object.
The admin represents this field as an ``<input type="file">`` (a file-upload The default form widget for this field is a
widget). :class:`~django.forms.widgets.FileInput`.
Using a :class:`FileField` or an :class:`ImageField` (see below) in a model Using a :class:`FileField` or an :class:`ImageField` (see below) in a model
takes a few steps: 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. A floating-point number represented in Python by a ``float`` instance.
The admin represents this as an ``<input type="text">`` (a single-line input). The default form widget for this field is a :class:`~django.forms.TextInput`.
.. _floatfield_vs_decimalfield: .. _floatfield_vs_decimalfield:
@ -776,16 +776,16 @@ length using the :attr:`~CharField.max_length` argument.
.. class:: IntegerField([**options]) .. class:: IntegerField([**options])
An integer. The admin represents this as an ``<input type="text">`` (a An integer. The default form widget for this field is a
single-line input). :class:`~django.forms.TextInput`.
``IPAddressField`` ``IPAddressField``
------------------ ------------------
.. class:: IPAddressField([**options]) .. class:: IPAddressField([**options])
An IP address, in string format (e.g. "192.0.2.30"). The admin represents this An IP address, in string format (e.g. "192.0.2.30"). The default form widget
as an ``<input type="text">`` (a single-line input). for this field is a :class:`~django.forms.TextInput`.
``GenericIPAddressField`` ``GenericIPAddressField``
------------------------- -------------------------
@ -795,8 +795,8 @@ as an ``<input type="text">`` (a single-line input).
.. versionadded:: 1.4 .. versionadded:: 1.4
An IPv4 or IPv6 address, in string format (e.g. ``192.0.2.30`` or An IPv4 or IPv6 address, in string format (e.g. ``192.0.2.30`` or
``2a02:42fe::4``). The admin represents this as an ``<input type="text">`` ``2a02:42fe::4``). The default form widget for this field is a
(a single-line input). :class:`~django.forms.TextInput`.
The IPv6 address normalization follows :rfc:`4291#section-2.2` section 2.2, 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 including using the IPv4 format suggested in paragraph 3 of that section, like
@ -823,8 +823,8 @@ are converted to lowercase.
.. class:: NullBooleanField([**options]) .. class:: NullBooleanField([**options])
Like a :class:`BooleanField`, but allows ``NULL`` as one of the options. Use 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 instead of a :class:`BooleanField` with ``null=True``. The default form
this as a ``<select>`` box with "Unknown", "Yes" and "No" choices. widget for this field is a :class:`~django.forms.NullBooleanSelect`.
``PositiveIntegerField`` ``PositiveIntegerField``
------------------------ ------------------------
@ -875,8 +875,8 @@ Like an :class:`IntegerField`, but only allows values under a certain
.. class:: TextField([**options]) .. class:: TextField([**options])
A large text field. The admin represents this as a ``<textarea>`` (a multi-line A large text field. The default form widget for this field is a
input). :class:`~django.forms.Textarea`.
.. admonition:: MySQL users .. admonition:: MySQL users
@ -893,8 +893,8 @@ input).
A time, represented in Python by a ``datetime.time`` instance. Accepts the same A time, represented in Python by a ``datetime.time`` instance. Accepts the same
auto-population options as :class:`DateField`. auto-population options as :class:`DateField`.
The admin represents this as an ``<input type="text">`` with some JavaScript The default form widget for this field is a :class:`~django.forms.TextInput`.
shortcuts. The admin adds some JavaScript shortcuts.
``URLField`` ``URLField``
------------ ------------
@ -903,7 +903,7 @@ shortcuts.
A :class:`CharField` for a URL. A :class:`CharField` for a URL.
The admin represents this as an ``<input type="text">`` (a single-line input). The default form widget for this field is a :class:`~django.forms.TextInput`.
Like all :class:`CharField` subclasses, :class:`URLField` takes the optional Like all :class:`CharField` subclasses, :class:`URLField` takes the optional
:attr:`~CharField.max_length`argument. If you don't specify :attr:`~CharField.max_length`argument. If you don't specify
@ -979,9 +979,9 @@ define the details of how the relation works.
.. attribute:: ForeignKey.limit_choices_to .. attribute:: ForeignKey.limit_choices_to
A dictionary of lookup arguments and values (see :doc:`/topics/db/queries`) A dictionary of lookup arguments and values (see :doc:`/topics/db/queries`)
that limit the available admin choices for this object. Use this with that limit the available admin or ModelForm choices for this object. Use
functions from the Python ``datetime`` module to limit choices of objects by this with functions from the Python ``datetime`` module to limit choices of
date. For example:: objects by date. For example::
limit_choices_to = {'pub_date__lte': datetime.now} limit_choices_to = {'pub_date__lte': datetime.now}

View File

@ -48,7 +48,7 @@ that, you need to :meth:`~Model.save()`.
2. Add a method on a custom manager (usually preferred):: 2. Add a method on a custom manager (usually preferred)::
class BookManager(models.Manager): class BookManager(models.Manager):
def create_book(title): def create_book(self, title):
book = self.create(title=title) book = self.create(title=title)
# do something with the book # do something with the book
return book return book
@ -459,9 +459,9 @@ using ``__str__()`` like this::
last_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50)
def __str__(self): def __str__(self):
# Note use of django.utils.encoding.smart_bytes() here because # Note use of django.utils.encoding.force_bytes() here because
# first_name and last_name will be unicode strings. # first_name and last_name will be unicode strings.
return smart_bytes('%s %s' % (self.first_name, self.last_name)) return force_bytes('%s %s' % (self.first_name, self.last_name))
``get_absolute_url`` ``get_absolute_url``
-------------------- --------------------

View File

@ -2064,7 +2064,7 @@ Note this is only available in MySQL and requires direct manipulation of the
database to add the full-text index. By default Django uses BOOLEAN MODE for database to add the full-text index. By default Django uses BOOLEAN MODE for
full text searches. See the `MySQL documentation`_ for additional details. full text searches. See the `MySQL documentation`_ for additional details.
.. _MySQL documentation: http://dev.mysql.com/doc/refman/5.1/en/fulltext-boolean.html> .. _MySQL documentation: http://dev.mysql.com/doc/refman/5.1/en/fulltext-boolean.html
.. fieldlookup:: regex .. fieldlookup:: regex
@ -2236,4 +2236,3 @@ Variance
extension. extension.
.. _SQLite documentation: http://www.sqlite.org/contrib .. _SQLite documentation: http://www.sqlite.org/contrib

View File

@ -731,10 +731,11 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
.. class:: HttpResponseRedirect .. class:: HttpResponseRedirect
The constructor takes a single argument -- the path to redirect to. This The first argument to the constructor is required -- the path to redirect
can be a fully qualified URL (e.g. ``'http://www.yahoo.com/search/'``) or to. This can be a fully qualified URL
an absolute path with no domain (e.g. ``'/search/'``). Note that this (e.g. ``'http://www.yahoo.com/search/'``) or an absolute path with no
returns an HTTP status code 302. domain (e.g. ``'/search/'``). See :class:`HttpResponse` for other optional
constructor arguments. Note that this returns an HTTP status code 302.
.. class:: HttpResponsePermanentRedirect .. class:: HttpResponsePermanentRedirect
@ -743,8 +744,9 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
.. class:: HttpResponseNotModified .. class:: HttpResponseNotModified
The constructor doesn't take any arguments. Use this to designate that a The constructor doesn't take any arguments and no content should be added
page hasn't been modified since the user's last request (status code 304). to this response. Use this to designate that a page hasn't been modified
since the user's last request (status code 304).
.. class:: HttpResponseBadRequest .. class:: HttpResponseBadRequest
@ -760,8 +762,9 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
.. class:: HttpResponseNotAllowed .. class:: HttpResponseNotAllowed
Like :class:`HttpResponse`, but uses a 405 status code. Takes a single, Like :class:`HttpResponse`, but uses a 405 status code. The first argument
required argument: a list of permitted methods (e.g. ``['GET', 'POST']``). to the constructor is required: a list of permitted methods (e.g.
``['GET', 'POST']``).
.. class:: HttpResponseGone .. class:: HttpResponseGone

View File

@ -192,7 +192,7 @@ compose a prefix, version and key into a final cache key. The default
implementation is equivalent to the function:: implementation is equivalent to the function::
def make_key(key, key_prefix, version): def make_key(key, key_prefix, version):
return ':'.join([key_prefix, str(version), smart_bytes(key)]) return ':'.join([key_prefix, str(version), key])
You may use any key function you want, as long as it has the same You may use any key function you want, as long as it has the same
argument signature. argument signature.
@ -1375,8 +1375,8 @@ MEDIA_ROOT
Default: ``''`` (Empty string) Default: ``''`` (Empty string)
Absolute path to the directory that holds media for this installation, used Absolute filesystem path to the directory that will hold :doc:`user-uploaded
for :doc:`managing stored files </topics/files>`. files </topics/files>`.
Example: ``"/var/www/example.com/media/"`` Example: ``"/var/www/example.com/media/"``

View File

@ -125,6 +125,10 @@ dot in a variable name, it tries the following lookups, in this order:
* Attribute lookup. Example: ``foo.bar`` * Attribute lookup. Example: ``foo.bar``
* List-index 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 The template system uses the first lookup type that works. It's short-circuit
logic. Here are a few examples:: logic. Here are a few examples::
@ -424,6 +428,10 @@ optional, third positional argument, ``processors``. In this example, the
my_data_dictionary, my_data_dictionary,
context_instance=RequestContext(request)) 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: Here's what each of the default processors does:
django.contrib.auth.context_processors.auth django.contrib.auth.context_processors.auth

14
docs/releases/1.3.2.txt Normal file
View File

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

View File

@ -587,7 +587,7 @@ gettext domain):
ones listed later. ones listed later.
* The ``locale`` subdirectory of the directory containing the settings, that * 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 deprecated in this release as a source of translations. (the precedence of
these translations is intermediate between applications and :setting:`LOCALE_PATHS` these translations is intermediate between applications and :setting:`LOCALE_PATHS`
translations). See the `corresponding deprecated features section`_ translations). See the `corresponding deprecated features section`_

14
docs/releases/1.4.1.txt Normal file
View File

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

14
docs/releases/1.4.2.txt Normal file
View File

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

View File

@ -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 ``content_type``, you must encode your data before passing it to the test
client and set the ``content_type`` argument. client and set the ``content_type`` argument.
.. _simplejson-incompatibilities:
System version of :mod:`simplejson` no longer used
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:ref:`As explained below <simplejson-deprecation>`, 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 String types of hasher method parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -179,7 +227,7 @@ If you have written a :ref:`custom password hasher <auth_password_storage>`,
your ``encode()``, ``verify()`` or ``safe_summary()`` methods should accept your ``encode()``, ``verify()`` or ``safe_summary()`` methods should accept
Unicode parameters (``password``, ``salt`` or ``encoded``). If any of the Unicode parameters (``password``, ``salt`` or ``encoded``). If any of the
hashing methods need byte strings, you can use 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 Validation of previous_page_number and next_page_number
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -282,13 +330,21 @@ Miscellaneous
Features deprecated in 1.5 Features deprecated in 1.5
========================== ==========================
.. _simplejson-deprecation:
``django.utils.simplejson`` ``django.utils.simplejson``
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since Django 1.5 drops support for Python 2.5, we can now rely on the 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 :mod:`json` module being available in Python's standard library, so we've
our own copy of ``simplejson``. You can safely change any use of removed our own copy of :mod:`simplejson`. You should now import :mod:`json`
:mod:`django.utils.simplejson` to :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 <simplejson-incompatibilities>` 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`` ``itercompat.product``
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~

View File

@ -27,6 +27,8 @@ Final releases
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
.. 1.4.2 (uncomment on release)
1.4.1
1.4 1.4
1.3 release 1.3 release
@ -34,6 +36,7 @@ Final releases
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
1.3.2
1.3.1 1.3.1
1.3 1.3

View File

@ -460,7 +460,7 @@ algorithm.
Increasing the work factor 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 hashing. This deliberately slows down attackers, making attacks against hashed
passwords harder. However, as computing power increases, the number of passwords harder. However, as computing power increases, the number of
iterations needs to be increased. We've chosen a reasonable default (and will 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, down, depending on your security needs and available processing power. To do so,
you'll subclass the appropriate algorithm and override the ``iterations`` you'll subclass the appropriate algorithm and override the ``iterations``
parameters. For example, to increase the number of iterations used by the 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``:: 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. :file:`registration/password_reset_form.html` if not supplied.
* ``email_template_name``: The full name of a template to use for * ``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. :file:`registration/password_reset_email.html` if not supplied.
* ``subject_template_name``: The full name of a template to use for * ``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. to :file:`registration/password_reset_subject.txt` if not supplied.
.. versionadded:: 1.4 .. versionadded:: 1.4
* ``password_reset_form``: Form that will be used to set the password. * ``password_reset_form``: Form that will be used to get the email of
Defaults to :class:`~django.contrib.auth.forms.PasswordResetForm`. 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 * ``token_generator``: Instance of the class to check the one time link.
will default to ``default_token_generator``, it's an instance of This will default to ``default_token_generator``, it's an instance of
``django.contrib.auth.tokens.PasswordResetTokenGenerator``. ``django.contrib.auth.tokens.PasswordResetTokenGenerator``.
* ``post_reset_redirect``: The URL to redirect to after a successful * ``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 * ``from_email``: A valid email address. By default Django uses
the :setting:`DEFAULT_FROM_EMAIL`. 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. * ``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): Sample ``registration/password_reset_email.html`` (email body template):

Some files were not shown because too many files have changed in this diff Show More