Merge remote-tracking branch 'core/master' into schema-alteration
Conflicts: docs/ref/django-admin.txt
This commit is contained in:
commit
b6a957f0ba
5
AUTHORS
5
AUTHORS
|
@ -317,6 +317,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Michael Josephson <http://www.sdjournal.com/>
|
Michael Josephson <http://www.sdjournal.com/>
|
||||||
jpellerin@gmail.com
|
jpellerin@gmail.com
|
||||||
junzhang.jn@gmail.com
|
junzhang.jn@gmail.com
|
||||||
|
Krzysztof Jurewicz <krzysztof.jurewicz@gmail.com>
|
||||||
Xia Kai <http://blog.xiaket.org/>
|
Xia Kai <http://blog.xiaket.org/>
|
||||||
Antti Kaihola <http://djangopeople.net/akaihola/>
|
Antti Kaihola <http://djangopeople.net/akaihola/>
|
||||||
Peter van Kampen
|
Peter van Kampen
|
||||||
|
@ -418,6 +419,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Christian Metts
|
Christian Metts
|
||||||
michal@plovarna.cz
|
michal@plovarna.cz
|
||||||
Justin Michalicek <jmichalicek@gmail.com>
|
Justin Michalicek <jmichalicek@gmail.com>
|
||||||
|
Bojan Mihelac <bmihelac@mihelac.org>
|
||||||
Slawek Mikula <slawek dot mikula at gmail dot com>
|
Slawek Mikula <slawek dot mikula at gmail dot com>
|
||||||
Katie Miller <katie@sub50.com>
|
Katie Miller <katie@sub50.com>
|
||||||
Shawn Milochik <shawn@milochik.com>
|
Shawn Milochik <shawn@milochik.com>
|
||||||
|
@ -440,6 +442,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Gopal Narayanan <gopastro@gmail.com>
|
Gopal Narayanan <gopastro@gmail.com>
|
||||||
Fraser Nevett <mail@nevett.org>
|
Fraser Nevett <mail@nevett.org>
|
||||||
Sam Newman <http://www.magpiebrain.com/>
|
Sam Newman <http://www.magpiebrain.com/>
|
||||||
|
Alasdair Nicol <http://al.sdair.co.uk/>
|
||||||
Ryan Niemeyer <https://profiles.google.com/ryan.niemeyer/about>
|
Ryan Niemeyer <https://profiles.google.com/ryan.niemeyer/about>
|
||||||
Filip Noetzel <http://filip.noetzel.co.uk/>
|
Filip Noetzel <http://filip.noetzel.co.uk/>
|
||||||
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
|
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
|
||||||
|
@ -537,6 +540,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Brenton Simpson <http://theillustratedlife.com>
|
Brenton Simpson <http://theillustratedlife.com>
|
||||||
Jozko Skrablin <jozko.skrablin@gmail.com>
|
Jozko Skrablin <jozko.skrablin@gmail.com>
|
||||||
Ben Slavin <benjamin.slavin@gmail.com>
|
Ben Slavin <benjamin.slavin@gmail.com>
|
||||||
|
Jonathan Slenders
|
||||||
sloonz <simon.lipp@insa-lyon.fr>
|
sloonz <simon.lipp@insa-lyon.fr>
|
||||||
Paul Smith <blinkylights23@gmail.com>
|
Paul Smith <blinkylights23@gmail.com>
|
||||||
Steven L. Smith (fvox13) <steven@stevenlsmith.com>
|
Steven L. Smith (fvox13) <steven@stevenlsmith.com>
|
||||||
|
@ -573,6 +577,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Aaron Swartz <http://www.aaronsw.com/>
|
Aaron Swartz <http://www.aaronsw.com/>
|
||||||
Ville Säävuori <http://www.unessa.net/>
|
Ville Säävuori <http://www.unessa.net/>
|
||||||
Mart Sõmermaa <http://mrts.pri.ee/>
|
Mart Sõmermaa <http://mrts.pri.ee/>
|
||||||
|
Susan Tan <susan.tan.fleckerl@gmail.com>
|
||||||
Christian Tanzer <tanzer@swing.co.at>
|
Christian Tanzer <tanzer@swing.co.at>
|
||||||
Tyler Tarabula <tyler.tarabula@gmail.com>
|
Tyler Tarabula <tyler.tarabula@gmail.com>
|
||||||
Tyson Tate <tyson@fallingbullets.com>
|
Tyson Tate <tyson@fallingbullets.com>
|
||||||
|
|
|
@ -313,6 +313,11 @@ FILE_UPLOAD_TEMP_DIR = None
|
||||||
# you'd pass directly to os.chmod; see http://docs.python.org/lib/os-file-dir.html.
|
# you'd pass directly to os.chmod; see http://docs.python.org/lib/os-file-dir.html.
|
||||||
FILE_UPLOAD_PERMISSIONS = None
|
FILE_UPLOAD_PERMISSIONS = None
|
||||||
|
|
||||||
|
# The numeric mode to assign to newly-created directories, when uploading files.
|
||||||
|
# The value should be a mode as you'd pass to os.chmod;
|
||||||
|
# see http://docs.python.org/lib/os-file-dir.html.
|
||||||
|
FILE_UPLOAD_DIRECTORY_PERMISSIONS = None
|
||||||
|
|
||||||
# Python module path where user will place custom format definition.
|
# Python module path where user will place custom format definition.
|
||||||
# The directory where this setting is pointing should contain subdirectories
|
# The directory where this setting is pointing should contain subdirectories
|
||||||
# named as the locales, containing a formats.py file
|
# named as the locales, containing a formats.py file
|
||||||
|
|
|
@ -23,8 +23,8 @@ ul.actionlist li {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.actionlist li.changelink {
|
ul.actionlist li {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
-o-text-overflow: ellipsis;
|
-o-text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ from django.utils import timezone
|
||||||
from django.utils.encoding import force_str, force_text, smart_text
|
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, NoReverseMatch
|
||||||
|
|
||||||
def lookup_needs_distinct(opts, lookup_path):
|
def lookup_needs_distinct(opts, lookup_path):
|
||||||
"""
|
"""
|
||||||
|
@ -113,12 +113,20 @@ def get_deleted_objects(objs, opts, user, admin_site, using):
|
||||||
has_admin = obj.__class__ in admin_site._registry
|
has_admin = obj.__class__ in admin_site._registry
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
|
|
||||||
|
no_edit_link = '%s: %s' % (capfirst(opts.verbose_name),
|
||||||
|
force_text(obj))
|
||||||
|
|
||||||
if has_admin:
|
if has_admin:
|
||||||
admin_url = reverse('%s:%s_%s_change'
|
try:
|
||||||
% (admin_site.name,
|
admin_url = reverse('%s:%s_%s_change'
|
||||||
opts.app_label,
|
% (admin_site.name,
|
||||||
opts.model_name),
|
opts.app_label,
|
||||||
None, (quote(obj._get_pk_val()),))
|
opts.model_name),
|
||||||
|
None, (quote(obj._get_pk_val()),))
|
||||||
|
except NoReverseMatch:
|
||||||
|
# Change url doesn't exist -- don't display link to edit
|
||||||
|
return no_edit_link
|
||||||
|
|
||||||
p = '%s.%s' % (opts.app_label,
|
p = '%s.%s' % (opts.app_label,
|
||||||
get_permission_codename('delete', opts))
|
get_permission_codename('delete', opts))
|
||||||
if not user.has_perm(p):
|
if not user.has_perm(p):
|
||||||
|
@ -131,8 +139,7 @@ def get_deleted_objects(objs, opts, user, admin_site, using):
|
||||||
else:
|
else:
|
||||||
# Don't display link to edit, because it either has no
|
# Don't display link to edit, because it either has no
|
||||||
# admin or is edited inline.
|
# admin or is edited inline.
|
||||||
return '%s: %s' % (capfirst(opts.verbose_name),
|
return no_edit_link
|
||||||
force_text(obj))
|
|
||||||
|
|
||||||
to_delete = collector.nested(format_callback)
|
to_delete = collector.nested(format_callback)
|
||||||
|
|
||||||
|
@ -155,9 +162,6 @@ class NestedObjects(Collector):
|
||||||
if source_attr:
|
if source_attr:
|
||||||
self.add_edge(getattr(obj, source_attr), obj)
|
self.add_edge(getattr(obj, source_attr), obj)
|
||||||
else:
|
else:
|
||||||
if obj._meta.proxy:
|
|
||||||
# Take concrete model's instance to avoid mismatch in edges
|
|
||||||
obj = obj._meta.concrete_model(pk=obj.pk)
|
|
||||||
self.add_edge(None, obj)
|
self.add_edge(None, obj)
|
||||||
try:
|
try:
|
||||||
return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs)
|
return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs)
|
||||||
|
|
|
@ -305,9 +305,9 @@ class AdminURLFieldWidget(forms.URLInput):
|
||||||
html = super(AdminURLFieldWidget, self).render(name, value, attrs)
|
html = super(AdminURLFieldWidget, self).render(name, value, attrs)
|
||||||
if value:
|
if value:
|
||||||
value = force_text(self._format_value(value))
|
value = force_text(self._format_value(value))
|
||||||
final_attrs = {'href': mark_safe(smart_urlquote(value))}
|
final_attrs = {'href': smart_urlquote(value)}
|
||||||
html = format_html(
|
html = format_html(
|
||||||
'<p class="url">{0} <a {1}>{2}</a><br />{3} {4}</p>',
|
'<p class="url">{0} <a{1}>{2}</a><br />{3} {4}</p>',
|
||||||
_('Currently:'), flatatt(final_attrs), value,
|
_('Currently:'), flatatt(final_attrs), value,
|
||||||
_('Change:'), html
|
_('Change:'), html
|
||||||
)
|
)
|
||||||
|
|
|
@ -64,8 +64,12 @@ def permission_required(perm, login_url=None, raise_exception=False):
|
||||||
is raised.
|
is raised.
|
||||||
"""
|
"""
|
||||||
def check_perms(user):
|
def check_perms(user):
|
||||||
|
if not isinstance(perm, (list, tuple)):
|
||||||
|
perms = (perm, )
|
||||||
|
else:
|
||||||
|
perms = perm
|
||||||
# First check if the user has the permission (even anon users)
|
# First check if the user has the permission (even anon users)
|
||||||
if user.has_perm(perm):
|
if user.has_perms(perms):
|
||||||
return True
|
return True
|
||||||
# In case the 403 handler should be called raise the exception
|
# In case the 403 handler should be called raise the exception
|
||||||
if raise_exception:
|
if raise_exception:
|
||||||
|
|
|
@ -400,11 +400,11 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin):
|
||||||
"Returns the short name for the user."
|
"Returns the short name for the user."
|
||||||
return self.first_name
|
return self.first_name
|
||||||
|
|
||||||
def email_user(self, subject, message, from_email=None):
|
def email_user(self, subject, message, from_email=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Sends an email to this User.
|
Sends an email to this User.
|
||||||
"""
|
"""
|
||||||
send_mail(subject, message, from_email, [self.email])
|
send_mail(subject, message, from_email, [self.email], **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth import models
|
||||||
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
from django.contrib.auth.tests.test_views import AuthViewsTestCase
|
from django.contrib.auth.tests.test_views import AuthViewsTestCase
|
||||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
|
|
||||||
@skipIfCustomUser
|
@skipIfCustomUser
|
||||||
|
@ -49,3 +54,54 @@ class LoginRequiredTestCase(AuthViewsTestCase):
|
||||||
"""
|
"""
|
||||||
self.testLoginRequired(view_url='/login_required_login_url/',
|
self.testLoginRequired(view_url='/login_required_login_url/',
|
||||||
login_url='/somewhere/')
|
login_url='/somewhere/')
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionsRequiredDecoratorTest(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the permission_required decorator
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.user = models.User.objects.create(username='joe', password='qwerty')
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
# Add permissions auth.add_customuser and auth.change_customuser
|
||||||
|
perms = models.Permission.objects.filter(codename__in=('add_customuser', 'change_customuser'))
|
||||||
|
self.user.user_permissions.add(*perms)
|
||||||
|
|
||||||
|
def test_many_permissions_pass(self):
|
||||||
|
|
||||||
|
@permission_required(['auth.add_customuser', 'auth.change_customuser'])
|
||||||
|
def a_view(request):
|
||||||
|
return HttpResponse()
|
||||||
|
request = self.factory.get('/rand')
|
||||||
|
request.user = self.user
|
||||||
|
resp = a_view(request)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
def test_single_permission_pass(self):
|
||||||
|
|
||||||
|
@permission_required('auth.add_customuser')
|
||||||
|
def a_view(request):
|
||||||
|
return HttpResponse()
|
||||||
|
request = self.factory.get('/rand')
|
||||||
|
request.user = self.user
|
||||||
|
resp = a_view(request)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
def test_permissioned_denied_redirect(self):
|
||||||
|
|
||||||
|
@permission_required(['auth.add_customuser', 'auth.change_customuser', 'non-existant-permission'])
|
||||||
|
def a_view(request):
|
||||||
|
return HttpResponse()
|
||||||
|
request = self.factory.get('/rand')
|
||||||
|
request.user = self.user
|
||||||
|
resp = a_view(request)
|
||||||
|
self.assertEqual(resp.status_code, 302)
|
||||||
|
|
||||||
|
def test_permissioned_denied_exception_raised(self):
|
||||||
|
|
||||||
|
@permission_required(['auth.add_customuser', 'auth.change_customuser', 'non-existant-permission'], raise_exception=True)
|
||||||
|
def a_view(request):
|
||||||
|
return HttpResponse()
|
||||||
|
request = self.factory.get('/rand')
|
||||||
|
request.user = self.user
|
||||||
|
self.assertRaises(PermissionDenied, a_view, request)
|
||||||
|
|
|
@ -121,17 +121,16 @@ class AuthenticationFormTest(TestCase):
|
||||||
[force_text(form.error_messages['inactive'])])
|
[force_text(form.error_messages['inactive'])])
|
||||||
|
|
||||||
def test_inactive_user_i18n(self):
|
def test_inactive_user_i18n(self):
|
||||||
with self.settings(USE_I18N=True):
|
with self.settings(USE_I18N=True), translation.override('pt-br', deactivate=True):
|
||||||
with translation.override('pt-br', deactivate=True):
|
# The user is inactive.
|
||||||
# The user is inactive.
|
data = {
|
||||||
data = {
|
'username': 'inactive',
|
||||||
'username': 'inactive',
|
'password': 'password',
|
||||||
'password': 'password',
|
}
|
||||||
}
|
form = AuthenticationForm(None, data)
|
||||||
form = AuthenticationForm(None, data)
|
self.assertFalse(form.is_valid())
|
||||||
self.assertFalse(form.is_valid())
|
self.assertEqual(form.non_field_errors(),
|
||||||
self.assertEqual(form.non_field_errors(),
|
[force_text(form.error_messages['inactive'])])
|
||||||
[force_text(form.error_messages['inactive'])])
|
|
||||||
|
|
||||||
def test_custom_login_allowed_policy(self):
|
def test_custom_login_allowed_policy(self):
|
||||||
# The user is inactive, but our custom form policy allows him to log in.
|
# The user is inactive, but our custom form policy allows him to log in.
|
||||||
|
|
|
@ -239,21 +239,22 @@ class PermissionTestCase(TestCase):
|
||||||
create_permissions(models, [], verbosity=0)
|
create_permissions(models, [], verbosity=0)
|
||||||
|
|
||||||
def test_default_permissions(self):
|
def test_default_permissions(self):
|
||||||
|
permission_content_type = ContentType.objects.get_by_natural_key('auth', 'permission')
|
||||||
models.Permission._meta.permissions = [
|
models.Permission._meta.permissions = [
|
||||||
('my_custom_permission', 'Some permission'),
|
('my_custom_permission', 'Some permission'),
|
||||||
]
|
]
|
||||||
create_permissions(models, [], verbosity=0)
|
create_permissions(models, [], verbosity=0)
|
||||||
|
|
||||||
# add/change/delete permission by default + custom permission
|
# add/change/delete permission by default + custom permission
|
||||||
self.assertEqual(models.Permission.objects.filter(content_type=
|
self.assertEqual(models.Permission.objects.filter(
|
||||||
ContentType.objects.get_by_natural_key('auth', 'permission')
|
content_type=permission_content_type,
|
||||||
).count(), 4)
|
).count(), 4)
|
||||||
|
|
||||||
models.Permission.objects.all().delete()
|
models.Permission.objects.filter(content_type=permission_content_type).delete()
|
||||||
models.Permission._meta.default_permissions = []
|
models.Permission._meta.default_permissions = []
|
||||||
create_permissions(models, [], verbosity=0)
|
create_permissions(models, [], verbosity=0)
|
||||||
|
|
||||||
# custom permission only since default permissions is empty
|
# custom permission only since default permissions is empty
|
||||||
self.assertEqual(models.Permission.objects.filter(content_type=
|
self.assertEqual(models.Permission.objects.filter(
|
||||||
ContentType.objects.get_by_natural_key('auth', 'permission')
|
content_type=permission_content_type,
|
||||||
).count(), 1)
|
).count(), 1)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group, User, UserManager
|
from django.contrib.auth.models import AbstractUser, Group, User, UserManager
|
||||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||||
|
from django.core import mail
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
@ -73,6 +74,29 @@ class UserManagerTestCase(TestCase):
|
||||||
User.objects.create_user, username='')
|
User.objects.create_user, username='')
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractUserTestCase(TestCase):
|
||||||
|
def test_email_user(self):
|
||||||
|
# valid send_mail parameters
|
||||||
|
kwargs = {
|
||||||
|
"fail_silently": False,
|
||||||
|
"auth_user": None,
|
||||||
|
"auth_password": None,
|
||||||
|
"connection": None,
|
||||||
|
"html_message": None,
|
||||||
|
}
|
||||||
|
abstract_user = AbstractUser(email='foo@bar.com')
|
||||||
|
abstract_user.email_user(subject="Subject here",
|
||||||
|
message="This is a message", from_email="from@domain.com", **kwargs)
|
||||||
|
# Test that one message has been sent.
|
||||||
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
# Verify that test email contains the correct attributes:
|
||||||
|
message = mail.outbox[0]
|
||||||
|
self.assertEqual(message.subject, "Subject here")
|
||||||
|
self.assertEqual(message.body, "This is a message")
|
||||||
|
self.assertEqual(message.from_email, "from@domain.com")
|
||||||
|
self.assertEqual(message.to, [abstract_user.email])
|
||||||
|
|
||||||
|
|
||||||
class IsActiveTestCase(TestCase):
|
class IsActiveTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests the behavior of the guaranteed is_active attribute
|
Tests the behavior of the guaranteed is_active attribute
|
||||||
|
|
|
@ -446,7 +446,8 @@ class LoginTest(AuthViewsTestCase):
|
||||||
for bad_url in ('http://example.com',
|
for bad_url in ('http://example.com',
|
||||||
'https://example.com',
|
'https://example.com',
|
||||||
'ftp://exampel.com',
|
'ftp://exampel.com',
|
||||||
'//example.com'):
|
'//example.com',
|
||||||
|
'javascript:alert("XSS")'):
|
||||||
|
|
||||||
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
|
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
|
||||||
'url': login_url,
|
'url': login_url,
|
||||||
|
@ -467,6 +468,7 @@ class LoginTest(AuthViewsTestCase):
|
||||||
'/view?param=ftp://exampel.com',
|
'/view?param=ftp://exampel.com',
|
||||||
'view/?param=//example.com',
|
'view/?param=//example.com',
|
||||||
'https:///',
|
'https:///',
|
||||||
|
'HTTPS:///',
|
||||||
'//testserver/',
|
'//testserver/',
|
||||||
'/url%20with%20spaces/'): # see ticket #12534
|
'/url%20with%20spaces/'): # see ticket #12534
|
||||||
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
|
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
|
||||||
|
@ -661,7 +663,8 @@ class LogoutTest(AuthViewsTestCase):
|
||||||
for bad_url in ('http://example.com',
|
for bad_url in ('http://example.com',
|
||||||
'https://example.com',
|
'https://example.com',
|
||||||
'ftp://exampel.com',
|
'ftp://exampel.com',
|
||||||
'//example.com'):
|
'//example.com',
|
||||||
|
'javascript:alert("XSS")'):
|
||||||
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
|
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
|
||||||
'url': logout_url,
|
'url': logout_url,
|
||||||
'next': REDIRECT_FIELD_NAME,
|
'next': REDIRECT_FIELD_NAME,
|
||||||
|
@ -680,6 +683,7 @@ class LogoutTest(AuthViewsTestCase):
|
||||||
'/view?param=ftp://exampel.com',
|
'/view?param=ftp://exampel.com',
|
||||||
'view/?param=//example.com',
|
'view/?param=//example.com',
|
||||||
'https:///',
|
'https:///',
|
||||||
|
'HTTPS:///',
|
||||||
'//testserver/',
|
'//testserver/',
|
||||||
'/url%20with%20spaces/'): # see ticket #12534
|
'/url%20with%20spaces/'): # see ticket #12534
|
||||||
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
|
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
|
||||||
|
|
|
@ -9,6 +9,14 @@ class PostGISIntrospection(DatabaseIntrospection):
|
||||||
# introspection is actually performed.
|
# introspection is actually performed.
|
||||||
postgis_types_reverse = {}
|
postgis_types_reverse = {}
|
||||||
|
|
||||||
|
ignored_tables = DatabaseIntrospection.ignored_tables + [
|
||||||
|
'geography_columns',
|
||||||
|
'geometry_columns',
|
||||||
|
'raster_columns',
|
||||||
|
'spatial_ref_sys',
|
||||||
|
'raster_overviews',
|
||||||
|
]
|
||||||
|
|
||||||
def get_postgis_types(self):
|
def get_postgis_types(self):
|
||||||
"""
|
"""
|
||||||
Returns a dictionary with keys that are the PostgreSQL object
|
Returns a dictionary with keys that are the PostgreSQL object
|
||||||
|
|
|
@ -76,7 +76,7 @@ class GeoQuery(sql.Query):
|
||||||
return super(GeoQuery, self).convert_values(value, field, connection)
|
return super(GeoQuery, self).convert_values(value, field, connection)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_aggregation(self, using):
|
def get_aggregation(self, using, force_subq=False):
|
||||||
# Remove any aggregates marked for reduction from the subquery
|
# Remove any aggregates marked for reduction from the subquery
|
||||||
# and move them to the outer AggregateQuery.
|
# and move them to the outer AggregateQuery.
|
||||||
connection = connections[using]
|
connection = connections[using]
|
||||||
|
@ -84,7 +84,7 @@ class GeoQuery(sql.Query):
|
||||||
if isinstance(aggregate, gis_aggregates.GeoAggregate):
|
if isinstance(aggregate, gis_aggregates.GeoAggregate):
|
||||||
if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
|
if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
|
||||||
self.extra_select_fields[alias] = GeomField()
|
self.extra_select_fields[alias] = GeomField()
|
||||||
return super(GeoQuery, self).get_aggregation(using)
|
return super(GeoQuery, self).get_aggregation(using, force_subq)
|
||||||
|
|
||||||
def resolve_aggregate(self, value, aggregate, connection):
|
def resolve_aggregate(self, value, aggregate, connection):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -40,7 +40,7 @@ class Track(models.Model):
|
||||||
def __str__(self): return self.name
|
def __str__(self): return self.name
|
||||||
|
|
||||||
class Truth(models.Model):
|
class Truth(models.Model):
|
||||||
val = models.BooleanField()
|
val = models.BooleanField(default=False)
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
|
|
||||||
if not spatialite:
|
if not spatialite:
|
||||||
|
|
|
@ -77,15 +77,14 @@ class HumanizeTests(TransRealMixin, TestCase):
|
||||||
'100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567', '1,234,567.1234567',
|
'100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567', '1,234,567.1234567',
|
||||||
None)
|
None)
|
||||||
|
|
||||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False):
|
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False), \
|
||||||
with translation.override('en'):
|
translation.override('en'):
|
||||||
self.humanize_tester(test_list, result_list, 'intcomma')
|
self.humanize_tester(test_list, result_list, 'intcomma')
|
||||||
|
|
||||||
def test_intcomma_without_number_grouping(self):
|
def test_intcomma_without_number_grouping(self):
|
||||||
# Regression for #17414
|
# Regression for #17414
|
||||||
with translation.override('ja'):
|
with translation.override('ja'), self.settings(USE_L10N=True):
|
||||||
with self.settings(USE_L10N=True):
|
self.humanize_tester([100], ['100'], 'intcomma')
|
||||||
self.humanize_tester([100], ['100'], 'intcomma')
|
|
||||||
|
|
||||||
def test_intword(self):
|
def test_intword(self):
|
||||||
test_list = ('100', '1000000', '1200000', '1290000',
|
test_list = ('100', '1000000', '1200000', '1290000',
|
||||||
|
@ -104,18 +103,18 @@ class HumanizeTests(TransRealMixin, TestCase):
|
||||||
'100', '1000', '10123', '10311', '1000000', None)
|
'100', '1000', '10123', '10311', '1000000', None)
|
||||||
result_list = ('100', '1.000', '10.123', '10.311', '1.000.000', '1.234.567,25',
|
result_list = ('100', '1.000', '10.123', '10.311', '1.000.000', '1.234.567,25',
|
||||||
'100', '1.000', '10.123', '10.311', '1.000.000', None)
|
'100', '1.000', '10.123', '10.311', '1.000.000', None)
|
||||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True):
|
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True), \
|
||||||
with translation.override('de'):
|
translation.override('de'):
|
||||||
self.humanize_tester(test_list, result_list, 'intcomma')
|
self.humanize_tester(test_list, result_list, 'intcomma')
|
||||||
|
|
||||||
def test_i18n_intword(self):
|
def test_i18n_intword(self):
|
||||||
test_list = ('100', '1000000', '1200000', '1290000',
|
test_list = ('100', '1000000', '1200000', '1290000',
|
||||||
'1000000000', '2000000000', '6000000000000')
|
'1000000000', '2000000000', '6000000000000')
|
||||||
result_list = ('100', '1,0 Million', '1,2 Millionen', '1,3 Millionen',
|
result_list = ('100', '1,0 Million', '1,2 Millionen', '1,3 Millionen',
|
||||||
'1,0 Milliarde', '2,0 Milliarden', '6,0 Billionen')
|
'1,0 Milliarde', '2,0 Milliarden', '6,0 Billionen')
|
||||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True):
|
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True), \
|
||||||
with translation.override('de'):
|
translation.override('de'):
|
||||||
self.humanize_tester(test_list, result_list, 'intword')
|
self.humanize_tester(test_list, result_list, 'intword')
|
||||||
|
|
||||||
def test_apnumber(self):
|
def test_apnumber(self):
|
||||||
test_list = [str(x) for x in range(1, 11)]
|
test_list = [str(x) for x in range(1, 11)]
|
||||||
|
@ -162,9 +161,9 @@ class HumanizeTests(TransRealMixin, TestCase):
|
||||||
|
|
||||||
orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime
|
orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime
|
||||||
try:
|
try:
|
||||||
with override_settings(TIME_ZONE="America/Chicago", USE_TZ=True):
|
with override_settings(TIME_ZONE="America/Chicago", USE_TZ=True), \
|
||||||
with translation.override('en'):
|
translation.override('en'):
|
||||||
self.humanize_tester([dt], ['yesterday'], 'naturalday')
|
self.humanize_tester([dt], ['yesterday'], 'naturalday')
|
||||||
finally:
|
finally:
|
||||||
humanize.datetime = orig_humanize_datetime
|
humanize.datetime = orig_humanize_datetime
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
def check_test_runner():
|
def check_test_runner():
|
||||||
"""
|
"""
|
||||||
|
@ -24,6 +25,31 @@ def check_test_runner():
|
||||||
]
|
]
|
||||||
return ' '.join(message)
|
return ' '.join(message)
|
||||||
|
|
||||||
|
def check_boolean_field_default_value():
|
||||||
|
"""
|
||||||
|
Checks if there are any BooleanFields without a default value, &
|
||||||
|
warns the user that the default has changed from False to Null.
|
||||||
|
"""
|
||||||
|
fields = []
|
||||||
|
for cls in models.get_models():
|
||||||
|
opts = cls._meta
|
||||||
|
for f in opts.local_fields:
|
||||||
|
if isinstance(f, models.BooleanField) and not f.has_default():
|
||||||
|
fields.append(
|
||||||
|
'%s.%s: "%s"' % (opts.app_label, opts.object_name, f.name)
|
||||||
|
)
|
||||||
|
if fields:
|
||||||
|
fieldnames = ", ".join(fields)
|
||||||
|
message = [
|
||||||
|
"You have not set a default value for one or more BooleanFields:",
|
||||||
|
"%s." % fieldnames,
|
||||||
|
"In Django 1.6 the default value of BooleanField was changed from",
|
||||||
|
"False to Null when Field.default isn't defined. See",
|
||||||
|
"https://docs.djangoproject.com/en/1.6/ref/models/fields/#booleanfield"
|
||||||
|
"for more information."
|
||||||
|
]
|
||||||
|
return ' '.join(message)
|
||||||
|
|
||||||
|
|
||||||
def run_checks():
|
def run_checks():
|
||||||
"""
|
"""
|
||||||
|
@ -31,7 +57,8 @@ def run_checks():
|
||||||
messages from all the relevant check functions for this version of Django.
|
messages from all the relevant check functions for this version of Django.
|
||||||
"""
|
"""
|
||||||
checks = [
|
checks = [
|
||||||
check_test_runner()
|
check_test_runner(),
|
||||||
|
check_boolean_field_default_value(),
|
||||||
]
|
]
|
||||||
# Filter out the ``None`` or empty strings.
|
# Filter out the ``None`` or empty strings.
|
||||||
return [output for output in checks if output]
|
return [output for output in checks if output]
|
||||||
|
|
|
@ -172,7 +172,16 @@ class FileSystemStorage(Storage):
|
||||||
directory = os.path.dirname(full_path)
|
directory = os.path.dirname(full_path)
|
||||||
if not os.path.exists(directory):
|
if not os.path.exists(directory):
|
||||||
try:
|
try:
|
||||||
os.makedirs(directory)
|
if settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS is not None:
|
||||||
|
# os.makedirs applies the global umask, so we reset it,
|
||||||
|
# for consistency with FILE_UPLOAD_PERMISSIONS behavior.
|
||||||
|
old_umask = os.umask(0)
|
||||||
|
try:
|
||||||
|
os.makedirs(directory, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS)
|
||||||
|
finally:
|
||||||
|
os.umask(old_umask)
|
||||||
|
else:
|
||||||
|
os.makedirs(directory)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno != errno.EEXIST:
|
if e.errno != errno.EEXIST:
|
||||||
raise
|
raise
|
||||||
|
|
|
@ -26,6 +26,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
1700: 'DecimalField',
|
1700: 'DecimalField',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ignored_tables = []
|
||||||
|
|
||||||
def get_table_list(self, cursor):
|
def get_table_list(self, cursor):
|
||||||
"Returns a list of table names in the current database."
|
"Returns a list of table names in the current database."
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
|
@ -35,7 +37,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
WHERE c.relkind IN ('r', 'v', '')
|
WHERE c.relkind IN ('r', 'v', '')
|
||||||
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
||||||
AND pg_catalog.pg_table_is_visible(c.oid)""")
|
AND pg_catalog.pg_table_is_visible(c.oid)""")
|
||||||
return [row[0] for row in cursor.fetchall()]
|
return [row[0] for row in cursor.fetchall() if row[0] not in self.ignored_tables]
|
||||||
|
|
||||||
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."
|
||||||
|
|
|
@ -184,10 +184,21 @@ class ModelBase(type):
|
||||||
else:
|
else:
|
||||||
new_class._meta.concrete_model = new_class
|
new_class._meta.concrete_model = new_class
|
||||||
|
|
||||||
# Do the appropriate setup for any model parents.
|
# Collect the parent links for multi-table inheritance.
|
||||||
o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
|
parent_links = {}
|
||||||
if isinstance(f, OneToOneField)])
|
for base in reversed([new_class] + parents):
|
||||||
|
# Conceptually equivalent to `if base is Model`.
|
||||||
|
if not hasattr(base, '_meta'):
|
||||||
|
continue
|
||||||
|
# Skip concrete parent classes.
|
||||||
|
if base != new_class and not base._meta.abstract:
|
||||||
|
continue
|
||||||
|
# Locate OneToOneField instances.
|
||||||
|
for field in base._meta.local_fields:
|
||||||
|
if isinstance(field, OneToOneField):
|
||||||
|
parent_links[field.rel.to] = field
|
||||||
|
|
||||||
|
# Do the appropriate setup for any model parents.
|
||||||
for base in parents:
|
for base in parents:
|
||||||
original_base = base
|
original_base = base
|
||||||
if not hasattr(base, '_meta'):
|
if not hasattr(base, '_meta'):
|
||||||
|
@ -208,8 +219,8 @@ class ModelBase(type):
|
||||||
if not base._meta.abstract:
|
if not base._meta.abstract:
|
||||||
# Concrete classes...
|
# Concrete classes...
|
||||||
base = base._meta.concrete_model
|
base = base._meta.concrete_model
|
||||||
if base in o2o_map:
|
if base in parent_links:
|
||||||
field = o2o_map[base]
|
field = parent_links[base]
|
||||||
elif not is_proxy:
|
elif not is_proxy:
|
||||||
attr_name = '%s_ptr' % base._meta.model_name
|
attr_name = '%s_ptr' % base._meta.model_name
|
||||||
field = OneToOneField(base, name=attr_name,
|
field = OneToOneField(base, name=attr_name,
|
||||||
|
@ -448,7 +459,9 @@ class Model(six.with_metaclass(ModelBase)):
|
||||||
return '%s object' % self.__class__.__name__
|
return '%s object' % self.__class__.__name__
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
|
return (isinstance(other, Model) and
|
||||||
|
self._meta.concrete_model == other._meta.concrete_model and
|
||||||
|
self._get_pk_val() == other._get_pk_val())
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
|
@ -313,14 +313,13 @@ class QuerySet(object):
|
||||||
kwargs[arg.default_alias] = arg
|
kwargs[arg.default_alias] = arg
|
||||||
|
|
||||||
query = self.query.clone()
|
query = self.query.clone()
|
||||||
|
force_subq = query.low_mark != 0 or query.high_mark is not None
|
||||||
aggregate_names = []
|
aggregate_names = []
|
||||||
for (alias, aggregate_expr) in kwargs.items():
|
for (alias, aggregate_expr) in kwargs.items():
|
||||||
query.add_aggregate(aggregate_expr, self.model, alias,
|
query.add_aggregate(aggregate_expr, self.model, alias,
|
||||||
is_summary=True)
|
is_summary=True)
|
||||||
aggregate_names.append(alias)
|
aggregate_names.append(alias)
|
||||||
|
return query.get_aggregation(using=self.db, force_subq=force_subq)
|
||||||
return query.get_aggregation(using=self.db)
|
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -167,7 +167,6 @@ class SQLCompiler(object):
|
||||||
if obj.low_mark == 0 and obj.high_mark is None:
|
if obj.low_mark == 0 and obj.high_mark is None:
|
||||||
# If there is no slicing in use, then we can safely drop all ordering
|
# If there is no slicing in use, then we can safely drop all ordering
|
||||||
obj.clear_ordering(True)
|
obj.clear_ordering(True)
|
||||||
obj.bump_prefix()
|
|
||||||
return obj.get_compiler(connection=self.connection).as_sql()
|
return obj.get_compiler(connection=self.connection).as_sql()
|
||||||
|
|
||||||
def get_columns(self, with_aliases=False):
|
def get_columns(self, with_aliases=False):
|
||||||
|
@ -808,13 +807,14 @@ class SQLCompiler(object):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def as_subquery_condition(self, alias, columns, qn):
|
def as_subquery_condition(self, alias, columns, qn):
|
||||||
|
inner_qn = self.quote_name_unless_alias
|
||||||
qn2 = self.connection.ops.quote_name
|
qn2 = self.connection.ops.quote_name
|
||||||
if len(columns) == 1:
|
if len(columns) == 1:
|
||||||
sql, params = self.as_sql()
|
sql, params = self.as_sql()
|
||||||
return '%s.%s IN (%s)' % (qn(alias), qn2(columns[0]), sql), params
|
return '%s.%s IN (%s)' % (qn(alias), qn2(columns[0]), sql), params
|
||||||
|
|
||||||
for index, select_col in enumerate(self.query.select):
|
for index, select_col in enumerate(self.query.select):
|
||||||
lhs = '%s.%s' % (qn(select_col.col[0]), qn2(select_col.col[1]))
|
lhs = '%s.%s' % (inner_qn(select_col.col[0]), qn2(select_col.col[1]))
|
||||||
rhs = '%s.%s' % (qn(alias), qn2(columns[index]))
|
rhs = '%s.%s' % (qn(alias), qn2(columns[index]))
|
||||||
self.query.where.add(
|
self.query.where.add(
|
||||||
QueryWrapper('%s = %s' % (lhs, rhs), []), 'AND')
|
QueryWrapper('%s = %s' % (lhs, rhs), []), 'AND')
|
||||||
|
@ -1010,7 +1010,6 @@ class SQLUpdateCompiler(SQLCompiler):
|
||||||
# We need to use a sub-select in the where clause to filter on things
|
# We need to use a sub-select in the where clause to filter on things
|
||||||
# from other tables.
|
# from other tables.
|
||||||
query = self.query.clone(klass=Query)
|
query = self.query.clone(klass=Query)
|
||||||
query.bump_prefix()
|
|
||||||
query.extra = {}
|
query.extra = {}
|
||||||
query.select = []
|
query.select = []
|
||||||
query.add_fields([query.get_meta().pk.name])
|
query.add_fields([query.get_meta().pk.name])
|
||||||
|
|
|
@ -97,6 +97,7 @@ class Query(object):
|
||||||
LOUTER = 'LEFT OUTER JOIN'
|
LOUTER = 'LEFT OUTER JOIN'
|
||||||
|
|
||||||
alias_prefix = 'T'
|
alias_prefix = 'T'
|
||||||
|
subq_aliases = frozenset([alias_prefix])
|
||||||
query_terms = QUERY_TERMS
|
query_terms = QUERY_TERMS
|
||||||
aggregates_module = base_aggregates_module
|
aggregates_module = base_aggregates_module
|
||||||
|
|
||||||
|
@ -273,6 +274,10 @@ class Query(object):
|
||||||
else:
|
else:
|
||||||
obj.used_aliases = set()
|
obj.used_aliases = set()
|
||||||
obj.filter_is_sticky = False
|
obj.filter_is_sticky = False
|
||||||
|
if 'alias_prefix' in self.__dict__:
|
||||||
|
obj.alias_prefix = self.alias_prefix
|
||||||
|
if 'subq_aliases' in self.__dict__:
|
||||||
|
obj.subq_aliases = self.subq_aliases.copy()
|
||||||
|
|
||||||
obj.__dict__.update(kwargs)
|
obj.__dict__.update(kwargs)
|
||||||
if hasattr(obj, '_setup_query'):
|
if hasattr(obj, '_setup_query'):
|
||||||
|
@ -310,7 +315,7 @@ class Query(object):
|
||||||
# Return value depends on the type of the field being processed.
|
# Return value depends on the type of the field being processed.
|
||||||
return self.convert_values(value, aggregate.field, connection)
|
return self.convert_values(value, aggregate.field, connection)
|
||||||
|
|
||||||
def get_aggregation(self, using):
|
def get_aggregation(self, using, force_subq=False):
|
||||||
"""
|
"""
|
||||||
Returns the dictionary with the values of the existing aggregations.
|
Returns the dictionary with the values of the existing aggregations.
|
||||||
"""
|
"""
|
||||||
|
@ -320,18 +325,26 @@ class Query(object):
|
||||||
# If there is a group by clause, aggregating does not add useful
|
# If there is a group by clause, aggregating does not add useful
|
||||||
# information but retrieves only the first row. Aggregate
|
# information but retrieves only the first row. Aggregate
|
||||||
# over the subquery instead.
|
# over the subquery instead.
|
||||||
if self.group_by is not None:
|
if self.group_by is not None or force_subq:
|
||||||
|
|
||||||
from django.db.models.sql.subqueries import AggregateQuery
|
from django.db.models.sql.subqueries import AggregateQuery
|
||||||
query = AggregateQuery(self.model)
|
query = AggregateQuery(self.model)
|
||||||
|
|
||||||
obj = self.clone()
|
obj = self.clone()
|
||||||
|
if not force_subq:
|
||||||
|
# In forced subq case the ordering and limits will likely
|
||||||
|
# affect the results.
|
||||||
|
obj.clear_ordering(True)
|
||||||
|
obj.clear_limits()
|
||||||
|
obj.select_for_update = False
|
||||||
|
obj.select_related = False
|
||||||
|
obj.related_select_cols = []
|
||||||
|
|
||||||
|
relabels = dict((t, 'subquery') for t in self.tables)
|
||||||
# Remove any aggregates marked for reduction from the subquery
|
# Remove any aggregates marked for reduction from the subquery
|
||||||
# and move them to the outer AggregateQuery.
|
# and move them to the outer AggregateQuery.
|
||||||
for alias, aggregate in self.aggregate_select.items():
|
for alias, aggregate in self.aggregate_select.items():
|
||||||
if aggregate.is_summary:
|
if aggregate.is_summary:
|
||||||
query.aggregate_select[alias] = aggregate
|
query.aggregate_select[alias] = aggregate.relabeled_clone(relabels)
|
||||||
del obj.aggregate_select[alias]
|
del obj.aggregate_select[alias]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -780,28 +793,22 @@ class Query(object):
|
||||||
data = data._replace(lhs_alias=change_map[lhs])
|
data = data._replace(lhs_alias=change_map[lhs])
|
||||||
self.alias_map[alias] = data
|
self.alias_map[alias] = data
|
||||||
|
|
||||||
def bump_prefix(self, exceptions=()):
|
def bump_prefix(self, outer_query):
|
||||||
"""
|
"""
|
||||||
Changes the alias prefix to the next letter in the alphabet and
|
Changes the alias prefix to the next letter in the alphabet in a way
|
||||||
relabels all the aliases. Even tables that previously had no alias will
|
that the outer query's aliases and this query's aliases will not
|
||||||
get an alias after this call (it's mostly used for nested queries and
|
conflict. Even tables that previously had no alias will get an alias
|
||||||
the outer query will already be using the non-aliased table name).
|
after this call.
|
||||||
|
|
||||||
Subclasses who create their own prefix should override this method to
|
|
||||||
produce a similar result (a new prefix and relabelled aliases).
|
|
||||||
|
|
||||||
The 'exceptions' parameter is a container that holds alias names which
|
|
||||||
should not be changed.
|
|
||||||
"""
|
"""
|
||||||
current = ord(self.alias_prefix)
|
self.alias_prefix = chr(ord(self.alias_prefix) + 1)
|
||||||
assert current < ord('Z')
|
while self.alias_prefix in self.subq_aliases:
|
||||||
prefix = chr(current + 1)
|
self.alias_prefix = chr(ord(self.alias_prefix) + 1)
|
||||||
self.alias_prefix = prefix
|
assert self.alias_prefix < 'Z'
|
||||||
|
self.subq_aliases = self.subq_aliases.union([self.alias_prefix])
|
||||||
|
outer_query.subq_aliases = outer_query.subq_aliases.union(self.subq_aliases)
|
||||||
change_map = OrderedDict()
|
change_map = OrderedDict()
|
||||||
for pos, alias in enumerate(self.tables):
|
for pos, alias in enumerate(self.tables):
|
||||||
if alias in exceptions:
|
new_alias = '%s%d' % (self.alias_prefix, pos)
|
||||||
continue
|
|
||||||
new_alias = '%s%d' % (prefix, pos)
|
|
||||||
change_map[alias] = new_alias
|
change_map[alias] = new_alias
|
||||||
self.tables[pos] = new_alias
|
self.tables[pos] = new_alias
|
||||||
self.change_aliases(change_map)
|
self.change_aliases(change_map)
|
||||||
|
@ -1005,6 +1012,65 @@ class Query(object):
|
||||||
# Add the aggregate to the query
|
# Add the aggregate to the query
|
||||||
aggregate.add_to_query(self, alias, col=col, source=source, is_summary=is_summary)
|
aggregate.add_to_query(self, alias, col=col, source=source, is_summary=is_summary)
|
||||||
|
|
||||||
|
def prepare_lookup_value(self, value, lookup_type, can_reuse):
|
||||||
|
# Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
|
||||||
|
# uses of None as a query value.
|
||||||
|
if value is None:
|
||||||
|
if lookup_type != 'exact':
|
||||||
|
raise ValueError("Cannot use None as a query value")
|
||||||
|
lookup_type = 'isnull'
|
||||||
|
value = True
|
||||||
|
elif callable(value):
|
||||||
|
value = value()
|
||||||
|
elif isinstance(value, ExpressionNode):
|
||||||
|
# If value is a query expression, evaluate it
|
||||||
|
value = SQLEvaluator(value, self, reuse=can_reuse)
|
||||||
|
if hasattr(value, 'query') and hasattr(value.query, 'bump_prefix'):
|
||||||
|
value = value._clone()
|
||||||
|
value.query.bump_prefix(self)
|
||||||
|
if hasattr(value, 'bump_prefix'):
|
||||||
|
value = value.clone()
|
||||||
|
value.bump_prefix(self)
|
||||||
|
# For Oracle '' is equivalent to null. The check needs to be done
|
||||||
|
# at this stage because join promotion can't be done at compiler
|
||||||
|
# stage. Using DEFAULT_DB_ALIAS isn't nice, but it is the best we
|
||||||
|
# can do here. Similar thing is done in is_nullable(), too.
|
||||||
|
if (connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls and
|
||||||
|
lookup_type == 'exact' and value == ''):
|
||||||
|
value = True
|
||||||
|
lookup_type = 'isnull'
|
||||||
|
return value, lookup_type
|
||||||
|
|
||||||
|
def solve_lookup_type(self, lookup):
|
||||||
|
"""
|
||||||
|
Solve the lookup type from the lookup (eg: 'foobar__id__icontains')
|
||||||
|
"""
|
||||||
|
lookup_type = 'exact' # Default lookup type
|
||||||
|
lookup_parts = lookup.split(LOOKUP_SEP)
|
||||||
|
num_parts = len(lookup_parts)
|
||||||
|
if (len(lookup_parts) > 1 and lookup_parts[-1] in self.query_terms
|
||||||
|
and lookup not in self.aggregates):
|
||||||
|
# Traverse the lookup query to distinguish related fields from
|
||||||
|
# lookup types.
|
||||||
|
lookup_model = self.model
|
||||||
|
for counter, field_name in enumerate(lookup_parts):
|
||||||
|
try:
|
||||||
|
lookup_field = lookup_model._meta.get_field(field_name)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
# Not a field. Bail out.
|
||||||
|
lookup_type = lookup_parts.pop()
|
||||||
|
break
|
||||||
|
# Unless we're at the end of the list of lookups, let's attempt
|
||||||
|
# to continue traversing relations.
|
||||||
|
if (counter + 1) < num_parts:
|
||||||
|
try:
|
||||||
|
lookup_model = lookup_field.rel.to
|
||||||
|
except AttributeError:
|
||||||
|
# Not a related field. Bail out.
|
||||||
|
lookup_type = lookup_parts.pop()
|
||||||
|
break
|
||||||
|
return lookup_type, lookup_parts
|
||||||
|
|
||||||
def build_filter(self, filter_expr, branch_negated=False, current_negated=False,
|
def build_filter(self, filter_expr, branch_negated=False, current_negated=False,
|
||||||
can_reuse=None):
|
can_reuse=None):
|
||||||
"""
|
"""
|
||||||
|
@ -1033,58 +1099,15 @@ class Query(object):
|
||||||
is responsible for unreffing the joins used.
|
is responsible for unreffing the joins used.
|
||||||
"""
|
"""
|
||||||
arg, value = filter_expr
|
arg, value = filter_expr
|
||||||
parts = arg.split(LOOKUP_SEP)
|
lookup_type, parts = self.solve_lookup_type(arg)
|
||||||
if not parts:
|
if not parts:
|
||||||
raise FieldError("Cannot parse keyword query %r" % arg)
|
raise FieldError("Cannot parse keyword query %r" % arg)
|
||||||
|
|
||||||
# Work out the lookup type and remove it from the end of 'parts',
|
# Work out the lookup type and remove it from the end of 'parts',
|
||||||
# if necessary.
|
# if necessary.
|
||||||
lookup_type = 'exact' # Default lookup type
|
value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse)
|
||||||
num_parts = len(parts)
|
|
||||||
if (len(parts) > 1 and parts[-1] in self.query_terms
|
|
||||||
and arg not in self.aggregates):
|
|
||||||
# Traverse the lookup query to distinguish related fields from
|
|
||||||
# lookup types.
|
|
||||||
lookup_model = self.model
|
|
||||||
for counter, field_name in enumerate(parts):
|
|
||||||
try:
|
|
||||||
lookup_field = lookup_model._meta.get_field(field_name)
|
|
||||||
except FieldDoesNotExist:
|
|
||||||
# Not a field. Bail out.
|
|
||||||
lookup_type = parts.pop()
|
|
||||||
break
|
|
||||||
# Unless we're at the end of the list of lookups, let's attempt
|
|
||||||
# to continue traversing relations.
|
|
||||||
if (counter + 1) < num_parts:
|
|
||||||
try:
|
|
||||||
lookup_model = lookup_field.rel.to
|
|
||||||
except AttributeError:
|
|
||||||
# Not a related field. Bail out.
|
|
||||||
lookup_type = parts.pop()
|
|
||||||
break
|
|
||||||
|
|
||||||
clause = self.where_class()
|
clause = self.where_class()
|
||||||
# Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
|
|
||||||
# uses of None as a query value.
|
|
||||||
if value is None:
|
|
||||||
if lookup_type != 'exact':
|
|
||||||
raise ValueError("Cannot use None as a query value")
|
|
||||||
lookup_type = 'isnull'
|
|
||||||
value = True
|
|
||||||
elif callable(value):
|
|
||||||
value = value()
|
|
||||||
elif isinstance(value, ExpressionNode):
|
|
||||||
# If value is a query expression, evaluate it
|
|
||||||
value = SQLEvaluator(value, self, reuse=can_reuse)
|
|
||||||
# For Oracle '' is equivalent to null. The check needs to be done
|
|
||||||
# at this stage because join promotion can't be done at compiler
|
|
||||||
# stage. Using DEFAULT_DB_ALIAS isn't nice, but it is the best we
|
|
||||||
# can do here. Similar thing is done in is_nullable(), too.
|
|
||||||
if (connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls and
|
|
||||||
lookup_type == 'exact' and value == ''):
|
|
||||||
value = True
|
|
||||||
lookup_type = 'isnull'
|
|
||||||
|
|
||||||
for alias, aggregate in self.aggregates.items():
|
for alias, aggregate in self.aggregates.items():
|
||||||
if alias in (parts[0], LOOKUP_SEP.join(parts)):
|
if alias in (parts[0], LOOKUP_SEP.join(parts)):
|
||||||
clause.add((aggregate, lookup_type, value), AND)
|
clause.add((aggregate, lookup_type, value), AND)
|
||||||
|
@ -1096,7 +1119,7 @@ class Query(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
field, sources, opts, join_list, path = self.setup_joins(
|
field, sources, opts, join_list, path = self.setup_joins(
|
||||||
parts, opts, alias, can_reuse, allow_many,)
|
parts, opts, alias, can_reuse, allow_many,)
|
||||||
if can_reuse is not None:
|
if can_reuse is not None:
|
||||||
can_reuse.update(join_list)
|
can_reuse.update(join_list)
|
||||||
except MultiJoin as e:
|
except MultiJoin as e:
|
||||||
|
@ -1404,7 +1427,6 @@ class Query(object):
|
||||||
# Generate the inner query.
|
# Generate the inner query.
|
||||||
query = Query(self.model)
|
query = Query(self.model)
|
||||||
query.where.add(query.build_filter(filter_expr), AND)
|
query.where.add(query.build_filter(filter_expr), AND)
|
||||||
query.bump_prefix()
|
|
||||||
query.clear_ordering(True)
|
query.clear_ordering(True)
|
||||||
# Try to have as simple as possible subquery -> trim leading joins from
|
# Try to have as simple as possible subquery -> trim leading joins from
|
||||||
# the subquery.
|
# the subquery.
|
||||||
|
|
|
@ -434,7 +434,9 @@ class BoundField(object):
|
||||||
This really is only useful for RadioSelect widgets, so that you can
|
This really is only useful for RadioSelect widgets, so that you can
|
||||||
iterate over individual radio buttons in a template.
|
iterate over individual radio buttons in a template.
|
||||||
"""
|
"""
|
||||||
for subwidget in self.field.widget.subwidgets(self.html_name, self.value()):
|
id_ = self.field.widget.attrs.get('id') or self.auto_id
|
||||||
|
attrs = {'id': id_} if id_ else {}
|
||||||
|
for subwidget in self.field.widget.subwidgets(self.html_name, self.value(), attrs):
|
||||||
yield subwidget
|
yield subwidget
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
|
|
@ -601,16 +601,15 @@ class ChoiceInput(SubWidget):
|
||||||
self.choice_value = force_text(choice[0])
|
self.choice_value = force_text(choice[0])
|
||||||
self.choice_label = force_text(choice[1])
|
self.choice_label = force_text(choice[1])
|
||||||
self.index = index
|
self.index = index
|
||||||
|
if 'id' in self.attrs:
|
||||||
|
self.attrs['id'] += "_%d" % self.index
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.render()
|
return self.render()
|
||||||
|
|
||||||
def render(self, name=None, value=None, attrs=None, choices=()):
|
def render(self, name=None, value=None, attrs=None, choices=()):
|
||||||
name = name or self.name
|
if self.id_for_label:
|
||||||
value = value or self.value
|
label_for = format_html(' for="{0}"', self.id_for_label)
|
||||||
attrs = attrs or self.attrs
|
|
||||||
if 'id' in self.attrs:
|
|
||||||
label_for = format_html(' for="{0}_{1}"', self.attrs['id'], self.index)
|
|
||||||
else:
|
else:
|
||||||
label_for = ''
|
label_for = ''
|
||||||
return format_html('<label{0}>{1} {2}</label>', label_for, self.tag(), self.choice_label)
|
return format_html('<label{0}>{1} {2}</label>', label_for, self.tag(), self.choice_label)
|
||||||
|
@ -619,13 +618,15 @@ class ChoiceInput(SubWidget):
|
||||||
return self.value == self.choice_value
|
return self.value == self.choice_value
|
||||||
|
|
||||||
def tag(self):
|
def tag(self):
|
||||||
if 'id' in self.attrs:
|
|
||||||
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
|
|
||||||
final_attrs = dict(self.attrs, type=self.input_type, name=self.name, value=self.choice_value)
|
final_attrs = dict(self.attrs, type=self.input_type, name=self.name, value=self.choice_value)
|
||||||
if self.is_checked():
|
if self.is_checked():
|
||||||
final_attrs['checked'] = 'checked'
|
final_attrs['checked'] = 'checked'
|
||||||
return format_html('<input{0} />', flatatt(final_attrs))
|
return format_html('<input{0} />', flatatt(final_attrs))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id_for_label(self):
|
||||||
|
return self.attrs.get('id', '')
|
||||||
|
|
||||||
|
|
||||||
class RadioChoiceInput(ChoiceInput):
|
class RadioChoiceInput(ChoiceInput):
|
||||||
input_type = 'radio'
|
input_type = 'radio'
|
||||||
|
|
|
@ -6,7 +6,7 @@ from importlib import import_module
|
||||||
from inspect import getargspec
|
from inspect import getargspec
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template.context import (Context, RequestContext,
|
from django.template.context import (BaseContext, Context, RequestContext,
|
||||||
ContextPopException)
|
ContextPopException)
|
||||||
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,
|
||||||
|
@ -765,6 +765,9 @@ class Variable(object):
|
||||||
current = current[bit]
|
current = current[bit]
|
||||||
except (TypeError, AttributeError, KeyError, ValueError):
|
except (TypeError, AttributeError, KeyError, ValueError):
|
||||||
try: # attribute lookup
|
try: # attribute lookup
|
||||||
|
# Don't return class attributes if the class is the context:
|
||||||
|
if isinstance(current, BaseContext) and getattr(type(current), bit):
|
||||||
|
raise AttributeError
|
||||||
current = getattr(current, bit)
|
current = getattr(current, bit)
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
try: # list-index lookup
|
try: # list-index lookup
|
||||||
|
|
|
@ -458,10 +458,11 @@ class VerbatimNode(Node):
|
||||||
return self.content
|
return self.content
|
||||||
|
|
||||||
class WidthRatioNode(Node):
|
class WidthRatioNode(Node):
|
||||||
def __init__(self, val_expr, max_expr, max_width):
|
def __init__(self, val_expr, max_expr, max_width, asvar=None):
|
||||||
self.val_expr = val_expr
|
self.val_expr = val_expr
|
||||||
self.max_expr = max_expr
|
self.max_expr = max_expr
|
||||||
self.max_width = max_width
|
self.max_width = max_width
|
||||||
|
self.asvar = asvar
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
try:
|
try:
|
||||||
|
@ -480,7 +481,13 @@ class WidthRatioNode(Node):
|
||||||
return '0'
|
return '0'
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return ''
|
return ''
|
||||||
return str(int(round(ratio)))
|
result = str(int(round(ratio)))
|
||||||
|
|
||||||
|
if self.asvar:
|
||||||
|
context[self.asvar] = result
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
class WithNode(Node):
|
class WithNode(Node):
|
||||||
def __init__(self, var, name, nodelist, extra_context=None):
|
def __init__(self, var, name, nodelist, extra_context=None):
|
||||||
|
@ -1353,20 +1360,34 @@ def widthratio(parser, token):
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
<img src='bar.gif' height='10' width='{% widthratio this_value max_value max_width %}' />
|
<img src="bar.png" alt="Bar"
|
||||||
|
height="10" width="{% widthratio this_value max_value max_width %}" />
|
||||||
|
|
||||||
If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100,
|
If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100,
|
||||||
the image in the above example will be 88 pixels wide
|
the image in the above example will be 88 pixels wide
|
||||||
(because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
|
(because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
|
||||||
|
|
||||||
|
In some cases you might want to capture the result of widthratio in a
|
||||||
|
variable. It can be useful for instance in a blocktrans like this::
|
||||||
|
|
||||||
|
{% widthratio this_value max_value max_width as width %}
|
||||||
|
{% blocktrans %}The width is: {{ width }}{% endblocktrans %}
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
if len(bits) != 4:
|
if len(bits) == 4:
|
||||||
raise TemplateSyntaxError("widthratio takes three arguments")
|
tag, this_value_expr, max_value_expr, max_width = bits
|
||||||
tag, this_value_expr, max_value_expr, max_width = bits
|
asvar = None
|
||||||
|
elif len(bits) == 6:
|
||||||
|
tag, this_value_expr, max_value_expr, max_width, as_, asvar = bits
|
||||||
|
if as_ != 'as':
|
||||||
|
raise TemplateSyntaxError("Invalid syntax in widthratio tag. Expecting 'as' keyword")
|
||||||
|
else:
|
||||||
|
raise TemplateSyntaxError("widthratio takes at least three arguments")
|
||||||
|
|
||||||
return WidthRatioNode(parser.compile_filter(this_value_expr),
|
return WidthRatioNode(parser.compile_filter(this_value_expr),
|
||||||
parser.compile_filter(max_value_expr),
|
parser.compile_filter(max_value_expr),
|
||||||
parser.compile_filter(max_width))
|
parser.compile_filter(max_width),
|
||||||
|
asvar=asvar)
|
||||||
|
|
||||||
@register.tag('with')
|
@register.tag('with')
|
||||||
def do_with(parser, token):
|
def do_with(parser, token):
|
||||||
|
|
|
@ -37,6 +37,7 @@ BOUNDARY = 'BoUnDaRyStRiNg'
|
||||||
MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
|
MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
|
||||||
CONTENT_TYPE_RE = re.compile('.*; charset=([\w\d-]+);?')
|
CONTENT_TYPE_RE = re.compile('.*; charset=([\w\d-]+);?')
|
||||||
|
|
||||||
|
|
||||||
class FakePayload(object):
|
class FakePayload(object):
|
||||||
"""
|
"""
|
||||||
A wrapper around BytesIO that restricts what can be read since data from
|
A wrapper around BytesIO that restricts what can be read since data from
|
||||||
|
@ -123,6 +124,7 @@ class ClientHandler(BaseHandler):
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def store_rendered_templates(store, signal, sender, template, context, **kwargs):
|
def store_rendered_templates(store, signal, sender, template, context, **kwargs):
|
||||||
"""
|
"""
|
||||||
Stores templates and contexts that are rendered.
|
Stores templates and contexts that are rendered.
|
||||||
|
@ -133,6 +135,7 @@ def store_rendered_templates(store, signal, sender, template, context, **kwargs)
|
||||||
store.setdefault('templates', []).append(template)
|
store.setdefault('templates', []).append(template)
|
||||||
store.setdefault('context', ContextList()).append(copy(context))
|
store.setdefault('context', ContextList()).append(copy(context))
|
||||||
|
|
||||||
|
|
||||||
def encode_multipart(boundary, data):
|
def encode_multipart(boundary, data):
|
||||||
"""
|
"""
|
||||||
Encodes multipart POST data from a dictionary of form values.
|
Encodes multipart POST data from a dictionary of form values.
|
||||||
|
@ -178,6 +181,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: force_bytes(s, settings.DEFAULT_CHARSET)
|
to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
|
||||||
if hasattr(file, 'content_type'):
|
if hasattr(file, 'content_type'):
|
||||||
|
@ -189,8 +193,8 @@ def encode_file(boundary, key, file):
|
||||||
content_type = 'application/octet-stream'
|
content_type = 'application/octet-stream'
|
||||||
return [
|
return [
|
||||||
to_bytes('--%s' % boundary),
|
to_bytes('--%s' % boundary),
|
||||||
to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"' \
|
to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"'
|
||||||
% (key, os.path.basename(file.name))),
|
% (key, os.path.basename(file.name))),
|
||||||
to_bytes('Content-Type: %s' % content_type),
|
to_bytes('Content-Type: %s' % content_type),
|
||||||
b'',
|
b'',
|
||||||
file.read()
|
file.read()
|
||||||
|
@ -274,14 +278,11 @@ class RequestFactory(object):
|
||||||
def get(self, path, data={}, **extra):
|
def get(self, path, data={}, **extra):
|
||||||
"Construct a GET request."
|
"Construct a GET request."
|
||||||
|
|
||||||
parsed = urlparse(path)
|
|
||||||
r = {
|
r = {
|
||||||
'PATH_INFO': self._get_path(parsed),
|
'QUERY_STRING': urlencode(data, doseq=True),
|
||||||
'QUERY_STRING': urlencode(data, doseq=True) or force_str(parsed[4]),
|
|
||||||
'REQUEST_METHOD': str('GET'),
|
|
||||||
}
|
}
|
||||||
r.update(extra)
|
r.update(extra)
|
||||||
return self.request(**r)
|
return self.generic('GET', path, **r)
|
||||||
|
|
||||||
def post(self, path, data={}, content_type=MULTIPART_CONTENT,
|
def post(self, path, data={}, content_type=MULTIPART_CONTENT,
|
||||||
**extra):
|
**extra):
|
||||||
|
@ -289,32 +290,19 @@ class RequestFactory(object):
|
||||||
|
|
||||||
post_data = self._encode_data(data, content_type)
|
post_data = self._encode_data(data, content_type)
|
||||||
|
|
||||||
parsed = urlparse(path)
|
return self.generic('POST', path, post_data, content_type, **extra)
|
||||||
r = {
|
|
||||||
'CONTENT_LENGTH': len(post_data),
|
|
||||||
'CONTENT_TYPE': content_type,
|
|
||||||
'PATH_INFO': self._get_path(parsed),
|
|
||||||
'QUERY_STRING': force_str(parsed[4]),
|
|
||||||
'REQUEST_METHOD': str('POST'),
|
|
||||||
'wsgi.input': FakePayload(post_data),
|
|
||||||
}
|
|
||||||
r.update(extra)
|
|
||||||
return self.request(**r)
|
|
||||||
|
|
||||||
def head(self, path, data={}, **extra):
|
def head(self, path, data={}, **extra):
|
||||||
"Construct a HEAD request."
|
"Construct a HEAD request."
|
||||||
|
|
||||||
parsed = urlparse(path)
|
|
||||||
r = {
|
r = {
|
||||||
'PATH_INFO': self._get_path(parsed),
|
'QUERY_STRING': urlencode(data, doseq=True),
|
||||||
'QUERY_STRING': urlencode(data, doseq=True) or force_str(parsed[4]),
|
|
||||||
'REQUEST_METHOD': str('HEAD'),
|
|
||||||
}
|
}
|
||||||
r.update(extra)
|
r.update(extra)
|
||||||
return self.request(**r)
|
return self.generic('HEAD', path, **r)
|
||||||
|
|
||||||
def options(self, path, data='', content_type='application/octet-stream',
|
def options(self, path, data='', content_type='application/octet-stream',
|
||||||
**extra):
|
**extra):
|
||||||
"Construct an OPTIONS request."
|
"Construct an OPTIONS request."
|
||||||
return self.generic('OPTIONS', path, data, content_type, **extra)
|
return self.generic('OPTIONS', path, data, content_type, **extra)
|
||||||
|
|
||||||
|
@ -324,22 +312,22 @@ class RequestFactory(object):
|
||||||
return self.generic('PUT', path, data, content_type, **extra)
|
return self.generic('PUT', path, data, content_type, **extra)
|
||||||
|
|
||||||
def patch(self, path, data='', content_type='application/octet-stream',
|
def patch(self, path, data='', content_type='application/octet-stream',
|
||||||
**extra):
|
**extra):
|
||||||
"Construct a PATCH request."
|
"Construct a PATCH request."
|
||||||
return self.generic('PATCH', path, data, content_type, **extra)
|
return self.generic('PATCH', path, data, content_type, **extra)
|
||||||
|
|
||||||
def delete(self, path, data='', content_type='application/octet-stream',
|
def delete(self, path, data='', content_type='application/octet-stream',
|
||||||
**extra):
|
**extra):
|
||||||
"Construct a DELETE request."
|
"Construct a DELETE request."
|
||||||
return self.generic('DELETE', path, data, content_type, **extra)
|
return self.generic('DELETE', path, data, content_type, **extra)
|
||||||
|
|
||||||
def generic(self, method, path,
|
def generic(self, method, path,
|
||||||
data='', content_type='application/octet-stream', **extra):
|
data='', content_type='application/octet-stream', **extra):
|
||||||
|
"""Constructs an arbitrary HTTP request."""
|
||||||
parsed = urlparse(path)
|
parsed = urlparse(path)
|
||||||
data = force_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': force_str(parsed[4]),
|
|
||||||
'REQUEST_METHOD': str(method),
|
'REQUEST_METHOD': str(method),
|
||||||
}
|
}
|
||||||
if data:
|
if data:
|
||||||
|
@ -349,8 +337,12 @@ class RequestFactory(object):
|
||||||
'wsgi.input': FakePayload(data),
|
'wsgi.input': FakePayload(data),
|
||||||
})
|
})
|
||||||
r.update(extra)
|
r.update(extra)
|
||||||
|
# If QUERY_STRING is absent or empty, we want to extract it from the URL.
|
||||||
|
if not r.get('QUERY_STRING'):
|
||||||
|
r['QUERY_STRING'] = force_str(parsed[4])
|
||||||
return self.request(**r)
|
return self.request(**r)
|
||||||
|
|
||||||
|
|
||||||
class Client(RequestFactory):
|
class Client(RequestFactory):
|
||||||
"""
|
"""
|
||||||
A class that can act as a client for testing purposes.
|
A class that can act as a client for testing purposes.
|
||||||
|
@ -392,7 +384,6 @@ class Client(RequestFactory):
|
||||||
return {}
|
return {}
|
||||||
session = property(_session)
|
session = property(_session)
|
||||||
|
|
||||||
|
|
||||||
def request(self, **request):
|
def request(self, **request):
|
||||||
"""
|
"""
|
||||||
The master request method. Composes the environment dictionary
|
The master request method. Composes the environment dictionary
|
||||||
|
@ -406,7 +397,8 @@ class Client(RequestFactory):
|
||||||
# callback function.
|
# callback function.
|
||||||
data = {}
|
data = {}
|
||||||
on_template_render = curry(store_rendered_templates, data)
|
on_template_render = curry(store_rendered_templates, data)
|
||||||
signals.template_rendered.connect(on_template_render, dispatch_uid="template-render")
|
signal_uid = "template-render-%s" % id(request)
|
||||||
|
signals.template_rendered.connect(on_template_render, dispatch_uid=signal_uid)
|
||||||
# Capture exceptions created by the handler.
|
# Capture exceptions created by the handler.
|
||||||
got_request_exception.connect(self.store_exc_info, dispatch_uid="request-exception")
|
got_request_exception.connect(self.store_exc_info, dispatch_uid="request-exception")
|
||||||
try:
|
try:
|
||||||
|
@ -452,7 +444,7 @@ class Client(RequestFactory):
|
||||||
|
|
||||||
return response
|
return response
|
||||||
finally:
|
finally:
|
||||||
signals.template_rendered.disconnect(dispatch_uid="template-render")
|
signals.template_rendered.disconnect(dispatch_uid=signal_uid)
|
||||||
got_request_exception.disconnect(dispatch_uid="request-exception")
|
got_request_exception.disconnect(dispatch_uid="request-exception")
|
||||||
|
|
||||||
def get(self, path, data={}, follow=False, **extra):
|
def get(self, path, data={}, follow=False, **extra):
|
||||||
|
@ -484,12 +476,11 @@ class Client(RequestFactory):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def options(self, path, data='', content_type='application/octet-stream',
|
def options(self, path, data='', content_type='application/octet-stream',
|
||||||
follow=False, **extra):
|
follow=False, **extra):
|
||||||
"""
|
"""
|
||||||
Request a response from the server using OPTIONS.
|
Request a response from the server using OPTIONS.
|
||||||
"""
|
"""
|
||||||
response = super(Client, self).options(path,
|
response = super(Client, self).options(path, data=data, content_type=content_type, **extra)
|
||||||
data=data, content_type=content_type, **extra)
|
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
return response
|
return response
|
||||||
|
@ -499,14 +490,13 @@ class Client(RequestFactory):
|
||||||
"""
|
"""
|
||||||
Send a resource to the server using PUT.
|
Send a resource to the server using PUT.
|
||||||
"""
|
"""
|
||||||
response = super(Client, self).put(path,
|
response = super(Client, self).put(path, data=data, content_type=content_type, **extra)
|
||||||
data=data, content_type=content_type, **extra)
|
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def patch(self, path, data='', content_type='application/octet-stream',
|
def patch(self, path, data='', content_type='application/octet-stream',
|
||||||
follow=False, **extra):
|
follow=False, **extra):
|
||||||
"""
|
"""
|
||||||
Send a resource to the server using PATCH.
|
Send a resource to the server using PATCH.
|
||||||
"""
|
"""
|
||||||
|
@ -517,12 +507,12 @@ class Client(RequestFactory):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def delete(self, path, data='', content_type='application/octet-stream',
|
def delete(self, path, data='', content_type='application/octet-stream',
|
||||||
follow=False, **extra):
|
follow=False, **extra):
|
||||||
"""
|
"""
|
||||||
Send a DELETE request to the server.
|
Send a DELETE request to the server.
|
||||||
"""
|
"""
|
||||||
response = super(Client, self).delete(path,
|
response = super(Client, self).delete(
|
||||||
data=data, content_type=content_type, **extra)
|
path, data=data, content_type=content_type, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -263,17 +263,12 @@ class LazyObject(object):
|
||||||
__dir__ = new_method_proxy(dir)
|
__dir__ = new_method_proxy(dir)
|
||||||
|
|
||||||
# Dictionary methods support
|
# Dictionary methods support
|
||||||
@new_method_proxy
|
__getitem__ = new_method_proxy(operator.getitem)
|
||||||
def __getitem__(self, key):
|
__setitem__ = new_method_proxy(operator.setitem)
|
||||||
return self[key]
|
__delitem__ = new_method_proxy(operator.delitem)
|
||||||
|
|
||||||
@new_method_proxy
|
__len__ = new_method_proxy(len)
|
||||||
def __setitem__(self, key, value):
|
__contains__ = new_method_proxy(operator.contains)
|
||||||
self[key] = value
|
|
||||||
|
|
||||||
@new_method_proxy
|
|
||||||
def __delitem__(self, key):
|
|
||||||
del self[key]
|
|
||||||
|
|
||||||
|
|
||||||
# Workaround for http://bugs.python.org/issue12370
|
# Workaround for http://bugs.python.org/issue12370
|
||||||
|
|
|
@ -109,8 +109,7 @@ def http_date(epoch_seconds=None):
|
||||||
|
|
||||||
Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'.
|
Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'.
|
||||||
"""
|
"""
|
||||||
rfcdate = formatdate(epoch_seconds)
|
return formatdate(epoch_seconds, usegmt=True)
|
||||||
return '%s GMT' % rfcdate[:25]
|
|
||||||
|
|
||||||
def parse_http_date(date):
|
def parse_http_date(date):
|
||||||
"""
|
"""
|
||||||
|
@ -253,11 +252,12 @@ def same_origin(url1, url2):
|
||||||
def is_safe_url(url, host=None):
|
def is_safe_url(url, host=None):
|
||||||
"""
|
"""
|
||||||
Return ``True`` if the url is a safe redirection (i.e. it doesn't point to
|
Return ``True`` if the url is a safe redirection (i.e. it doesn't point to
|
||||||
a different host).
|
a different host and uses a safe scheme).
|
||||||
|
|
||||||
Always returns ``False`` on an empty url.
|
Always returns ``False`` on an empty url.
|
||||||
"""
|
"""
|
||||||
if not url:
|
if not url:
|
||||||
return False
|
return False
|
||||||
netloc = urllib_parse.urlparse(url)[1]
|
url_info = urllib_parse.urlparse(url)
|
||||||
return not netloc or netloc == host
|
return (not url_info.netloc or url_info.netloc == host) and \
|
||||||
|
(not url_info.scheme or url_info.scheme in ['http', 'https'])
|
||||||
|
|
|
@ -227,7 +227,7 @@ class ExceptionReporter(object):
|
||||||
return "File exists"
|
return "File exists"
|
||||||
|
|
||||||
def get_traceback_data(self):
|
def get_traceback_data(self):
|
||||||
"Return a Context instance containing traceback information."
|
"""Return a dictionary containing traceback information."""
|
||||||
|
|
||||||
if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
|
if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
|
||||||
from django.template.loader import template_source_loaders
|
from django.template.loader import template_source_loaders
|
||||||
|
@ -295,13 +295,13 @@ class ExceptionReporter(object):
|
||||||
def get_traceback_html(self):
|
def get_traceback_html(self):
|
||||||
"Return HTML version of debug 500 HTTP error page."
|
"Return HTML version of debug 500 HTTP error page."
|
||||||
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
|
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
|
||||||
c = Context(self.get_traceback_data())
|
c = Context(self.get_traceback_data(), use_l10n=False)
|
||||||
return t.render(c)
|
return t.render(c)
|
||||||
|
|
||||||
def get_traceback_text(self):
|
def get_traceback_text(self):
|
||||||
"Return plain text version of debug 500 HTTP error page."
|
"Return plain text version of debug 500 HTTP error page."
|
||||||
t = Template(TECHNICAL_500_TEXT_TEMPLATE, name='Technical 500 template')
|
t = Template(TECHNICAL_500_TEXT_TEMPLATE, name='Technical 500 template')
|
||||||
c = Context(self.get_traceback_data(), autoescape=False)
|
c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False)
|
||||||
return t.render(c)
|
return t.render(c)
|
||||||
|
|
||||||
def get_template_exception_info(self):
|
def get_template_exception_info(self):
|
||||||
|
|
|
@ -295,9 +295,9 @@ validation process will break.
|
||||||
Therefore, you must ensure that the form field used to represent your
|
Therefore, you must ensure that the form field used to represent your
|
||||||
custom field performs whatever input validation and data cleaning is
|
custom field performs whatever input validation and data cleaning is
|
||||||
necessary to convert user-provided form input into a
|
necessary to convert user-provided form input into a
|
||||||
`to_python()`-compatible model field value. This may require writing a
|
``to_python()``-compatible model field value. This may require writing a
|
||||||
custom form field, and/or implementing the :meth:`.formfield` method on
|
custom form field, and/or implementing the :meth:`.formfield` method on
|
||||||
your field to return a form field class whose `to_python()` returns the
|
your field to return a form field class whose ``to_python()`` returns the
|
||||||
correct datatype.
|
correct datatype.
|
||||||
|
|
||||||
Documenting your custom field
|
Documenting your custom field
|
||||||
|
|
|
@ -28,6 +28,7 @@ the easiest, fastest, and most stable deployment choice.
|
||||||
* `Chapter 12 of the Django Book (second edition)`_ discusses deployment
|
* `Chapter 12 of the Django Book (second edition)`_ discusses deployment
|
||||||
and especially scaling in more detail. However, note that this edition
|
and especially scaling in more detail. However, note that this edition
|
||||||
was written against Django version 1.1 and has not been updated since
|
was written against Django version 1.1 and has not been updated since
|
||||||
`mod_python` was first deprecated, then completely removed in Django 1.5.
|
``mod_python`` was first deprecated, then completely removed in
|
||||||
|
Django 1.5.
|
||||||
|
|
||||||
.. _chapter 12 of the django book (second edition): http://djangobook.com/en/2.0/chapter12/
|
.. _chapter 12 of the django book (second edition): http://djangobook.com/en/2.0/chapter12/
|
||||||
|
|
|
@ -16,7 +16,7 @@ version >= 2.2 and mod_wsgi >= 2.0. For example, you could:
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
If you have installed a :ref:`custom User model <auth-custom-user>` and
|
If you have installed a :ref:`custom User model <auth-custom-user>` and
|
||||||
want to use this default auth handler, it must support an `is_active`
|
want to use this default auth handler, it must support an ``is_active``
|
||||||
attribute. If you want to use group based authorization, your custom user
|
attribute. If you want to use group based authorization, your custom user
|
||||||
must have a relation named 'groups', referring to a related object that has
|
must have a relation named 'groups', referring to a related object that has
|
||||||
a 'name' field. You can also specify your own custom mod_wsgi
|
a 'name' field. You can also specify your own custom mod_wsgi
|
||||||
|
|
|
@ -193,7 +193,7 @@ other approaches:
|
||||||
configuration).
|
configuration).
|
||||||
|
|
||||||
2. Use an ``Alias`` directive, as demonstrated above, to alias the appropriate
|
2. Use an ``Alias`` directive, as demonstrated above, to alias the appropriate
|
||||||
URL (probably :setting:`STATIC_URL` + `admin/`) to the actual location of
|
URL (probably :setting:`STATIC_URL` + ``admin/``) to the actual location of
|
||||||
the admin files.
|
the admin files.
|
||||||
|
|
||||||
3. Copy the admin static files so that they live within your Apache
|
3. Copy the admin static files so that they live within your Apache
|
||||||
|
|
|
@ -157,7 +157,7 @@ using interactive rebase::
|
||||||
The HEAD~2 above is shorthand for two latest commits. The above command
|
The HEAD~2 above is shorthand for two latest commits. The above command
|
||||||
will open an editor showing the two commits, prefixed with the word "pick".
|
will open an editor showing the two commits, prefixed with the word "pick".
|
||||||
|
|
||||||
Change the second line to "squash" instead. This will keep the
|
Change "pick" on the second line to "squash" instead. This will keep the
|
||||||
first commit, and squash the second commit into the first one. Save and quit
|
first commit, and squash the second commit into the first one. Save and quit
|
||||||
the editor. A second editor window should open, so you can reword the
|
the editor. A second editor window should open, so you can reword the
|
||||||
commit message for the commit now that it includes both your steps.
|
commit message for the commit now that it includes both your steps.
|
||||||
|
|
|
@ -370,8 +370,11 @@ these changes.
|
||||||
* Remove the backward compatible shims introduced to rename the attributes
|
* Remove the backward compatible shims introduced to rename the attributes
|
||||||
``ChangeList.root_query_set`` and ``ChangeList.query_set``.
|
``ChangeList.root_query_set`` and ``ChangeList.query_set``.
|
||||||
|
|
||||||
* ``django.conf.urls.shortcut`` and ``django.views.defaults.shortcut`` will be
|
* ``django.views.defaults.shortcut`` will be removed, as part of the
|
||||||
removed.
|
goal of removing all ``django.contrib`` references from the core
|
||||||
|
Django codebase. Instead use
|
||||||
|
``django.contrib.contenttypes.views.shortcut``. ``django.conf.urls.shortcut``
|
||||||
|
will also be removed.
|
||||||
|
|
||||||
* Support for the Python Imaging Library (PIL) module will be removed, as it
|
* Support for the Python Imaging Library (PIL) module will be removed, as it
|
||||||
no longer appears to be actively maintained & does not work on Python 3.
|
no longer appears to be actively maintained & does not work on Python 3.
|
||||||
|
@ -442,11 +445,5 @@ these changes.
|
||||||
2.0
|
2.0
|
||||||
---
|
---
|
||||||
|
|
||||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
|
||||||
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
|
|
||||||
goal of removing all ``django.contrib`` references from the core
|
|
||||||
Django codebase. The old shortcut will be removed in the 2.0
|
|
||||||
release.
|
|
||||||
|
|
||||||
* ``ssi`` and ``url`` template tags will be removed from the ``future`` template
|
* ``ssi`` and ``url`` template tags will be removed from the ``future`` template
|
||||||
tag library (used during the 1.3/1.4 deprecation period).
|
tag library (used during the 1.3/1.4 deprecation period).
|
||||||
|
|
|
@ -83,10 +83,11 @@ A few items need to be taken care of before even beginning the release process.
|
||||||
This stuff starts about a week before the release; most of it can be done
|
This stuff starts about a week before the release; most of it can be done
|
||||||
any time leading up to the actual release:
|
any time leading up to the actual release:
|
||||||
|
|
||||||
#. If this is a security release, send out pre-notification **one week**
|
#. If this is a security release, send out pre-notification **one week** before
|
||||||
before the release. We maintain a list of who gets these pre-notification
|
the release. We maintain a list of who gets these pre-notification emails in
|
||||||
emails at *FIXME WHERE?*. This email should be signed by the key you'll use
|
the private ``django-core`` repository. This email should be signed by the
|
||||||
for the release, and should include patches for each issue being fixed.
|
key you'll use for the release, and should include patches for each issue
|
||||||
|
being fixed.
|
||||||
|
|
||||||
#. As the release approaches, watch Trac to make sure no release blockers
|
#. As the release approaches, watch Trac to make sure no release blockers
|
||||||
are left for the upcoming release.
|
are left for the upcoming release.
|
||||||
|
|
|
@ -108,8 +108,12 @@ On the day of disclosure, we will take the following steps:
|
||||||
relevant patches and new releases, and crediting the reporter of
|
relevant patches and new releases, and crediting the reporter of
|
||||||
the issue (if the reporter wishes to be publicly identified).
|
the issue (if the reporter wishes to be publicly identified).
|
||||||
|
|
||||||
|
4. Post a notice to the `django-announce`_ mailing list that links to the blog
|
||||||
|
post.
|
||||||
|
|
||||||
.. _the Python Package Index: http://pypi.python.org/pypi
|
.. _the Python Package Index: http://pypi.python.org/pypi
|
||||||
.. _the official Django development blog: https://www.djangoproject.com/weblog/
|
.. _the official Django development blog: https://www.djangoproject.com/weblog/
|
||||||
|
.. _django-announce: http://groups.google.com/group/django-announce
|
||||||
|
|
||||||
If a reported issue is believed to be particularly time-sensitive --
|
If a reported issue is believed to be particularly time-sensitive --
|
||||||
due to a known exploit in the wild, for example -- the time between
|
due to a known exploit in the wild, for example -- the time between
|
||||||
|
@ -214,4 +218,4 @@ If you are added to the notification list, security-related emails
|
||||||
will be sent to you by Django's release manager, and all notification
|
will be sent to you by Django's release manager, and all notification
|
||||||
emails will be signed with the same key used to sign Django releases;
|
emails will be signed with the same key used to sign Django releases;
|
||||||
that key has the ID ``0x3684C0C08C8B2AE1``, and is available from most
|
that key has the ID ``0x3684C0C08C8B2AE1``, and is available from most
|
||||||
commonly-used keyservers.
|
commonly-used keyservers.
|
||||||
|
|
|
@ -600,7 +600,7 @@ for your own sanity when dealing with the interactive prompt, but also because
|
||||||
objects' representations are used throughout Django's automatically-generated
|
objects' representations are used throughout Django's automatically-generated
|
||||||
admin.
|
admin.
|
||||||
|
|
||||||
.. admonition:: `__unicode__` or `__str__`?
|
.. admonition:: ``__unicode__`` or ``__str__``?
|
||||||
|
|
||||||
On Python 3, things are simpler, just use
|
On Python 3, things are simpler, just use
|
||||||
:meth:`~django.db.models.Model.__str__` and forget about
|
:meth:`~django.db.models.Model.__str__` and forget about
|
||||||
|
|
|
@ -385,15 +385,6 @@ search terms, Django will search the ``question`` field. You can use as many
|
||||||
fields as you'd like -- although because it uses a ``LIKE`` query behind the
|
fields as you'd like -- although because it uses a ``LIKE`` query behind the
|
||||||
scenes, keep it reasonable, to keep your database happy.
|
scenes, keep it reasonable, to keep your database happy.
|
||||||
|
|
||||||
Finally, because ``Poll`` objects have dates, it'd be convenient to be able to
|
|
||||||
drill down by date. Add this line::
|
|
||||||
|
|
||||||
date_hierarchy = 'pub_date'
|
|
||||||
|
|
||||||
That adds hierarchical navigation, by date, to the top of the change list page.
|
|
||||||
At top level, it displays all available years. Then it drills down to months
|
|
||||||
and, ultimately, days.
|
|
||||||
|
|
||||||
Now's also a good time to note that change lists give you free pagination. The
|
Now's also a good time to note that change lists give you free pagination. The
|
||||||
default is to display 100 items per page. Change-list pagination, search boxes,
|
default is to display 100 items per page. Change-list pagination, search boxes,
|
||||||
filters, date-hierarchies and column-header-ordering all work together like you
|
filters, date-hierarchies and column-header-ordering all work together like you
|
||||||
|
|
|
@ -104,12 +104,6 @@ TemplateView
|
||||||
Renders a given template, with the context containing parameters captured
|
Renders a given template, with the context containing parameters captured
|
||||||
in the URL.
|
in the URL.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
The context used to be populated with a ``{{ params }}`` dictionary of
|
|
||||||
the parameters captured in the URL. Now those parameters are first-level
|
|
||||||
context variables.
|
|
||||||
|
|
||||||
**Ancestors (MRO)**
|
**Ancestors (MRO)**
|
||||||
|
|
||||||
This view inherits methods and attributes from the following views:
|
This view inherits methods and attributes from the following views:
|
||||||
|
|
|
@ -138,24 +138,16 @@ YearArchiveView
|
||||||
* ``year``: A :class:`~datetime.date` object
|
* ``year``: A :class:`~datetime.date` object
|
||||||
representing the given year.
|
representing the given year.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Previously, this returned a string.
|
|
||||||
|
|
||||||
* ``next_year``: A :class:`~datetime.date` object
|
* ``next_year``: A :class:`~datetime.date` object
|
||||||
representing the first day of the next year, according to
|
representing the first day of the next year, according to
|
||||||
:attr:`~BaseDateListView.allow_empty` and
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
:attr:`~DateMixin.allow_future`.
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
* ``previous_year``: A :class:`~datetime.date` object
|
* ``previous_year``: A :class:`~datetime.date` object
|
||||||
representing the first day of the previous year, according to
|
representing the first day of the previous year, according to
|
||||||
:attr:`~BaseDateListView.allow_empty` and
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
:attr:`~DateMixin.allow_future`.
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
**Notes**
|
**Notes**
|
||||||
|
|
||||||
* Uses a default ``template_name_suffix`` of ``_archive_year``.
|
* Uses a default ``template_name_suffix`` of ``_archive_year``.
|
||||||
|
|
|
@ -328,8 +328,3 @@ BaseDateListView
|
||||||
:meth:`~BaseDateListView.get_date_list_period` is used. ``date_type``
|
:meth:`~BaseDateListView.get_date_list_period` is used. ``date_type``
|
||||||
and ``ordering`` are simply passed to
|
and ``ordering`` are simply passed to
|
||||||
:meth:`QuerySet.dates()<django.db.models.query.QuerySet.dates>`.
|
:meth:`QuerySet.dates()<django.db.models.query.QuerySet.dates>`.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
The ``ordering`` parameter was added, and the default order was
|
|
||||||
changed to ascending.
|
|
||||||
|
|
|
@ -90,8 +90,6 @@ MultipleObjectMixin
|
||||||
|
|
||||||
.. attribute:: page_kwarg
|
.. attribute:: page_kwarg
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
A string specifying the name to use for the page parameter.
|
A string specifying the name to use for the page parameter.
|
||||||
The view will expect this prameter to be available either as a query
|
The view will expect this prameter to be available either as a query
|
||||||
string parameter (via ``request.GET``) or as a kwarg variable specified
|
string parameter (via ``request.GET``) or as a kwarg variable specified
|
||||||
|
|
|
@ -7,8 +7,6 @@ ContextMixin
|
||||||
|
|
||||||
.. class:: django.views.generic.base.ContextMixin
|
.. class:: django.views.generic.base.ContextMixin
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
**Methods**
|
**Methods**
|
||||||
|
|
||||||
.. method:: get_context_data(**kwargs)
|
.. method:: get_context_data(**kwargs)
|
||||||
|
@ -77,8 +75,6 @@ TemplateResponseMixin
|
||||||
|
|
||||||
.. attribute:: content_type
|
.. attribute:: content_type
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
The content type to use for the response. ``content_type`` is passed
|
The content type to use for the response. ``content_type`` is passed
|
||||||
as a keyword argument to ``response_class``. Default is ``None`` --
|
as a keyword argument to ``response_class``. Default is ``None`` --
|
||||||
meaning that Django uses :setting:`DEFAULT_CONTENT_TYPE`.
|
meaning that Django uses :setting:`DEFAULT_CONTENT_TYPE`.
|
||||||
|
|
|
@ -269,8 +269,8 @@ Making actions available site-wide
|
||||||
|
|
||||||
admin.site.add_action(export_selected_objects)
|
admin.site.add_action(export_selected_objects)
|
||||||
|
|
||||||
This makes the `export_selected_objects` action globally available as an
|
This makes the ``export_selected_objects`` action globally available as an
|
||||||
action named `"export_selected_objects"`. You can explicitly give the action
|
action named "export_selected_objects". You can explicitly give the action
|
||||||
a name -- good if you later want to programmatically :ref:`remove the action
|
a name -- good if you later want to programmatically :ref:`remove the action
|
||||||
<disabling-admin-actions>` -- by passing a second argument to
|
<disabling-admin-actions>` -- by passing a second argument to
|
||||||
:meth:`AdminSite.add_action()`::
|
:meth:`AdminSite.add_action()`::
|
||||||
|
|
|
@ -156,6 +156,6 @@ Edit this object
|
||||||
Using these bookmarklets requires that you are either logged into the
|
Using these bookmarklets requires that you are either logged into the
|
||||||
:mod:`Django admin <django.contrib.admin>` as a
|
:mod:`Django admin <django.contrib.admin>` as a
|
||||||
:class:`~django.contrib.auth.models.User` with
|
:class:`~django.contrib.auth.models.User` with
|
||||||
:attr:`~django.contrib.auth.models.User.is_staff` set to `True`, or that the
|
:attr:`~django.contrib.auth.models.User.is_staff` set to ``True``, or that the
|
||||||
``XViewMiddleware`` is installed and you are accessing the site from an IP
|
``XViewMiddleware`` is installed and you are accessing the site from an IP
|
||||||
address listed in :setting:`INTERNAL_IPS`.
|
address listed in :setting:`INTERNAL_IPS`.
|
||||||
|
|
|
@ -1235,8 +1235,6 @@ templates used by the :class:`ModelAdmin` views:
|
||||||
|
|
||||||
.. method:: ModelAdmin.get_list_filter(self, request)
|
.. method:: ModelAdmin.get_list_filter(self, request)
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
The ``get_list_filter`` method is given the ``HttpRequest`` and is expected
|
The ``get_list_filter`` method is given the ``HttpRequest`` and is expected
|
||||||
to return the same kind of sequence type as for the
|
to return the same kind of sequence type as for the
|
||||||
:attr:`~ModelAdmin.list_filter` attribute.
|
:attr:`~ModelAdmin.list_filter` attribute.
|
||||||
|
@ -1251,8 +1249,6 @@ templates used by the :class:`ModelAdmin` views:
|
||||||
|
|
||||||
.. method:: ModelAdmin.get_inline_instances(self, request, obj=None)
|
.. method:: ModelAdmin.get_inline_instances(self, request, obj=None)
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
The ``get_inline_instances`` method is given the ``HttpRequest`` and the
|
The ``get_inline_instances`` method is given the ``HttpRequest`` and the
|
||||||
``obj`` being edited (or ``None`` on an add form) and is expected to return
|
``obj`` being edited (or ``None`` on an add form) and is expected to return
|
||||||
a ``list`` or ``tuple`` of :class:`~django.contrib.admin.InlineModelAdmin`
|
a ``list`` or ``tuple`` of :class:`~django.contrib.admin.InlineModelAdmin`
|
||||||
|
@ -1506,10 +1502,6 @@ templates used by the :class:`ModelAdmin` views:
|
||||||
Sends a message to the user using the :mod:`django.contrib.messages`
|
Sends a message to the user using the :mod:`django.contrib.messages`
|
||||||
backend. See the :ref:`custom ModelAdmin example <custom-admin-action>`.
|
backend. See the :ref:`custom ModelAdmin example <custom-admin-action>`.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Keyword arguments were added in Django 1.5.
|
|
||||||
|
|
||||||
Keyword arguments allow you to change the message level, add extra CSS
|
Keyword arguments allow you to change the message level, add extra CSS
|
||||||
tags, or fail silently if the ``contrib.messages`` framework is not
|
tags, or fail silently if the ``contrib.messages`` framework is not
|
||||||
installed. These keyword arguments match those for
|
installed. These keyword arguments match those for
|
||||||
|
|
|
@ -215,11 +215,16 @@ Methods
|
||||||
(the Django app label). If the user is inactive, this method will
|
(the Django app label). If the user is inactive, this method will
|
||||||
always return ``False``.
|
always return ``False``.
|
||||||
|
|
||||||
.. method:: email_user(subject, message, from_email=None)
|
.. method:: email_user(subject, message, from_email=None, **kwargs)
|
||||||
|
|
||||||
Sends an email to the user. If ``from_email`` is ``None``, Django uses
|
Sends an email to the user. If ``from_email`` is ``None``, Django uses
|
||||||
the :setting:`DEFAULT_FROM_EMAIL`.
|
the :setting:`DEFAULT_FROM_EMAIL`.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
Any ``**kwargs`` are passed to the underlying
|
||||||
|
:meth:`~django.core.mail.send_mail()` call.
|
||||||
|
|
||||||
Manager methods
|
Manager methods
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -384,8 +389,6 @@ can be used for notification when a user logs in or out.
|
||||||
|
|
||||||
.. function:: user_login_failed
|
.. function:: user_login_failed
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Sent when the user failed to login successfully
|
Sent when the user failed to login successfully
|
||||||
|
|
||||||
``sender``
|
``sender``
|
||||||
|
|
|
@ -199,14 +199,18 @@ The ``ContentTypeManager``
|
||||||
|
|
||||||
Takes either a model class or an instance of a model, and returns the
|
Takes either a model class or an instance of a model, and returns the
|
||||||
:class:`~django.contrib.contenttypes.models.ContentType` instance
|
:class:`~django.contrib.contenttypes.models.ContentType` instance
|
||||||
representing that model.
|
representing that model. ``for_concrete_model=False`` allows fetching
|
||||||
|
the :class:`~django.contrib.contenttypes.models.ContentType` of a proxy
|
||||||
|
model.
|
||||||
|
|
||||||
.. method:: get_for_models(*models[, for_concrete_models=True])
|
.. method:: get_for_models(*models[, for_concrete_models=True])
|
||||||
|
|
||||||
Takes a variadic number of model classes, and returns a dictionary
|
Takes a variadic number of model classes, and returns a dictionary
|
||||||
mapping the model classes to the
|
mapping the model classes to the
|
||||||
:class:`~django.contrib.contenttypes.models.ContentType` instances
|
:class:`~django.contrib.contenttypes.models.ContentType` instances
|
||||||
representing them.
|
representing them. ``for_concrete_models=False`` allows fetching the
|
||||||
|
:class:`~django.contrib.contenttypes.models.ContentType` of proxy
|
||||||
|
models.
|
||||||
|
|
||||||
.. method:: get_by_natural_key(app_label, model)
|
.. method:: get_by_natural_key(app_label, model)
|
||||||
|
|
||||||
|
@ -232,21 +236,6 @@ lookup::
|
||||||
|
|
||||||
.. _generic-relations:
|
.. _generic-relations:
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Prior to Django 1.5,
|
|
||||||
:meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_model` and
|
|
||||||
:meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_models`
|
|
||||||
always returned the :class:`~django.contrib.contenttypes.models.ContentType`
|
|
||||||
associated with the concrete model of the specified one(s). That means there
|
|
||||||
was no way to retrieve the
|
|
||||||
:class:`~django.contrib.contenttypes.models.ContentType` of a proxy model
|
|
||||||
using those methods. As of Django 1.5 you can now pass a boolean flag –
|
|
||||||
``for_concrete_model`` and ``for_concrete_models`` respectively – to specify
|
|
||||||
wether or not you want to retrieve the
|
|
||||||
:class:`~django.contrib.contenttypes.models.ContentType` for the concrete or
|
|
||||||
direct model.
|
|
||||||
|
|
||||||
Generic relations
|
Generic relations
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|
|
@ -949,10 +949,6 @@ __ http://geohash.org/
|
||||||
|
|
||||||
*Availability*: PostGIS, SpatiaLite
|
*Availability*: PostGIS, SpatiaLite
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
``geojson`` support for Spatialite > 3.0 has been added.
|
|
||||||
|
|
||||||
Attaches a ``geojson`` attribute to every model in the queryset that contains the
|
Attaches a ``geojson`` attribute to every model in the queryset that contains the
|
||||||
`GeoJSON`__ representation of the geometry.
|
`GeoJSON`__ representation of the geometry.
|
||||||
|
|
||||||
|
|
|
@ -276,10 +276,6 @@ that the SRID value is not included in this representation
|
||||||
because it is not a part of the OGC specification (use the
|
because it is not a part of the OGC specification (use the
|
||||||
:attr:`GEOSGeometry.hexewkb` property instead).
|
:attr:`GEOSGeometry.hexewkb` property instead).
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Prior to Django 1.5, the Z value of the geometry was dropped.
|
|
||||||
|
|
||||||
.. attribute:: GEOSGeometry.hexewkb
|
.. attribute:: GEOSGeometry.hexewkb
|
||||||
|
|
||||||
Returns the EWKB of this Geometry in hexadecimal form. This is an
|
Returns the EWKB of this Geometry in hexadecimal form. This is an
|
||||||
|
@ -325,10 +321,6 @@ Returns the WKB (Well-Known Binary) representation of this Geometry
|
||||||
as a Python buffer. SRID value is not included, use the
|
as a Python buffer. SRID value is not included, use the
|
||||||
:attr:`GEOSGeometry.ewkb` property instead.
|
:attr:`GEOSGeometry.ewkb` property instead.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Prior to Django 1.5, the Z value of the geometry was dropped.
|
|
||||||
|
|
||||||
.. _ewkb:
|
.. _ewkb:
|
||||||
|
|
||||||
.. attribute:: GEOSGeometry.ewkb
|
.. attribute:: GEOSGeometry.ewkb
|
||||||
|
@ -426,8 +418,6 @@ geometry that do not make up other.
|
||||||
.. method:: GEOSGeometry.interpolate(distance)
|
.. method:: GEOSGeometry.interpolate(distance)
|
||||||
.. method:: GEOSGeometry.interpolate_normalized(distance)
|
.. method:: GEOSGeometry.interpolate_normalized(distance)
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Given a distance (float), returns the point (or closest point) within the
|
Given a distance (float), returns the point (or closest point) within the
|
||||||
geometry (:class:`LineString` or :class:`MultiLineString`) at that distance.
|
geometry (:class:`LineString` or :class:`MultiLineString`) at that distance.
|
||||||
The normalized version takes the distance as a float between 0 (origin) and 1
|
The normalized version takes the distance as a float between 0 (origin) and 1
|
||||||
|
@ -443,8 +433,6 @@ geometry and other.
|
||||||
.. method:: GEOSGeometry.project(point)
|
.. method:: GEOSGeometry.project(point)
|
||||||
.. method:: GEOSGeometry.project_normalized(point)
|
.. method:: GEOSGeometry.project_normalized(point)
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Returns the distance (float) from the origin of the geometry
|
Returns the distance (float) from the origin of the geometry
|
||||||
(:class:`LineString` or :class:`MultiLineString`) to the point projected on the
|
(:class:`LineString` or :class:`MultiLineString`) to the point projected on the
|
||||||
geometry (that is to a point of the line the closest to the given point).
|
geometry (that is to a point of the line the closest to the given point).
|
||||||
|
|
|
@ -54,20 +54,21 @@ Storage backends
|
||||||
|
|
||||||
The messages framework can use different backends to store temporary messages.
|
The messages framework can use different backends to store temporary messages.
|
||||||
|
|
||||||
Django provides three built-in storage classes:
|
Django provides three built-in storage classes in
|
||||||
|
:mod:`django.contrib.messages`:
|
||||||
|
|
||||||
.. class:: django.contrib.messages.storage.session.SessionStorage
|
.. class:: storage.session.SessionStorage
|
||||||
|
|
||||||
This class stores all messages inside of the request's session. Therefore
|
This class stores all messages inside of the request's session. Therefore
|
||||||
it requires Django's ``contrib.sessions`` application.
|
it requires Django's ``contrib.sessions`` application.
|
||||||
|
|
||||||
.. class:: django.contrib.messages.storage.cookie.CookieStorage
|
.. class:: storage.cookie.CookieStorage
|
||||||
|
|
||||||
This class stores the message data in a cookie (signed with a secret hash
|
This class stores the message data in a cookie (signed with a secret hash
|
||||||
to prevent manipulation) to persist notifications across requests. Old
|
to prevent manipulation) to persist notifications across requests. Old
|
||||||
messages are dropped if the cookie data size would exceed 2048 bytes.
|
messages are dropped if the cookie data size would exceed 2048 bytes.
|
||||||
|
|
||||||
.. class:: django.contrib.messages.storage.fallback.FallbackStorage
|
.. class:: storage.fallback.FallbackStorage
|
||||||
|
|
||||||
This class first uses ``CookieStorage``, and falls back to using
|
This class first uses ``CookieStorage``, and falls back to using
|
||||||
``SessionStorage`` for the messages that could not fit in a single cookie.
|
``SessionStorage`` for the messages that could not fit in a single cookie.
|
||||||
|
@ -83,6 +84,8 @@ path, for example::
|
||||||
|
|
||||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||||
|
|
||||||
|
.. class:: storage.base.BaseStorage
|
||||||
|
|
||||||
To write your own storage class, subclass the ``BaseStorage`` class in
|
To write your own storage class, subclass the ``BaseStorage`` class in
|
||||||
``django.contrib.messages.storage.base`` and implement the ``_get`` and
|
``django.contrib.messages.storage.base`` and implement the ``_get`` and
|
||||||
``_store`` methods.
|
``_store`` methods.
|
||||||
|
|
|
@ -417,9 +417,10 @@ Here's how Django uses the sites framework:
|
||||||
:class:`~django.contrib.sites.models.Site` name to the template as
|
:class:`~django.contrib.sites.models.Site` name to the template as
|
||||||
``{{ site_name }}``.
|
``{{ site_name }}``.
|
||||||
|
|
||||||
* The shortcut view (``django.views.defaults.shortcut``) uses the domain
|
* The shortcut view (``django.contrib.contenttypes.views.shortcut``)
|
||||||
of the current :class:`~django.contrib.sites.models.Site` object when
|
uses the domain of the current
|
||||||
calculating an object's URL.
|
:class:`~django.contrib.sites.models.Site` object when calculating
|
||||||
|
an object's URL.
|
||||||
|
|
||||||
* In the admin framework, the "view on site" link uses the current
|
* In the admin framework, the "view on site" link uses the current
|
||||||
:class:`~django.contrib.sites.models.Site` to work out the domain for the
|
:class:`~django.contrib.sites.models.Site` to work out the domain for the
|
||||||
|
|
|
@ -255,8 +255,6 @@ CachedStaticFilesStorage
|
||||||
|
|
||||||
.. method:: file_hash(name, content=None)
|
.. method:: file_hash(name, content=None)
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
The method that is used when creating the hashed name of a file.
|
The method that is used when creating the hashed name of a file.
|
||||||
Needs to return a hash for the given file name and content.
|
Needs to return a hash for the given file name and content.
|
||||||
By default it calculates a MD5 hash from the content's chunks as
|
By default it calculates a MD5 hash from the content's chunks as
|
||||||
|
@ -290,8 +288,6 @@ The previous example is equal to calling the ``url`` method of an instance of
|
||||||
useful when using a non-local storage backend to deploy files as documented
|
useful when using a non-local storage backend to deploy files as documented
|
||||||
in :ref:`staticfiles-from-cdn`.
|
in :ref:`staticfiles-from-cdn`.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
If you'd like to retrieve a static URL without displaying it, you can use a
|
If you'd like to retrieve a static URL without displaying it, you can use a
|
||||||
slightly different call:
|
slightly different call:
|
||||||
|
|
||||||
|
|
|
@ -191,10 +191,6 @@ Django supports MySQL 5.0.3 and higher.
|
||||||
`MySQL 5.0`_ adds the ``information_schema`` database, which contains detailed
|
`MySQL 5.0`_ adds the ``information_schema`` database, which contains detailed
|
||||||
data on all database schema. Django's ``inspectdb`` feature uses it.
|
data on all database schema. Django's ``inspectdb`` feature uses it.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
The minimum version requirement of MySQL 5.0.3 was set in Django 1.5.
|
|
||||||
|
|
||||||
Django expects the database to support Unicode (UTF-8 encoding) and delegates to
|
Django expects the database to support Unicode (UTF-8 encoding) and delegates to
|
||||||
it the task of enforcing transactions and referential integrity. It is important
|
it the task of enforcing transactions and referential integrity. It is important
|
||||||
to be aware of the fact that the two latter ones aren't actually enforced by
|
to be aware of the fact that the two latter ones aren't actually enforced by
|
||||||
|
|
|
@ -254,8 +254,6 @@ to flush.
|
||||||
``--no-initial-data``
|
``--no-initial-data``
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Use ``--no-initial-data`` to avoid loading the initial_data fixture.
|
Use ``--no-initial-data`` to avoid loading the initial_data fixture.
|
||||||
|
|
||||||
|
|
||||||
|
@ -332,8 +330,6 @@ onto which the data will be loaded.
|
||||||
|
|
||||||
.. django-admin-option:: --ignorenonexistent
|
.. django-admin-option:: --ignorenonexistent
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
The :djadminopt:`--ignorenonexistent` option can be used to ignore fields that
|
The :djadminopt:`--ignorenonexistent` option can be used to ignore fields that
|
||||||
may have been removed from models since the fixture was originally generated.
|
may have been removed from models since the fixture was originally generated.
|
||||||
|
|
||||||
|
@ -903,10 +899,6 @@ behavior you can use the ``--no-startup`` option. e.g.::
|
||||||
|
|
||||||
django-admin.py shell --plain --no-startup
|
django-admin.py shell --plain --no-startup
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
The ``--interface`` option was added in Django 1.5.
|
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
The ``--no-startup`` option was added in Django 1.6.
|
The ``--no-startup`` option was added in Django 1.6.
|
||||||
|
@ -1337,8 +1329,6 @@ clearsessions
|
||||||
|
|
||||||
.. django-admin:: clearsessions
|
.. django-admin:: clearsessions
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Can be run as a cron job or directly to clean out expired sessions.
|
Can be run as a cron job or directly to clean out expired sessions.
|
||||||
|
|
||||||
``django.contrib.sitemaps``
|
``django.contrib.sitemaps``
|
||||||
|
|
|
@ -100,10 +100,6 @@ The ``ContentFile`` Class
|
||||||
f1 = ContentFile("esta sentencia está en español")
|
f1 = ContentFile("esta sentencia está en español")
|
||||||
f2 = ContentFile(b"these are bytes")
|
f2 = ContentFile(b"these are bytes")
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
ContentFile also accepts Unicode strings.
|
|
||||||
|
|
||||||
.. currentmodule:: django.core.files.images
|
.. currentmodule:: django.core.files.images
|
||||||
|
|
||||||
The ``ImageFile`` Class
|
The ``ImageFile`` Class
|
||||||
|
|
|
@ -211,11 +211,6 @@ only the valid fields::
|
||||||
>>> f.cleaned_data
|
>>> f.cleaned_data
|
||||||
{'cc_myself': True, 'message': u'Hi there'}
|
{'cc_myself': True, 'message': u'Hi there'}
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Until Django 1.5, the ``cleaned_data`` attribute wasn't defined at all when
|
|
||||||
the ``Form`` didn't validate.
|
|
||||||
|
|
||||||
``cleaned_data`` will always *only* contain a key for fields defined in the
|
``cleaned_data`` will always *only* contain a key for fields defined in the
|
||||||
``Form``, even if you pass extra data when you define the ``Form``. In this
|
``Form``, even if you pass extra data when you define the ``Form``. In this
|
||||||
example, we pass a bunch of extra fields to the ``ContactForm`` constructor,
|
example, we pass a bunch of extra fields to the ``ContactForm`` constructor,
|
||||||
|
|
|
@ -573,16 +573,12 @@ For each field, we describe the default widget used if you don't specify
|
||||||
|
|
||||||
.. attribute:: allow_files
|
.. attribute:: allow_files
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
|
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
|
||||||
whether files in the specified location should be included. Either this or
|
whether files in the specified location should be included. Either this or
|
||||||
:attr:`allow_folders` must be ``True``.
|
:attr:`allow_folders` must be ``True``.
|
||||||
|
|
||||||
.. attribute:: allow_folders
|
.. attribute:: allow_folders
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
||||||
whether folders in the specified location should be included. Either this or
|
whether folders in the specified location should be included. Either this or
|
||||||
:attr:`allow_files` must be ``True``.
|
:attr:`allow_files` must be ``True``.
|
||||||
|
@ -1065,11 +1061,6 @@ objects (in the case of ``ModelMultipleChoiceField``) into the
|
||||||
* Error message keys: ``required``, ``list``, ``invalid_choice``,
|
* Error message keys: ``required``, ``list``, ``invalid_choice``,
|
||||||
``invalid_pk_value``
|
``invalid_pk_value``
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
The empty and normalized values were changed to be consistently
|
|
||||||
``QuerySets`` instead of ``[]`` and ``QuerySet`` respectively.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.6
|
.. versionchanged:: 1.6
|
||||||
|
|
||||||
The ``invalid_choice`` message may contain ``%(value)s`` and the
|
The ``invalid_choice`` message may contain ``%(value)s`` and the
|
||||||
|
|
|
@ -450,11 +450,5 @@ entries in ``_errors``.
|
||||||
|
|
||||||
Secondly, once we have decided that the combined data in the two fields we are
|
Secondly, once we have decided that the combined data in the two fields we are
|
||||||
considering aren't valid, we must remember to remove them from the
|
considering aren't valid, we must remember to remove them from the
|
||||||
``cleaned_data``.
|
``cleaned_data``. `cleaned_data`` is present even if the form doesn't
|
||||||
|
validate, but it contains only field values that did validate.
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Django used to remove the ``cleaned_data`` attribute entirely if there were
|
|
||||||
any errors in the form. Since version 1.5, ``cleaned_data`` is present even if
|
|
||||||
the form doesn't validate, but it contains only field values that did
|
|
||||||
validate.
|
|
||||||
|
|
|
@ -525,11 +525,6 @@ Selector and checkbox widgets
|
||||||
A callable that takes the value of the CheckBoxInput and returns
|
A callable that takes the value of the CheckBoxInput and returns
|
||||||
``True`` if the checkbox should be checked for that value.
|
``True`` if the checkbox should be checked for that value.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Exceptions from ``check_test`` used to be silenced by its caller,
|
|
||||||
this is no longer the case, they will propagate upwards.
|
|
||||||
|
|
||||||
``Select``
|
``Select``
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -590,25 +585,26 @@ Selector and checkbox widgets
|
||||||
.. code-block:: html
|
.. code-block:: html
|
||||||
|
|
||||||
<div class="myradio">
|
<div class="myradio">
|
||||||
<label><input type="radio" name="beatles" value="john" /> John</label>
|
<label for="id_beatles_0"><input id="id_beatles_0" name="beatles" type="radio" value="john" /> John</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="myradio">
|
<div class="myradio">
|
||||||
<label><input type="radio" name="beatles" value="paul" /> Paul</label>
|
<label for="id_beatles_1"><input id="id_beatles_1" name="beatles" type="radio" value="paul" /> Paul</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="myradio">
|
<div class="myradio">
|
||||||
<label><input type="radio" name="beatles" value="george" /> George</label>
|
<label for="id_beatles_2"><input id="id_beatles_2" name="beatles" type="radio" value="george" /> George</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="myradio">
|
<div class="myradio">
|
||||||
<label><input type="radio" name="beatles" value="ringo" /> Ringo</label>
|
<label for="id_beatles_3"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" /> Ringo</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
That included the ``<label>`` tags. To get more granular, you can use each
|
That included the ``<label>`` tags. To get more granular, you can use each
|
||||||
radio button's ``tag`` and ``choice_label`` attributes. For example, this template...
|
radio button's ``tag``, ``choice_label`` and ``id_for_label`` attributes.
|
||||||
|
For example, this template...
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
|
||||||
{% for radio in myform.beatles %}
|
{% for radio in myform.beatles %}
|
||||||
<label>
|
<label for="{{ radio.id_for_label }}">
|
||||||
{{ radio.choice_label }}
|
{{ radio.choice_label }}
|
||||||
<span class="radio">{{ radio.tag }}</span>
|
<span class="radio">{{ radio.tag }}</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -618,31 +614,41 @@ Selector and checkbox widgets
|
||||||
|
|
||||||
.. code-block:: html
|
.. code-block:: html
|
||||||
|
|
||||||
<label>
|
<label for="id_beatles_0">
|
||||||
John
|
John
|
||||||
<span class="radio"><input type="radio" name="beatles" value="john" /></span>
|
<span class="radio"><input id="id_beatles_0" name="beatles" type="radio" value="john" /></span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
|
||||||
Paul
|
|
||||||
<span class="radio"><input type="radio" name="beatles" value="paul" /></span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
George
|
|
||||||
<span class="radio"><input type="radio" name="beatles" value="george" /></span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Ringo
|
|
||||||
<span class="radio"><input type="radio" name="beatles" value="ringo" /></span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
If you decide not to loop over the radio buttons -- e.g., if your template simply includes
|
<label for="id_beatles_1">
|
||||||
``{{ myform.beatles }}`` -- they'll be output in a ``<ul>`` with ``<li>`` tags, as above.
|
Paul
|
||||||
|
<span class="radio"><input id="id_beatles_1" name="beatles" type="radio" value="paul" /></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label for="id_beatles_2">
|
||||||
|
George
|
||||||
|
<span class="radio"><input id="id_beatles_2" name="beatles" type="radio" value="george" /></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label for="id_beatles_3">
|
||||||
|
Ringo
|
||||||
|
<span class="radio"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" /></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
If you decide not to loop over the radio buttons -- e.g., if your template
|
||||||
|
simply includes ``{{ myform.beatles }}`` -- they'll be output in a ``<ul>``
|
||||||
|
with ``<li>`` tags, as above.
|
||||||
|
|
||||||
.. versionchanged:: 1.6
|
.. versionchanged:: 1.6
|
||||||
|
|
||||||
The outer ``<ul>`` container will now receive the ``id`` attribute defined on
|
The outer ``<ul>`` container will now receive the ``id`` attribute defined on
|
||||||
the widget.
|
the widget.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
When looping over the radio buttons, the ``label`` and ``input`` tags include
|
||||||
|
``for`` and ``id`` attributes, respectively. Each radio button has an
|
||||||
|
``id_for_label`` attribute to output the element's ID.
|
||||||
|
|
||||||
``CheckboxSelectMultiple``
|
``CheckboxSelectMultiple``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -666,6 +672,12 @@ the widget.
|
||||||
Like :class:`RadioSelect`, you can now loop over the individual checkboxes making
|
Like :class:`RadioSelect`, you can now loop over the individual checkboxes making
|
||||||
up the lists. See the documentation of :class:`RadioSelect` for more details.
|
up the lists. See the documentation of :class:`RadioSelect` for more details.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
When looping over the checkboxes, the ``label`` and ``input`` tags include
|
||||||
|
``for`` and ``id`` attributes, respectively. Each checkbox has an
|
||||||
|
``id_for_label`` attribute to output the element's ID.
|
||||||
|
|
||||||
.. _file-upload-widgets:
|
.. _file-upload-widgets:
|
||||||
|
|
||||||
File upload widgets
|
File upload widgets
|
||||||
|
|
|
@ -757,21 +757,16 @@ directory on the filesystem. Has three special arguments, of which the first is
|
||||||
|
|
||||||
.. attribute:: FilePathField.allow_files
|
.. attribute:: FilePathField.allow_files
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
|
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
|
||||||
whether files in the specified location should be included. Either this or
|
whether files in the specified location should be included. Either this or
|
||||||
:attr:`~FilePathField.allow_folders` must be ``True``.
|
:attr:`~FilePathField.allow_folders` must be ``True``.
|
||||||
|
|
||||||
.. attribute:: FilePathField.allow_folders
|
.. attribute:: FilePathField.allow_folders
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
||||||
whether folders in the specified location should be included. Either this
|
whether folders in the specified location should be included. Either this
|
||||||
or :attr:`~FilePathField.allow_files` must be ``True``.
|
or :attr:`~FilePathField.allow_files` must be ``True``.
|
||||||
|
|
||||||
|
|
||||||
Of course, these arguments can be used together.
|
Of course, these arguments can be used together.
|
||||||
|
|
||||||
The one potential gotcha is that :attr:`~FilePathField.match` applies to the
|
The one potential gotcha is that :attr:`~FilePathField.match` applies to the
|
||||||
|
@ -980,12 +975,6 @@ 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
|
||||||
:attr:`~CharField.max_length`, a default of 200 is used.
|
:attr:`~CharField.max_length`, a default of 200 is used.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
The current value of the field will be displayed as a clickable link above the
|
|
||||||
input widget.
|
|
||||||
|
|
||||||
|
|
||||||
Relationship fields
|
Relationship fields
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|
|
@ -388,8 +388,6 @@ For more details, see the documentation on :ref:`F() expressions
|
||||||
Specifying which fields to save
|
Specifying which fields to save
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
If ``save()`` is passed a list of field names in keyword argument
|
If ``save()`` is passed a list of field names in keyword argument
|
||||||
``update_fields``, only the fields named in that list will be updated.
|
``update_fields``, only the fields named in that list will be updated.
|
||||||
This may be desirable if you want to update just one or a few fields on
|
This may be desirable if you want to update just one or a few fields on
|
||||||
|
@ -494,6 +492,40 @@ using ``__str__()`` like this::
|
||||||
# first_name and last_name will be unicode strings.
|
# first_name and last_name will be unicode strings.
|
||||||
return force_bytes('%s %s' % (self.first_name, self.last_name))
|
return force_bytes('%s %s' % (self.first_name, self.last_name))
|
||||||
|
|
||||||
|
``__eq__``
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. method:: Model.__eq__()
|
||||||
|
|
||||||
|
The equality method is defined such that instances with the same primary
|
||||||
|
key value and the same concrete class are considered equal. For proxy
|
||||||
|
models, concrete class is defined as the model's first non-proxy parent;
|
||||||
|
for all other models it is simply the model's class.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
form django.db import models
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
id = models.AutoField(primary_key=True)
|
||||||
|
|
||||||
|
class MyProxyModel(MyModel):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
class MultitableInherited(MyModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
MyModel(id=1) == MyModel(id=1)
|
||||||
|
MyModel(id=1) == MyProxyModel(id=1)
|
||||||
|
MyModel(id=1) != MultitableInherited(id=1)
|
||||||
|
MyModel(id=1) != MyModel(id=2)
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
In previous versions only instances of the exact same class and same
|
||||||
|
primary key value were considered equal.
|
||||||
|
|
||||||
``get_absolute_url``
|
``get_absolute_url``
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
|
@ -286,8 +286,6 @@ Django quotes column and table names behind the scenes.
|
||||||
|
|
||||||
.. attribute:: Options.index_together
|
.. attribute:: Options.index_together
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Sets of field names that, taken together, are indexed::
|
Sets of field names that, taken together, are indexed::
|
||||||
|
|
||||||
index_together = [
|
index_together = [
|
||||||
|
|
|
@ -913,7 +913,7 @@ needed by ``select_related``), it is able to detect that the ``best_pizza``
|
||||||
objects have already been fetched, and it will skip fetching them again.
|
objects have already been fetched, and it will skip fetching them again.
|
||||||
|
|
||||||
Chaining ``prefetch_related`` calls will accumulate the lookups that are
|
Chaining ``prefetch_related`` calls will accumulate the lookups that are
|
||||||
prefetched. To clear any ``prefetch_related`` behavior, pass `None` as a
|
prefetched. To clear any ``prefetch_related`` behavior, pass ``None`` as a
|
||||||
parameter::
|
parameter::
|
||||||
|
|
||||||
>>> non_prefetched = qs.prefetch_related(None)
|
>>> non_prefetched = qs.prefetch_related(None)
|
||||||
|
@ -1149,13 +1149,11 @@ to ``defer()``::
|
||||||
# Load all fields immediately.
|
# Load all fields immediately.
|
||||||
my_queryset.defer(None)
|
my_queryset.defer(None)
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
Some fields in a model won't be deferred, even if you ask for them. You can
|
||||||
|
never defer the loading of the primary key. If you are using
|
||||||
Some fields in a model won't be deferred, even if you ask for them. You can
|
:meth:`select_related()` to retrieve related models, you shouldn't defer the
|
||||||
never defer the loading of the primary key. If you are using
|
loading of the field that connects from the primary model to the related
|
||||||
:meth:`select_related()` to retrieve related models, you shouldn't defer the
|
one, doing so will result in an error.
|
||||||
loading of the field that connects from the primary model to the related
|
|
||||||
one, doing so will result in an error.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -1178,8 +1176,6 @@ to ``defer()``::
|
||||||
reader, is slightly faster and consumes a little less memory in the Python
|
reader, is slightly faster and consumes a little less memory in the Python
|
||||||
process.
|
process.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
When calling :meth:`~django.db.models.Model.save()` for instances with
|
When calling :meth:`~django.db.models.Model.save()` for instances with
|
||||||
|
@ -1227,16 +1223,14 @@ All of the cautions in the note for the :meth:`defer` documentation apply to
|
||||||
``only()`` as well. Use it cautiously and only after exhausting your other
|
``only()`` as well. Use it cautiously and only after exhausting your other
|
||||||
options.
|
options.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
Using :meth:`only` and omitting a field requested using :meth:`select_related`
|
||||||
|
is an error as well.
|
||||||
|
|
||||||
Using :meth:`only` and omitting a field requested using
|
.. note::
|
||||||
:meth:`select_related` is an error as well.
|
|
||||||
|
|
||||||
.. note::
|
When calling :meth:`~django.db.models.Model.save()` for instances with
|
||||||
|
deferred fields, only the loaded fields will be saved. See
|
||||||
When calling :meth:`~django.db.models.Model.save()` for instances with
|
:meth:`~django.db.models.Model.save()` for more details.
|
||||||
deferred fields, only the loaded fields will be saved. See
|
|
||||||
:meth:`~django.db.models.Model.save()` for more details.
|
|
||||||
|
|
||||||
using
|
using
|
||||||
~~~~~
|
~~~~~
|
||||||
|
@ -1567,10 +1561,6 @@ The ``batch_size`` parameter controls how many objects are created in single
|
||||||
query. The default is to create all objects in one batch, except for SQLite
|
query. The default is to create all objects in one batch, except for SQLite
|
||||||
where the default is such that at maximum 999 variables per query is used.
|
where the default is such that at maximum 999 variables per query is used.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
The ``batch_size`` parameter was added in version 1.5.
|
|
||||||
|
|
||||||
count
|
count
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
@ -1900,10 +1890,6 @@ methods on your models. It does, however, emit the
|
||||||
:data:`~django.db.models.signals.post_delete` signals for all deleted objects
|
:data:`~django.db.models.signals.post_delete` signals for all deleted objects
|
||||||
(including cascaded deletions).
|
(including cascaded deletions).
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Allow fast-path deletion of objects.
|
|
||||||
|
|
||||||
Django needs to fetch objects into memory to send signals and handle cascades.
|
Django needs to fetch objects into memory to send signals and handle cascades.
|
||||||
However, if there are no cascades and no signals, then Django may take a
|
However, if there are no cascades and no signals, then Django may take a
|
||||||
fast-path and delete objects without fetching into memory. For large
|
fast-path and delete objects without fetching into memory. For large
|
||||||
|
@ -1911,7 +1897,7 @@ deletes this can result in significantly reduced memory usage. The amount of
|
||||||
executed queries can be reduced, too.
|
executed queries can be reduced, too.
|
||||||
|
|
||||||
ForeignKeys which are set to :attr:`~django.db.models.ForeignKey.on_delete`
|
ForeignKeys which are set to :attr:`~django.db.models.ForeignKey.on_delete`
|
||||||
DO_NOTHING do not prevent taking the fast-path in deletion.
|
``DO_NOTHING`` do not prevent taking the fast-path in deletion.
|
||||||
|
|
||||||
Note that the queries generated in object deletion is an implementation
|
Note that the queries generated in object deletion is an implementation
|
||||||
detail subject to change.
|
detail subject to change.
|
||||||
|
|
|
@ -93,10 +93,6 @@ All attributes should be considered read-only, unless stated otherwise below.
|
||||||
non-form data posted in the request, access this through the
|
non-form data posted in the request, access this through the
|
||||||
:attr:`HttpRequest.body` attribute instead.
|
:attr:`HttpRequest.body` attribute instead.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Before Django 1.5, HttpRequest.POST contained non-form data.
|
|
||||||
|
|
||||||
It's possible that a request can come in via POST with an empty ``POST``
|
It's possible that a request can come in via POST with an empty ``POST``
|
||||||
dictionary -- if, say, a form is requested via the POST HTTP method but
|
dictionary -- if, say, a form is requested via the POST HTTP method but
|
||||||
does not include form data. Therefore, you shouldn't use ``if request.POST``
|
does not include form data. Therefore, you shouldn't use ``if request.POST``
|
||||||
|
@ -196,8 +192,6 @@ All attributes should be considered read-only, unless stated otherwise below.
|
||||||
|
|
||||||
.. attribute:: HttpRequest.resolver_match
|
.. attribute:: HttpRequest.resolver_match
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
An instance of :class:`~django.core.urlresolvers.ResolverMatch` representing
|
An instance of :class:`~django.core.urlresolvers.ResolverMatch` representing
|
||||||
the resolved url. This attribute is only set after url resolving took place,
|
the resolved url. This attribute is only set after url resolving took place,
|
||||||
which means it's available in all views but not in middleware methods which
|
which means it's available in all views but not in middleware methods which
|
||||||
|
@ -824,8 +818,6 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
|
||||||
StreamingHttpResponse objects
|
StreamingHttpResponse objects
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
.. class:: StreamingHttpResponse
|
.. class:: StreamingHttpResponse
|
||||||
|
|
||||||
The :class:`StreamingHttpResponse` class is used to stream a response from
|
The :class:`StreamingHttpResponse` class is used to stream a response from
|
||||||
|
|
|
@ -1108,6 +1108,19 @@ Default: ``2621440`` (i.e. 2.5 MB).
|
||||||
The maximum size (in bytes) that an upload will be before it gets streamed to
|
The maximum size (in bytes) that an upload will be before it gets streamed to
|
||||||
the file system. See :doc:`/topics/files` for details.
|
the file system. See :doc:`/topics/files` for details.
|
||||||
|
|
||||||
|
.. setting:: FILE_UPLOAD_DIRECTORY_PERMISSIONS
|
||||||
|
|
||||||
|
FILE_UPLOAD_DIRECTORY_PERMISSIONS
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
The numeric mode to apply to directories created in the process of
|
||||||
|
uploading files. This value mirrors the functionality and caveats of
|
||||||
|
the :setting:`FILE_UPLOAD_PERMISSIONS` setting.
|
||||||
|
|
||||||
.. setting:: FILE_UPLOAD_PERMISSIONS
|
.. setting:: FILE_UPLOAD_PERMISSIONS
|
||||||
|
|
||||||
FILE_UPLOAD_PERMISSIONS
|
FILE_UPLOAD_PERMISSIONS
|
||||||
|
@ -1529,6 +1542,8 @@ unpredictable value.
|
||||||
:djadmin:`django-admin.py startproject <startproject>` automatically adds a
|
:djadmin:`django-admin.py startproject <startproject>` automatically adds a
|
||||||
randomly-generated ``SECRET_KEY`` to each new project.
|
randomly-generated ``SECRET_KEY`` to each new project.
|
||||||
|
|
||||||
|
Django will refuse to start if :setting:`SECRET_KEY` is not set.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
**Keep this value secret.**
|
**Keep this value secret.**
|
||||||
|
@ -1537,10 +1552,6 @@ randomly-generated ``SECRET_KEY`` to each new project.
|
||||||
security protections, and can lead to privilege escalation and remote code
|
security protections, and can lead to privilege escalation and remote code
|
||||||
execution vulnerabilities.
|
execution vulnerabilities.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Django will now refuse to start if :setting:`SECRET_KEY` is not set.
|
|
||||||
|
|
||||||
.. setting:: SECURE_PROXY_SSL_HEADER
|
.. setting:: SECURE_PROXY_SSL_HEADER
|
||||||
|
|
||||||
SECURE_PROXY_SSL_HEADER
|
SECURE_PROXY_SSL_HEADER
|
||||||
|
@ -2095,13 +2106,9 @@ The URL where requests are redirected after login when the
|
||||||
This is used by the :func:`~django.contrib.auth.decorators.login_required`
|
This is used by the :func:`~django.contrib.auth.decorators.login_required`
|
||||||
decorator, for example.
|
decorator, for example.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
This setting also accepts view function names and :ref:`named URL patterns
|
||||||
|
<naming-url-patterns>` which can be used to reduce configuration duplication
|
||||||
This setting now also accepts view function names and
|
since you don't have to define the URL in two places (``settings`` and URLconf).
|
||||||
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
|
|
||||||
configuration duplication since you no longer have to define the URL in two
|
|
||||||
places (``settings`` and URLconf).
|
|
||||||
For backward compatibility reasons the default remains unchanged.
|
|
||||||
|
|
||||||
.. setting:: LOGIN_URL
|
.. setting:: LOGIN_URL
|
||||||
|
|
||||||
|
@ -2113,13 +2120,9 @@ Default: ``'/accounts/login/'``
|
||||||
The URL where requests are redirected for login, especially when using the
|
The URL where requests are redirected for login, especially when using the
|
||||||
:func:`~django.contrib.auth.decorators.login_required` decorator.
|
:func:`~django.contrib.auth.decorators.login_required` decorator.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
This setting also accepts view function names and :ref:`named URL patterns
|
||||||
|
<naming-url-patterns>` which can be used to reduce configuration duplication
|
||||||
This setting now also accepts view function names and
|
since you don't have to define the URL in two places (``settings`` and URLconf).
|
||||||
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
|
|
||||||
configuration duplication since you no longer have to define the URL in two
|
|
||||||
places (``settings`` and URLconf).
|
|
||||||
For backward compatibility reasons the default remains unchanged.
|
|
||||||
|
|
||||||
.. setting:: LOGOUT_URL
|
.. setting:: LOGOUT_URL
|
||||||
|
|
||||||
|
@ -2249,7 +2252,9 @@ Controls where Django stores message data. Valid values are:
|
||||||
|
|
||||||
See :ref:`message storage backends <message-storage-backends>` for more details.
|
See :ref:`message storage backends <message-storage-backends>` for more details.
|
||||||
|
|
||||||
The backends that use cookies -- ``CookieStorage`` and ``FallbackStorage`` --
|
The backends that use cookies --
|
||||||
|
:class:`~django.contrib.messages.storage.cookie.CookieStorage` and
|
||||||
|
:class:`~django.contrib.messages.storage.fallback.FallbackStorage` --
|
||||||
use the value of :setting:`SESSION_COOKIE_DOMAIN` when setting their cookies.
|
use the value of :setting:`SESSION_COOKIE_DOMAIN` when setting their cookies.
|
||||||
|
|
||||||
.. setting:: MESSAGE_TAGS
|
.. setting:: MESSAGE_TAGS
|
||||||
|
|
|
@ -118,8 +118,6 @@ Arguments sent with this signal:
|
||||||
``using``
|
``using``
|
||||||
The database alias being used.
|
The database alias being used.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
``update_fields``
|
``update_fields``
|
||||||
The set of fields to update explicitly specified in the ``save()`` method.
|
The set of fields to update explicitly specified in the ``save()`` method.
|
||||||
``None`` if this argument was not used in the ``save()`` call.
|
``None`` if this argument was not used in the ``save()`` call.
|
||||||
|
@ -153,8 +151,6 @@ Arguments sent with this signal:
|
||||||
``using``
|
``using``
|
||||||
The database alias being used.
|
The database alias being used.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
``update_fields``
|
``update_fields``
|
||||||
The set of fields to update explicitly specified in the ``save()`` method.
|
The set of fields to update explicitly specified in the ``save()`` method.
|
||||||
``None`` if this argument was not used in the ``save()`` call.
|
``None`` if this argument was not used in the ``save()`` call.
|
||||||
|
@ -522,14 +518,7 @@ request_finished
|
||||||
.. data:: django.core.signals.request_finished
|
.. data:: django.core.signals.request_finished
|
||||||
:module:
|
:module:
|
||||||
|
|
||||||
Sent when Django finishes processing an HTTP request.
|
Sent when Django finishes delvering an HTTP response to the client.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Before Django 1.5, this signal was sent before delivering content to the
|
|
||||||
client. In order to accommodate :ref:`streaming responses
|
|
||||||
<httpresponse-streaming>`, it is now sent after the response has been fully
|
|
||||||
delivered to the client.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
|
|
@ -75,16 +75,10 @@ Methods
|
||||||
The HTTP Status code for the response.
|
The HTTP Status code for the response.
|
||||||
|
|
||||||
``content_type``
|
``content_type``
|
||||||
|
The value included in the HTTP ``Content-Type`` header, including the
|
||||||
.. versionchanged:: 1.5
|
MIME type specification and the character set encoding. If
|
||||||
|
``content_type`` is specified, then its value is used. Otherwise,
|
||||||
Historically, this parameter was only called ``mimetype`` (now
|
:setting:`DEFAULT_CONTENT_TYPE` is used.
|
||||||
deprecated), but since this is actually the value included in the HTTP
|
|
||||||
``Content-Type`` header, it can also include the character set
|
|
||||||
encoding, which makes it more than just a MIME type specification. If
|
|
||||||
``content_type`` is specified, then its value is used. Otherwise,
|
|
||||||
:setting:`DEFAULT_CONTENT_TYPE` is used.
|
|
||||||
|
|
||||||
|
|
||||||
.. method:: SimpleTemplateResponse.resolve_context(context)
|
.. method:: SimpleTemplateResponse.resolve_context(context)
|
||||||
|
|
||||||
|
@ -167,13 +161,8 @@ Methods
|
||||||
The HTTP Status code for the response.
|
The HTTP Status code for the response.
|
||||||
|
|
||||||
``content_type``
|
``content_type``
|
||||||
|
The value included in the HTTP ``Content-Type`` header, including the
|
||||||
.. versionchanged:: 1.5
|
MIME type specification and the character set encoding. If
|
||||||
|
|
||||||
Historically, this parameter was only called ``mimetype`` (now
|
|
||||||
deprecated), but since this is actually the value included in the HTTP
|
|
||||||
``Content-Type`` header, it can also include the character set
|
|
||||||
encoding, which makes it more than just a MIME type specification. If
|
|
||||||
``content_type`` is specified, then its value is used. Otherwise,
|
``content_type`` is specified, then its value is used. Otherwise,
|
||||||
:setting:`DEFAULT_CONTENT_TYPE` is used.
|
:setting:`DEFAULT_CONTENT_TYPE` is used.
|
||||||
|
|
||||||
|
|
|
@ -274,11 +274,6 @@ Builtin variables
|
||||||
Every context contains ``True``, ``False`` and ``None``. As you would expect,
|
Every context contains ``True``, ``False`` and ``None``. As you would expect,
|
||||||
these variables resolve to the corresponding Python objects.
|
these variables resolve to the corresponding Python objects.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Before Django 1.5, these variables weren't a special case, and they
|
|
||||||
resolved to ``None`` unless you defined them in the context.
|
|
||||||
|
|
||||||
Playing with Context objects
|
Playing with Context objects
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
|
|
@ -1059,22 +1059,14 @@ by the context as to the current application.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
Don't forget to put quotes around the function path or pattern name!
|
Don't forget to put quotes around the function path or pattern name,
|
||||||
|
otherwise the value will be interpreted as a context variable!
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
The first parameter used not to be quoted, which was inconsistent with
|
|
||||||
other template tags. Since Django 1.5, it is evaluated according to
|
|
||||||
the usual rules: it can be a quoted string or a variable that will be
|
|
||||||
looked up in the context.
|
|
||||||
|
|
||||||
.. templatetag:: verbatim
|
.. templatetag:: verbatim
|
||||||
|
|
||||||
verbatim
|
verbatim
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Stops the template engine from rendering the contents of this block tag.
|
Stops the template engine from rendering the contents of this block tag.
|
||||||
|
|
||||||
A common use is to allow a Javascript template layer that collides with
|
A common use is to allow a Javascript template layer that collides with
|
||||||
|
@ -1108,6 +1100,14 @@ If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100, the
|
||||||
image in the above example will be 88 pixels wide
|
image in the above example will be 88 pixels wide
|
||||||
(because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
|
(because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
In some cases you might want to capture the result of ``widthratio`` in a
|
||||||
|
variable. It can be useful, for instance, in a :ttag:`blocktrans` like this::
|
||||||
|
|
||||||
|
{% widthratio this_value max_value max_width as width %}
|
||||||
|
{% blocktrans %}The width is: {{ width }}{% endblocktrans %}
|
||||||
|
|
||||||
.. templatetag:: with
|
.. templatetag:: with
|
||||||
|
|
||||||
with
|
with
|
||||||
|
@ -2403,8 +2403,6 @@ It is also able to consume standard context variables, e.g. assuming a
|
||||||
If you'd like to retrieve a static URL without displaying it, you can use a
|
If you'd like to retrieve a static URL without displaying it, you can use a
|
||||||
slightly different call:
|
slightly different call:
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. code-block:: html+django
|
||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
|
@ -45,28 +45,26 @@ rendering or anywhere else -- you have two choices for encoding those strings.
|
||||||
You can use Unicode strings, or you can use normal strings (sometimes called
|
You can use Unicode strings, or you can use normal strings (sometimes called
|
||||||
"bytestrings") that are encoded using UTF-8.
|
"bytestrings") that are encoded using UTF-8.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
In Python 3, the logic is reversed, that is normal strings are Unicode, and
|
||||||
|
when you want to specifically create a bytestring, you have to prefix the
|
||||||
|
string with a 'b'. As we are doing in Django code from version 1.5,
|
||||||
|
we recommend that you import ``unicode_literals`` from the __future__ library
|
||||||
|
in your code. Then, when you specifically want to create a bytestring literal,
|
||||||
|
prefix the string with 'b'.
|
||||||
|
|
||||||
In Python 3, the logic is reversed, that is normal strings are Unicode, and
|
Python 2 legacy::
|
||||||
when you want to specifically create a bytestring, you have to prefix the
|
|
||||||
string with a 'b'. As we are doing in Django code from version 1.5,
|
|
||||||
we recommend that you import ``unicode_literals`` from the __future__ library
|
|
||||||
in your code. Then, when you specifically want to create a bytestring literal,
|
|
||||||
prefix the string with 'b'.
|
|
||||||
|
|
||||||
Python 2 legacy::
|
my_string = "This is a bytestring"
|
||||||
|
my_unicode = u"This is an Unicode string"
|
||||||
|
|
||||||
my_string = "This is a bytestring"
|
Python 2 with unicode literals or Python 3::
|
||||||
my_unicode = u"This is an Unicode string"
|
|
||||||
|
|
||||||
Python 2 with unicode literals or Python 3::
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
my_string = b"This is a bytestring"
|
||||||
|
my_unicode = "This is an Unicode string"
|
||||||
|
|
||||||
my_string = b"This is a bytestring"
|
See also :doc:`Python 3 compatibility </topics/python3>`.
|
||||||
my_unicode = "This is an Unicode string"
|
|
||||||
|
|
||||||
See also :doc:`Python 3 compatibility </topics/python3>`.
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
|
@ -204,8 +204,6 @@ The functions defined in this module share the following properties:
|
||||||
|
|
||||||
.. function:: smart_text(s, encoding='utf-8', strings_only=False, errors='strict')
|
.. function:: smart_text(s, encoding='utf-8', strings_only=False, errors='strict')
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Returns a text object representing ``s`` -- ``unicode`` on Python 2 and
|
Returns a text object representing ``s`` -- ``unicode`` on Python 2 and
|
||||||
``str`` on Python 3. Treats bytestrings using the ``encoding`` codec.
|
``str`` on Python 3. Treats bytestrings using the ``encoding`` codec.
|
||||||
|
|
||||||
|
@ -225,8 +223,6 @@ The functions defined in this module share the following properties:
|
||||||
|
|
||||||
.. function:: force_text(s, encoding='utf-8', strings_only=False, errors='strict')
|
.. function:: force_text(s, encoding='utf-8', strings_only=False, errors='strict')
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Similar to ``smart_text``, except that lazy instances are resolved to
|
Similar to ``smart_text``, except that lazy instances are resolved to
|
||||||
strings, rather than kept as lazy objects.
|
strings, rather than kept as lazy objects.
|
||||||
|
|
||||||
|
@ -239,8 +235,6 @@ The functions defined in this module share the following properties:
|
||||||
|
|
||||||
.. function:: smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')
|
.. function:: smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Returns a bytestring version of ``s``, encoded as specified in
|
Returns a bytestring version of ``s``, encoded as specified in
|
||||||
``encoding``.
|
``encoding``.
|
||||||
|
|
||||||
|
@ -249,8 +243,6 @@ The functions defined in this module share the following properties:
|
||||||
|
|
||||||
.. function:: force_bytes(s, encoding='utf-8', strings_only=False, errors='strict')
|
.. function:: force_bytes(s, encoding='utf-8', strings_only=False, errors='strict')
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Similar to ``smart_bytes``, except that lazy instances are resolved to
|
Similar to ``smart_bytes``, except that lazy instances are resolved to
|
||||||
bytestrings, rather than kept as lazy objects.
|
bytestrings, rather than kept as lazy objects.
|
||||||
|
|
||||||
|
@ -745,8 +737,6 @@ appropriate entities.
|
||||||
|
|
||||||
.. class:: SafeBytes
|
.. class:: SafeBytes
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
A ``bytes`` subclass that has been specifically marked as "safe"
|
A ``bytes`` subclass that has been specifically marked as "safe"
|
||||||
(requires no further escaping) for HTML output purposes.
|
(requires no further escaping) for HTML output purposes.
|
||||||
|
|
||||||
|
@ -758,8 +748,6 @@ appropriate entities.
|
||||||
|
|
||||||
.. class:: SafeText
|
.. class:: SafeText
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
A ``str`` (in Python 3) or ``unicode`` (in Python 2) subclass
|
A ``str`` (in Python 3) or ``unicode`` (in Python 2) subclass
|
||||||
that has been specifically marked as "safe" for HTML output purposes.
|
that has been specifically marked as "safe" for HTML output purposes.
|
||||||
|
|
||||||
|
@ -977,8 +965,6 @@ For a complete discussion on the usage of the following see the
|
||||||
``None``, the :ref:`current time zone <default-current-time-zone>` is unset
|
``None``, the :ref:`current time zone <default-current-time-zone>` is unset
|
||||||
on entry with :func:`deactivate()` instead.
|
on entry with :func:`deactivate()` instead.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
.. function:: localtime(value, timezone=None)
|
.. function:: localtime(value, timezone=None)
|
||||||
|
|
||||||
Converts an aware :class:`~datetime.datetime` to a different time zone,
|
Converts an aware :class:`~datetime.datetime` to a different time zone,
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
==========================
|
||||||
|
Django 1.3.3 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*August 1, 2012*
|
||||||
|
|
||||||
|
Following Monday's security release of :doc:`Django 1.3.2 </releases/1.3.2>`,
|
||||||
|
we began receiving reports that one of the fixes applied was breaking Python
|
||||||
|
2.4 compatibility for Django 1.3. Since Python 2.4 is a supported Python
|
||||||
|
version for that release series, this release fixes compatibility with
|
||||||
|
Python 2.4.
|
|
@ -0,0 +1,37 @@
|
||||||
|
==========================
|
||||||
|
Django 1.3.4 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*October 17, 2012*
|
||||||
|
|
||||||
|
This is the fourth release in the Django 1.3 series.
|
||||||
|
|
||||||
|
Host header poisoning
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Some parts of Django -- independent of end-user-written applications -- make
|
||||||
|
use of full URLs, including domain name, which are generated from the HTTP Host
|
||||||
|
header. Some attacks against this are beyond Django's ability to control, and
|
||||||
|
require the web server to be properly configured; Django's documentation has
|
||||||
|
for some time contained notes advising users on such configuration.
|
||||||
|
|
||||||
|
Django's own built-in parsing of the Host header is, however, still vulnerable,
|
||||||
|
as was reported to us recently. The Host header parsing in Django 1.3.3 and
|
||||||
|
Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was
|
||||||
|
incorrectly handling username/password information in the header. Thus, for
|
||||||
|
example, the following Host header would be accepted by Django when running on
|
||||||
|
"validsite.com"::
|
||||||
|
|
||||||
|
Host: validsite.com:random@evilsite.com
|
||||||
|
|
||||||
|
Using this, an attacker can cause parts of Django -- particularly the
|
||||||
|
password-reset mechanism -- to generate and display arbitrary URLs to users.
|
||||||
|
|
||||||
|
To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified;
|
||||||
|
Host headers which contain potentially dangerous content (such as
|
||||||
|
username/password pairs) now raise the exception
|
||||||
|
:exc:`django.core.exceptions.SuspiciousOperation`.
|
||||||
|
|
||||||
|
Details of this issue were initially posted online as a `security advisory`_.
|
||||||
|
|
||||||
|
.. _security advisory: https://www.djangoproject.com/weblog/2012/oct/17/security/
|
|
@ -0,0 +1,60 @@
|
||||||
|
==========================
|
||||||
|
Django 1.3.5 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*December 10, 2012*
|
||||||
|
|
||||||
|
Django 1.3.5 addresses two security issues present in previous Django releases
|
||||||
|
in the 1.3 series.
|
||||||
|
|
||||||
|
Please be aware that this security release is slightly different from previous
|
||||||
|
ones. Both issues addressed here have been dealt with in prior security updates
|
||||||
|
to Django. In one case, we have received ongoing reports of problems, and in
|
||||||
|
the other we've chosen to take further steps to tighten up Django's code in
|
||||||
|
response to independent discovery of potential problems from multiple sources.
|
||||||
|
|
||||||
|
Host header poisoning
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Several earlier Django security releases focused on the issue of poisoning the
|
||||||
|
HTTP Host header, causing Django to generate URLs pointing to arbitrary,
|
||||||
|
potentially-malicious domains.
|
||||||
|
|
||||||
|
In response to further input received and reports of continuing issues
|
||||||
|
following the previous release, we're taking additional steps to tighten Host
|
||||||
|
header validation. Rather than attempt to accommodate all features HTTP
|
||||||
|
supports here, Django's Host header validation attempts to support a smaller,
|
||||||
|
but far more common, subset:
|
||||||
|
|
||||||
|
* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot
|
||||||
|
('.').
|
||||||
|
* IP addresses -- both IPv4 and IPv6 -- are permitted.
|
||||||
|
* Port, if specified, is numeric.
|
||||||
|
|
||||||
|
Any deviation from this will now be rejected, raising the exception
|
||||||
|
:exc:`django.core.exceptions.SuspiciousOperation`.
|
||||||
|
|
||||||
|
Redirect poisoning
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Also following up on a previous issue: in July of this year, we made changes to
|
||||||
|
Django's HTTP redirect classes, performing additional validation of the scheme
|
||||||
|
of the URL to redirect to (since, both within Django's own supplied
|
||||||
|
applications and many third-party applications, accepting a user-supplied
|
||||||
|
redirect target is a common pattern).
|
||||||
|
|
||||||
|
Since then, two independent audits of the code turned up further potential
|
||||||
|
problems. So, similar to the Host-header issue, we are taking steps to provide
|
||||||
|
tighter validation in response to reported problems (primarily with third-party
|
||||||
|
applications, but to a certain extent also within Django itself). This comes in
|
||||||
|
two parts:
|
||||||
|
|
||||||
|
1. A new utility function, ``django.utils.http.is_safe_url``, is added; this
|
||||||
|
function takes a URL and a hostname, and checks that the URL is either
|
||||||
|
relative, or if absolute matches the supplied hostname. This function is
|
||||||
|
intended for use whenever user-supplied redirect targets are accepted, to
|
||||||
|
ensure that such redirects cannot lead to arbitrary third-party sites.
|
||||||
|
|
||||||
|
2. All of Django's own built-in views -- primarily in the authentication system
|
||||||
|
-- which allow user-supplied redirect targets now use ``is_safe_url`` to
|
||||||
|
validate the supplied URL.
|
|
@ -0,0 +1,78 @@
|
||||||
|
==========================
|
||||||
|
Django 1.3.6 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*February 19, 2013*
|
||||||
|
|
||||||
|
Django 1.3.6 fixes four security issues present in previous Django releases in
|
||||||
|
the 1.3 series.
|
||||||
|
|
||||||
|
This is the sixth bugfix/security release in the Django 1.3 series.
|
||||||
|
|
||||||
|
|
||||||
|
Host header poisoning
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Some parts of Django -- independent of end-user-written applications -- make
|
||||||
|
use of full URLs, including domain name, which are generated from the HTTP Host
|
||||||
|
header. Django's documentation has for some time contained notes advising users
|
||||||
|
on how to configure webservers to ensure that only valid Host headers can reach
|
||||||
|
the Django application. However, it has been reported to us that even with the
|
||||||
|
recommended webserver configurations there are still techniques available for
|
||||||
|
tricking many common webservers into supplying the application with an
|
||||||
|
incorrect and possibly malicious Host header.
|
||||||
|
|
||||||
|
For this reason, Django 1.3.6 adds a new setting, ``ALLOWED_HOSTS``, which
|
||||||
|
should contain an explicit list of valid host/domain names for this site. A
|
||||||
|
request with a Host header not matching an entry in this list will raise
|
||||||
|
``SuspiciousOperation`` if ``request.get_host()`` is called. For full details
|
||||||
|
see the documentation for the :setting:`ALLOWED_HOSTS` setting.
|
||||||
|
|
||||||
|
The default value for this setting in Django 1.3.6 is ``['*']`` (matching any
|
||||||
|
host), for backwards-compatibility, but we strongly encourage all sites to set
|
||||||
|
a more restrictive value.
|
||||||
|
|
||||||
|
This host validation is disabled when ``DEBUG`` is ``True`` or when running tests.
|
||||||
|
|
||||||
|
|
||||||
|
XML deserialization
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
The XML parser in the Python standard library is vulnerable to a number of
|
||||||
|
attacks via external entities and entity expansion. Django uses this parser for
|
||||||
|
deserializing XML-formatted database fixtures. The fixture deserializer is not
|
||||||
|
intended for use with untrusted data, but in order to err on the side of safety
|
||||||
|
in Django 1.3.6 the XML deserializer refuses to parse an XML document with a
|
||||||
|
DTD (DOCTYPE definition), which closes off these attack avenues.
|
||||||
|
|
||||||
|
These issues in the Python standard library are CVE-2013-1664 and
|
||||||
|
CVE-2013-1665. More information available `from the Python security team`_.
|
||||||
|
|
||||||
|
Django's XML serializer does not create documents with a DTD, so this should
|
||||||
|
not cause any issues with the typical round-trip from ``dumpdata`` to
|
||||||
|
``loaddata``, but if you feed your own XML documents to the ``loaddata``
|
||||||
|
management command, you will need to ensure they do not contain a DTD.
|
||||||
|
|
||||||
|
.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
|
||||||
|
|
||||||
|
|
||||||
|
Formset memory exhaustion
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Previous versions of Django did not validate or limit the form-count data
|
||||||
|
provided by the client in a formset's management form, making it possible to
|
||||||
|
exhaust a server's available memory by forcing it to create very large numbers
|
||||||
|
of forms.
|
||||||
|
|
||||||
|
In Django 1.3.6, all formsets have a strictly-enforced maximum number of forms
|
||||||
|
(1000 by default, though it can be set higher via the ``max_num`` formset
|
||||||
|
factory argument).
|
||||||
|
|
||||||
|
|
||||||
|
Admin history view information leakage
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
In previous versions of Django, an admin user without change permission on a
|
||||||
|
model could still view the unicode representation of instances via their admin
|
||||||
|
history log. Django 1.3.6 now limits the admin history log view for an object
|
||||||
|
to users with change permission for that model.
|
|
@ -0,0 +1,13 @@
|
||||||
|
==========================
|
||||||
|
Django 1.3.7 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*February 20, 2013*
|
||||||
|
|
||||||
|
Django 1.3.7 corrects a packaging problem with yesterday's :doc:`1.3.6 release
|
||||||
|
</releases/1.3.6>`.
|
||||||
|
|
||||||
|
The release contained stray ``.pyc`` files that caused "bad magic number"
|
||||||
|
errors when running with some versions of Python. This releases corrects this,
|
||||||
|
and also fixes a bad documentation link in the project template ``settings.py``
|
||||||
|
file generated by ``manage.py startproject``.
|
|
@ -17,7 +17,7 @@ for some time contained notes advising users on such configuration.
|
||||||
|
|
||||||
Django's own built-in parsing of the Host header is, however, still vulnerable,
|
Django's own built-in parsing of the Host header is, however, still vulnerable,
|
||||||
as was reported to us recently. The Host header parsing in Django 1.3.3 and
|
as was reported to us recently. The Host header parsing in Django 1.3.3 and
|
||||||
Django 1.4.1 -- specifically, django.http.HttpRequest.get_host() -- was
|
Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was
|
||||||
incorrectly handling username/password information in the header. Thus, for
|
incorrectly handling username/password information in the header. Thus, for
|
||||||
example, the following Host header would be accepted by Django when running on
|
example, the following Host header would be accepted by Django when running on
|
||||||
"validsite.com"::
|
"validsite.com"::
|
||||||
|
@ -27,9 +27,10 @@ example, the following Host header would be accepted by Django when running on
|
||||||
Using this, an attacker can cause parts of Django -- particularly the
|
Using this, an attacker can cause parts of Django -- particularly the
|
||||||
password-reset mechanism -- to generate and display arbitrary URLs to users.
|
password-reset mechanism -- to generate and display arbitrary URLs to users.
|
||||||
|
|
||||||
To remedy this, the parsing in HttpRequest.get_host() is being modified; Host
|
To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified;
|
||||||
headers which contain potentially dangerous content (such as username/password
|
Host headers which contain potentially dangerous content (such as
|
||||||
pairs) now raise the exception django.core.exceptions.SuspiciousOperation
|
username/password pairs) now raise the exception
|
||||||
|
:exc:`django.core.exceptions.SuspiciousOperation`.
|
||||||
|
|
||||||
Details of this issue were initially posted online as a `security advisory`_.
|
Details of this issue were initially posted online as a `security advisory`_.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
==========================
|
||||||
|
Django 1.4.3 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*December 10, 2012*
|
||||||
|
|
||||||
|
Django 1.4.3 addresses two security issues present in previous Django releases
|
||||||
|
in the 1.4 series.
|
||||||
|
|
||||||
|
Please be aware that this security release is slightly different from previous
|
||||||
|
ones. Both issues addressed here have been dealt with in prior security updates
|
||||||
|
to Django. In one case, we have received ongoing reports of problems, and in
|
||||||
|
the other we've chosen to take further steps to tighten up Django's code in
|
||||||
|
response to independent discovery of potential problems from multiple sources.
|
||||||
|
|
||||||
|
Host header poisoning
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Several earlier Django security releases focused on the issue of poisoning the
|
||||||
|
HTTP Host header, causing Django to generate URLs pointing to arbitrary,
|
||||||
|
potentially-malicious domains.
|
||||||
|
|
||||||
|
In response to further input received and reports of continuing issues
|
||||||
|
following the previous release, we're taking additional steps to tighten Host
|
||||||
|
header validation. Rather than attempt to accommodate all features HTTP
|
||||||
|
supports here, Django's Host header validation attempts to support a smaller,
|
||||||
|
but far more common, subset:
|
||||||
|
|
||||||
|
* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot
|
||||||
|
('.').
|
||||||
|
* IP addresses -- both IPv4 and IPv6 -- are permitted.
|
||||||
|
* Port, if specified, is numeric.
|
||||||
|
|
||||||
|
Any deviation from this will now be rejected, raising the exception
|
||||||
|
:exc:`django.core.exceptions.SuspiciousOperation`.
|
||||||
|
|
||||||
|
Redirect poisoning
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Also following up on a previous issue: in July of this year, we made changes to
|
||||||
|
Django's HTTP redirect classes, performing additional validation of the scheme
|
||||||
|
of the URL to redirect to (since, both within Django's own supplied
|
||||||
|
applications and many third-party applications, accepting a user-supplied
|
||||||
|
redirect target is a common pattern).
|
||||||
|
|
||||||
|
Since then, two independent audits of the code turned up further potential
|
||||||
|
problems. So, similar to the Host-header issue, we are taking steps to provide
|
||||||
|
tighter validation in response to reported problems (primarily with third-party
|
||||||
|
applications, but to a certain extent also within Django itself). This comes in
|
||||||
|
two parts:
|
||||||
|
|
||||||
|
1. A new utility function, ``django.utils.http.is_safe_url``, is added; this
|
||||||
|
function takes a URL and a hostname, and checks that the URL is either
|
||||||
|
relative, or if absolute matches the supplied hostname. This function is
|
||||||
|
intended for use whenever user-supplied redirect targets are accepted, to
|
||||||
|
ensure that such redirects cannot lead to arbitrary third-party sites.
|
||||||
|
|
||||||
|
2. All of Django's own built-in views -- primarily in the authentication system
|
||||||
|
-- which allow user-supplied redirect targets now use ``is_safe_url`` to
|
||||||
|
validate the supplied URL.
|
|
@ -0,0 +1,88 @@
|
||||||
|
==========================
|
||||||
|
Django 1.4.4 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*February 19, 2013*
|
||||||
|
|
||||||
|
Django 1.4.4 fixes four security issues present in previous Django releases in
|
||||||
|
the 1.4 series, as well as several other bugs and numerous documentation
|
||||||
|
improvements.
|
||||||
|
|
||||||
|
This is the fourth bugfix/security release in the Django 1.4 series.
|
||||||
|
|
||||||
|
|
||||||
|
Host header poisoning
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Some parts of Django -- independent of end-user-written applications -- make
|
||||||
|
use of full URLs, including domain name, which are generated from the HTTP Host
|
||||||
|
header. Django's documentation has for some time contained notes advising users
|
||||||
|
on how to configure webservers to ensure that only valid Host headers can reach
|
||||||
|
the Django application. However, it has been reported to us that even with the
|
||||||
|
recommended webserver configurations there are still techniques available for
|
||||||
|
tricking many common webservers into supplying the application with an
|
||||||
|
incorrect and possibly malicious Host header.
|
||||||
|
|
||||||
|
For this reason, Django 1.4.4 adds a new setting, ``ALLOWED_HOSTS``, containing
|
||||||
|
an explicit list of valid host/domain names for this site. A request with a
|
||||||
|
Host header not matching an entry in this list will raise
|
||||||
|
``SuspiciousOperation`` if ``request.get_host()`` is called. For full details
|
||||||
|
see the documentation for the :setting:`ALLOWED_HOSTS` setting.
|
||||||
|
|
||||||
|
The default value for this setting in Django 1.4.4 is ``['*']`` (matching any
|
||||||
|
host), for backwards-compatibility, but we strongly encourage all sites to set
|
||||||
|
a more restrictive value.
|
||||||
|
|
||||||
|
This host validation is disabled when ``DEBUG`` is ``True`` or when running tests.
|
||||||
|
|
||||||
|
|
||||||
|
XML deserialization
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
The XML parser in the Python standard library is vulnerable to a number of
|
||||||
|
attacks via external entities and entity expansion. Django uses this parser for
|
||||||
|
deserializing XML-formatted database fixtures. This deserializer is not
|
||||||
|
intended for use with untrusted data, but in order to err on the side of safety
|
||||||
|
in Django 1.4.4 the XML deserializer refuses to parse an XML document with a
|
||||||
|
DTD (DOCTYPE definition), which closes off these attack avenues.
|
||||||
|
|
||||||
|
These issues in the Python standard library are CVE-2013-1664 and
|
||||||
|
CVE-2013-1665. More information available `from the Python security team`_.
|
||||||
|
|
||||||
|
Django's XML serializer does not create documents with a DTD, so this should
|
||||||
|
not cause any issues with the typical round-trip from ``dumpdata`` to
|
||||||
|
``loaddata``, but if you feed your own XML documents to the ``loaddata``
|
||||||
|
management command, you will need to ensure they do not contain a DTD.
|
||||||
|
|
||||||
|
.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
|
||||||
|
|
||||||
|
|
||||||
|
Formset memory exhaustion
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Previous versions of Django did not validate or limit the form-count data
|
||||||
|
provided by the client in a formset's management form, making it possible to
|
||||||
|
exhaust a server's available memory by forcing it to create very large numbers
|
||||||
|
of forms.
|
||||||
|
|
||||||
|
In Django 1.4.4, all formsets have a strictly-enforced maximum number of forms
|
||||||
|
(1000 by default, though it can be set higher via the ``max_num`` formset
|
||||||
|
factory argument).
|
||||||
|
|
||||||
|
|
||||||
|
Admin history view information leakage
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
In previous versions of Django, an admin user without change permission on a
|
||||||
|
model could still view the unicode representation of instances via their admin
|
||||||
|
history log. Django 1.4.4 now limits the admin history log view for an object
|
||||||
|
to users with change permission for that model.
|
||||||
|
|
||||||
|
|
||||||
|
Other bugfixes and changes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* Prevented transaction state from leaking from one request to the next (#19707).
|
||||||
|
* Changed a SQL command syntax to be MySQL 4 compatible (#19702).
|
||||||
|
* Added backwards-compatibility with old unsalted MD5 passwords (#18144).
|
||||||
|
* Numerous documentation improvements and fixes.
|
|
@ -0,0 +1,13 @@
|
||||||
|
==========================
|
||||||
|
Django 1.4.5 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*February 20, 2013*
|
||||||
|
|
||||||
|
Django 1.4.5 corrects a packaging problem with yesterday's :doc:`1.4.4 release
|
||||||
|
</releases/1.4.4>`.
|
||||||
|
|
||||||
|
The release contained stray ``.pyc`` files that caused "bad magic number"
|
||||||
|
errors when running with some versions of Python. This releases corrects this,
|
||||||
|
and also fixes a bad documentation link in the project template ``settings.py``
|
||||||
|
file generated by ``manage.py startproject``.
|
|
@ -0,0 +1,31 @@
|
||||||
|
==========================
|
||||||
|
Django 1.4.6 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*August 13, 2013*
|
||||||
|
|
||||||
|
Django 1.4.6 fixes one security issue present in previous Django releases in
|
||||||
|
the 1.4 series, as well as one other bug.
|
||||||
|
|
||||||
|
This is the sixth bugfix/security release in the Django 1.4 series.
|
||||||
|
|
||||||
|
Mitigated possible XSS attack via user-supplied redirect URLs
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
Django relies on user input in some cases (e.g.
|
||||||
|
:func:`django.contrib.auth.views.login`, :mod:`django.contrib.comments`, and
|
||||||
|
:doc:`i18n </topics/i18n/index>`) to redirect the user to an "on success" URL.
|
||||||
|
The security checks for these redirects (namely
|
||||||
|
``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)``
|
||||||
|
and as such allowed ``javascript:...`` URLs to be entered. If a developer
|
||||||
|
relied on ``is_safe_url()`` to provide safe redirect targets and put such a
|
||||||
|
URL into a link, he could suffer from a XSS attack. This bug doesn't affect
|
||||||
|
Django currently, since we only put this URL into the ``Location`` response
|
||||||
|
header and browsers seem to ignore JavaScript there.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
========
|
||||||
|
|
||||||
|
* Fixed an obscure bug with the :func:`~django.test.utils.override_settings`
|
||||||
|
decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
|
||||||
|
'_original_allowed_hosts'`` exception, it's probably fixed (#20636).
|
|
@ -0,0 +1,62 @@
|
||||||
|
==========================
|
||||||
|
Django 1.5.2 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*August 13, 2013*
|
||||||
|
|
||||||
|
This is Django 1.5.2, a bugfix and security release for Django 1.5.
|
||||||
|
|
||||||
|
Mitigated possible XSS attack via user-supplied redirect URLs
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
Django relies on user input in some cases (e.g.
|
||||||
|
:func:`django.contrib.auth.views.login`, :mod:`django.contrib.comments`, and
|
||||||
|
:doc:`i18n </topics/i18n/index>`) to redirect the user to an "on success" URL.
|
||||||
|
The security checks for these redirects (namely
|
||||||
|
``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)``
|
||||||
|
and as such allowed ``javascript:...`` URLs to be entered. If a developer
|
||||||
|
relied on ``is_safe_url()`` to provide safe redirect targets and put such a
|
||||||
|
URL into a link, he could suffer from a XSS attack. This bug doesn't affect
|
||||||
|
Django currently, since we only put this URL into the ``Location`` response
|
||||||
|
header and browsers seem to ignore JavaScript there.
|
||||||
|
|
||||||
|
XSS vulnerability in :mod:`django.contrib.admin`
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
If a :class:`~django.db.models.URLField` is used in Django 1.5, it displays the
|
||||||
|
current value of the field and a link to the target on the admin change page.
|
||||||
|
The display routine of this widget was flawed and allowed for XSS.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
========
|
||||||
|
|
||||||
|
* Fixed a crash with :meth:`~django.db.models.query.QuerySet.prefetch_related`
|
||||||
|
(#19607) as well as some ``pickle`` regressions with ``prefetch_related``
|
||||||
|
(#20157 and #20257).
|
||||||
|
* Fixed a regression in :mod:`django.contrib.gis` in the Google Map output on
|
||||||
|
Python 3 (#20773).
|
||||||
|
* Made ``DjangoTestSuiteRunner.setup_databases`` properly handle aliases for
|
||||||
|
the default database (#19940) and prevented ``teardown_databases`` from
|
||||||
|
attempting to tear down aliases (#20681).
|
||||||
|
* Fixed the ``django.core.cache.backends.memcached.MemcachedCache`` backend's
|
||||||
|
``get_many()`` method on Python 3 (#20722).
|
||||||
|
* Fixed :mod:`django.contrib.humanize` translation syntax errors. Affected
|
||||||
|
languages: Mexican Spanish, Mongolian, Romanian, Turkish (#20695).
|
||||||
|
* Added support for wheel packages (#19252).
|
||||||
|
* The CSRF token now rotates when a user logs in.
|
||||||
|
* Some Python 3 compatibility fixes including #20212 and #20025.
|
||||||
|
* Fixed some rare cases where :meth:`~django.db.models.query.QuerySet.get`
|
||||||
|
exceptions recursed infinitely (#20278).
|
||||||
|
* :djadmin:`makemessages` no longer crashes with ``UnicodeDecodeError``
|
||||||
|
(#20354).
|
||||||
|
* Fixed ``geojson`` detection with Spatialite.
|
||||||
|
* :meth:`~django.test.SimpleTestCase.assertContains` once again works with
|
||||||
|
binary content (#20237).
|
||||||
|
* Fixed :class:`~django.db.models.ManyToManyField` if it has a unicode ``name``
|
||||||
|
parameter (#20207).
|
||||||
|
* Ensured that the WSGI request's path is correctly based on the
|
||||||
|
``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME`
|
||||||
|
setting, regardless of whether or not either has a trailing slash (#20169).
|
||||||
|
* Fixed an obscure bug with the :func:`~django.test.utils.override_settings`
|
||||||
|
decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
|
||||||
|
'_original_allowed_hosts'`` exception, it's probably fixed (#20636).
|
|
@ -2,6 +2,8 @@
|
||||||
Django 1.5 release notes
|
Django 1.5 release notes
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
*February 26, 2013*
|
||||||
|
|
||||||
Welcome to Django 1.5!
|
Welcome to Django 1.5!
|
||||||
|
|
||||||
These release notes cover the `new features`_, as well
|
These release notes cover the `new features`_, as well
|
||||||
|
|
|
@ -130,7 +130,7 @@ Minor features
|
||||||
context level.
|
context level.
|
||||||
|
|
||||||
* The :class:`~django.utils.feedgenerator.Atom1Feed` syndication feed's
|
* The :class:`~django.utils.feedgenerator.Atom1Feed` syndication feed's
|
||||||
``updated`` element now utilizes `updateddate` instead of ``pubdate``,
|
``updated`` element now utilizes ``updateddate`` instead of ``pubdate``,
|
||||||
allowing the ``published`` element to be included in the feed (which
|
allowing the ``published`` element to be included in the feed (which
|
||||||
relies on ``pubdate``).
|
relies on ``pubdate``).
|
||||||
|
|
||||||
|
@ -190,6 +190,30 @@ Minor features
|
||||||
``Meta`` option allows you to customize (or disable) creation of the default
|
``Meta`` option allows you to customize (or disable) creation of the default
|
||||||
add, change, and delete permissions.
|
add, change, and delete permissions.
|
||||||
|
|
||||||
|
* The :func:`~django.contrib.auth.decorators.permission_required` decorator can
|
||||||
|
take a list of permissions as well as a single permission.
|
||||||
|
|
||||||
|
* The new :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` setting controls
|
||||||
|
the file system permissions of directories created during file upload, like
|
||||||
|
:setting:`FILE_UPLOAD_PERMISSIONS` does for the files themselves.
|
||||||
|
|
||||||
|
* Explicit :class:`~django.db.models.OneToOneField` for
|
||||||
|
:ref:`multi-table-inheritance` are now discovered in abstract classes.
|
||||||
|
|
||||||
|
* The ``<label>`` and ``<input>`` tags rendered by
|
||||||
|
:class:`~django.forms.RadioSelect` and
|
||||||
|
:class:`~django.forms.CheckboxSelectMultiple` when looping over the radio
|
||||||
|
buttons or checkboxes now include ``for`` and ``id`` attributes, respectively.
|
||||||
|
Each radio button or checkbox includes an ``id_for_label`` attribute to
|
||||||
|
output the element's ID.
|
||||||
|
|
||||||
|
* Any ``**kwargs`` passed to
|
||||||
|
:meth:`~django.contrib.auth.models.User.email_user()` are passed to the
|
||||||
|
underlying :meth:`~django.core.mail.send_mail()` call.
|
||||||
|
|
||||||
|
* The :ttag:`widthratio` template tag now accepts an "as" parameter to capture
|
||||||
|
the result in a variable.
|
||||||
|
|
||||||
Backwards incompatible changes in 1.7
|
Backwards incompatible changes in 1.7
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
@ -237,6 +261,11 @@ Miscellaneous
|
||||||
removes the ability for visitors to generate spurious HTTP 500 errors by
|
removes the ability for visitors to generate spurious HTTP 500 errors by
|
||||||
requesting static files that don't exist or haven't been collected yet.
|
requesting static files that don't exist or haven't been collected yet.
|
||||||
|
|
||||||
|
* The :meth:`django.db.models.Model.__eq__` method is now defined in a
|
||||||
|
way where instances of a proxy model and its base model are considered
|
||||||
|
equal when primary keys match. Previously only instances of exact same
|
||||||
|
class were considered equal on primary key match.
|
||||||
|
|
||||||
Features deprecated in 1.7
|
Features deprecated in 1.7
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ Final releases
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
1.5.2
|
||||||
1.5.1
|
1.5.1
|
||||||
1.5
|
1.5
|
||||||
|
|
||||||
|
@ -44,6 +45,10 @@ Final releases
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
1.4.6
|
||||||
|
1.4.5
|
||||||
|
1.4.4
|
||||||
|
1.4.3
|
||||||
1.4.2
|
1.4.2
|
||||||
1.4.1
|
1.4.1
|
||||||
1.4
|
1.4
|
||||||
|
@ -53,6 +58,11 @@ Final releases
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
1.3.7
|
||||||
|
1.3.6
|
||||||
|
1.3.5
|
||||||
|
1.3.4
|
||||||
|
1.3.3
|
||||||
1.3.2
|
1.3.2
|
||||||
1.3.1
|
1.3.1
|
||||||
1.3
|
1.3
|
||||||
|
|
|
@ -359,8 +359,6 @@ the extra database load.
|
||||||
Substituting a custom User model
|
Substituting a custom User model
|
||||||
================================
|
================================
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Some kinds of projects may have authentication requirements for which Django's
|
Some kinds of projects may have authentication requirements for which Django's
|
||||||
built-in :class:`~django.contrib.auth.models.User` model is not always
|
built-in :class:`~django.contrib.auth.models.User` model is not always
|
||||||
appropriate. For instance, on some sites it makes more sense to use an email
|
appropriate. For instance, on some sites it makes more sense to use an email
|
||||||
|
@ -684,13 +682,13 @@ auth views.
|
||||||
* :class:`~django.contrib.auth.forms.AuthenticationForm`
|
* :class:`~django.contrib.auth.forms.AuthenticationForm`
|
||||||
|
|
||||||
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`,
|
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`,
|
||||||
and will adapt to use the field defined in `USERNAME_FIELD`.
|
and will adapt to use the field defined in ``USERNAME_FIELD``.
|
||||||
|
|
||||||
* :class:`~django.contrib.auth.forms.PasswordResetForm`
|
* :class:`~django.contrib.auth.forms.PasswordResetForm`
|
||||||
|
|
||||||
Assumes that the user model has an integer primary key, has a field named
|
Assumes that the user model has an integer primary key, has a field named
|
||||||
``email`` that can be used to identify the user, and a boolean field
|
``email`` that can be used to identify the user, and a boolean field
|
||||||
named `is_active` to prevent password resets for inactive users.
|
named ``is_active`` to prevent password resets for inactive users.
|
||||||
|
|
||||||
* :class:`~django.contrib.auth.forms.SetPasswordForm`
|
* :class:`~django.contrib.auth.forms.SetPasswordForm`
|
||||||
|
|
||||||
|
|
|
@ -434,12 +434,10 @@ The login_required decorator
|
||||||
|
|
||||||
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
|
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
The :setting:`settings.LOGIN_URL <LOGIN_URL>` also accepts view function
|
||||||
|
names and :ref:`named URL patterns <naming-url-patterns>`. This allows you
|
||||||
The :setting:`settings.LOGIN_URL <LOGIN_URL>` also accepts
|
to freely remap your login view within your URLconf without having to
|
||||||
view function names and :ref:`named URL patterns <naming-url-patterns>`.
|
update the setting.
|
||||||
This allows you to freely remap your login view within your URLconf
|
|
||||||
without having to update the setting.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -528,6 +526,11 @@ The permission_required decorator
|
||||||
(HTTP Forbidden) view<http_forbidden_view>` instead of redirecting to the
|
(HTTP Forbidden) view<http_forbidden_view>` instead of redirecting to the
|
||||||
login page.
|
login page.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
The :func:`~django.contrib.auth.decorators.permission_required`
|
||||||
|
decorator can take a list of permissions as well as a single permission.
|
||||||
|
|
||||||
Applying permissions to generic views
|
Applying permissions to generic views
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -1182,10 +1185,6 @@ Thus, you can check permissions in template ``{% if %}`` statements:
|
||||||
<p>You don't have permission to do anything in the foo app.</p>
|
<p>You don't have permission to do anything in the foo app.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Permission lookup by "if in".
|
|
||||||
|
|
||||||
It is possible to also look permissions up by ``{% if in %}`` statements.
|
It is possible to also look permissions up by ``{% if in %}`` statements.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|
|
@ -198,6 +198,7 @@ A similar class-based view might look like::
|
||||||
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.views.generic.base import View
|
||||||
|
|
||||||
from .forms import MyForm
|
from .forms import MyForm
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,6 @@ interface to working with templates in class-based views.
|
||||||
override this to provide more flexible defaults when dealing with actual
|
override this to provide more flexible defaults when dealing with actual
|
||||||
objects.
|
objects.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
:class:`~django.views.generic.base.ContextMixin`
|
:class:`~django.views.generic.base.ContextMixin`
|
||||||
Every built in view which needs context data, such as for rendering a
|
Every built in view which needs context data, such as for rendering a
|
||||||
template (including ``TemplateResponseMixin`` above), should call
|
template (including ``TemplateResponseMixin`` above), should call
|
||||||
|
|
|
@ -265,8 +265,8 @@ Methods are copied according to the following rules:
|
||||||
|
|
||||||
- Public methods are copied by default.
|
- Public methods are copied by default.
|
||||||
- Private methods (starting with an underscore) are not copied by default.
|
- Private methods (starting with an underscore) are not copied by default.
|
||||||
- Methods with a `queryset_only` attribute set to `False` are always copied.
|
- Methods with a ``queryset_only`` attribute set to ``False`` are always copied.
|
||||||
- Methods with a `queryset_only` attribute set to `True` are never copied.
|
- Methods with a ``queryset_only`` attribute set to ``True`` are never copied.
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
|
|
|
@ -689,11 +689,6 @@ In addition, some objects are automatically created just after
|
||||||
- three ``Permission`` for each model (including those not stored in that
|
- three ``Permission`` for each model (including those not stored in that
|
||||||
database).
|
database).
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
Previously, ``ContentType`` and ``Permission`` instances were created only
|
|
||||||
in the default database.
|
|
||||||
|
|
||||||
For common setups with multiple databases, it isn't useful to have these
|
For common setups with multiple databases, it isn't useful to have these
|
||||||
objects in more than one database. Common setups include master / slave and
|
objects in more than one database. Common setups include master / slave and
|
||||||
connecting to external databases. Therefore, it's recommended:
|
connecting to external databases. Therefore, it's recommended:
|
||||||
|
|
|
@ -639,20 +639,11 @@ that were modified more than 3 days after they were published::
|
||||||
>>> from datetime import timedelta
|
>>> from datetime import timedelta
|
||||||
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
|
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
The ``F()`` objects support bitwise operations by ``.bitand()`` and
|
||||||
|
|
||||||
``.bitand()`` and ``.bitor()``
|
|
||||||
|
|
||||||
The ``F()`` objects now support bitwise operations by ``.bitand()`` and
|
|
||||||
``.bitor()``, for example::
|
``.bitor()``, for example::
|
||||||
|
|
||||||
>>> F('somefield').bitand(16)
|
>>> F('somefield').bitand(16)
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
The previously undocumented operators ``&`` and ``|`` no longer produce
|
|
||||||
bitwise operations, use ``.bitand()`` and ``.bitor()`` instead.
|
|
||||||
|
|
||||||
The pk lookup shortcut
|
The pk lookup shortcut
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ upload behavior.
|
||||||
Changing upload handler behavior
|
Changing upload handler behavior
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
Three settings control Django's file upload behavior:
|
There are a few settings which control Django's file upload behavior:
|
||||||
|
|
||||||
:setting:`FILE_UPLOAD_MAX_MEMORY_SIZE`
|
:setting:`FILE_UPLOAD_MAX_MEMORY_SIZE`
|
||||||
The maximum size, in bytes, for files that will be uploaded into memory.
|
The maximum size, in bytes, for files that will be uploaded into memory.
|
||||||
|
@ -167,6 +167,11 @@ Three settings control Django's file upload behavior:
|
||||||
|
|
||||||
**Always prefix the mode with a 0.**
|
**Always prefix the mode with a 0.**
|
||||||
|
|
||||||
|
:setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`
|
||||||
|
The numeric mode to apply to directories created in the process of
|
||||||
|
uploading files. This value mirrors the functionality and caveats of
|
||||||
|
the :setting:`FILE_UPLOAD_PERMISSIONS` setting.
|
||||||
|
|
||||||
:setting:`FILE_UPLOAD_HANDLERS`
|
:setting:`FILE_UPLOAD_HANDLERS`
|
||||||
The actual handlers for uploaded files. Changing this setting allows
|
The actual handlers for uploaded files. Changing this setting allows
|
||||||
complete customization -- even replacement -- of Django's upload
|
complete customization -- even replacement -- of Django's upload
|
||||||
|
|
|
@ -204,11 +204,6 @@ reverse order, from the bottom up. This means classes defined at the end of
|
||||||
Dealing with streaming responses
|
Dealing with streaming responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
``response`` may also be an :class:`~django.http.StreamingHttpResponse`
|
|
||||||
object.
|
|
||||||
|
|
||||||
Unlike :class:`~django.http.HttpResponse`,
|
Unlike :class:`~django.http.HttpResponse`,
|
||||||
:class:`~django.http.StreamingHttpResponse` does not have a ``content``
|
:class:`~django.http.StreamingHttpResponse` does not have a ``content``
|
||||||
attribute. As a result, middleware can no longer assume that all responses
|
attribute. As a result, middleware can no longer assume that all responses
|
||||||
|
|
|
@ -70,10 +70,6 @@ If you have multiple caches defined in :setting:`CACHES`, Django will use the
|
||||||
default cache. To use another cache, set :setting:`SESSION_CACHE_ALIAS` to the
|
default cache. To use another cache, set :setting:`SESSION_CACHE_ALIAS` to the
|
||||||
name of that cache.
|
name of that cache.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
The :setting:`SESSION_CACHE_ALIAS` setting was added.
|
|
||||||
|
|
||||||
Once your cache is configured, you've got two choices for how to store data in
|
Once your cache is configured, you've got two choices for how to store data in
|
||||||
the cache:
|
the cache:
|
||||||
|
|
||||||
|
@ -302,8 +298,6 @@ You can edit it multiple times.
|
||||||
|
|
||||||
.. method:: SessionBase.clear_expired
|
.. method:: SessionBase.clear_expired
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Removes expired sessions from the session store. This class method is
|
Removes expired sessions from the session store. This class method is
|
||||||
called by :djadmin:`clearsessions`.
|
called by :djadmin:`clearsessions`.
|
||||||
|
|
||||||
|
@ -469,9 +463,7 @@ cookie will be sent on every request.
|
||||||
Similarly, the ``expires`` part of a session cookie is updated each time the
|
Similarly, the ``expires`` part of a session cookie is updated each time the
|
||||||
session cookie is sent.
|
session cookie is sent.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
The session is not saved if the response's status code is 500.
|
||||||
|
|
||||||
The session is not saved if the response's status code is 500.
|
|
||||||
|
|
||||||
.. _browser-length-vs-persistent-sessions:
|
.. _browser-length-vs-persistent-sessions:
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ introduce controlled coupling for convenience's sake.
|
||||||
:class:`~django.http.HttpResponse` object with that rendered text.
|
:class:`~django.http.HttpResponse` object with that rendered text.
|
||||||
|
|
||||||
:func:`render()` is the same as a call to
|
:func:`render()` is the same as a call to
|
||||||
:func:`render_to_response()` with a `context_instance` argument that
|
:func:`render_to_response()` with a ``context_instance`` argument that
|
||||||
forces the use of a :class:`~django.template.RequestContext`.
|
forces the use of a :class:`~django.template.RequestContext`.
|
||||||
|
|
||||||
Required arguments
|
Required arguments
|
||||||
|
@ -50,10 +50,6 @@ Optional arguments
|
||||||
The MIME type to use for the resulting document. Defaults to the value of
|
The MIME type to use for the resulting document. Defaults to the value of
|
||||||
the :setting:`DEFAULT_CONTENT_TYPE` setting.
|
the :setting:`DEFAULT_CONTENT_TYPE` setting.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
This parameter used to be called ``mimetype``.
|
|
||||||
|
|
||||||
``status``
|
``status``
|
||||||
The status code for the response. Defaults to ``200``.
|
The status code for the response. Defaults to ``200``.
|
||||||
|
|
||||||
|
@ -129,11 +125,6 @@ Optional arguments
|
||||||
The MIME type to use for the resulting document. Defaults to the value of
|
The MIME type to use for the resulting document. Defaults to the value of
|
||||||
the :setting:`DEFAULT_CONTENT_TYPE` setting.
|
the :setting:`DEFAULT_CONTENT_TYPE` setting.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
|
|
||||||
This parameter used to be called ``mimetype``.
|
|
||||||
|
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -169,7 +160,8 @@ This example is equivalent to::
|
||||||
|
|
||||||
The arguments could be:
|
The arguments could be:
|
||||||
|
|
||||||
* A model: the model's `get_absolute_url()` function will be called.
|
* A model: the model's `:meth:`~django.db.models.Model.get_absolute_url()`
|
||||||
|
function will be called.
|
||||||
|
|
||||||
* A view name, possibly with arguments: :func:`urlresolvers.reverse
|
* A view name, possibly with arguments: :func:`urlresolvers.reverse
|
||||||
<django.core.urlresolvers.reverse>` will be used to reverse-resolve the
|
<django.core.urlresolvers.reverse>` will be used to reverse-resolve the
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue