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/>
|
||||
jpellerin@gmail.com
|
||||
junzhang.jn@gmail.com
|
||||
Krzysztof Jurewicz <krzysztof.jurewicz@gmail.com>
|
||||
Xia Kai <http://blog.xiaket.org/>
|
||||
Antti Kaihola <http://djangopeople.net/akaihola/>
|
||||
Peter van Kampen
|
||||
|
@ -418,6 +419,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
Christian Metts
|
||||
michal@plovarna.cz
|
||||
Justin Michalicek <jmichalicek@gmail.com>
|
||||
Bojan Mihelac <bmihelac@mihelac.org>
|
||||
Slawek Mikula <slawek dot mikula at gmail dot com>
|
||||
Katie Miller <katie@sub50.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>
|
||||
Fraser Nevett <mail@nevett.org>
|
||||
Sam Newman <http://www.magpiebrain.com/>
|
||||
Alasdair Nicol <http://al.sdair.co.uk/>
|
||||
Ryan Niemeyer <https://profiles.google.com/ryan.niemeyer/about>
|
||||
Filip Noetzel <http://filip.noetzel.co.uk/>
|
||||
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>
|
||||
Jozko Skrablin <jozko.skrablin@gmail.com>
|
||||
Ben Slavin <benjamin.slavin@gmail.com>
|
||||
Jonathan Slenders
|
||||
sloonz <simon.lipp@insa-lyon.fr>
|
||||
Paul Smith <blinkylights23@gmail.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/>
|
||||
Ville Säävuori <http://www.unessa.net/>
|
||||
Mart Sõmermaa <http://mrts.pri.ee/>
|
||||
Susan Tan <susan.tan.fleckerl@gmail.com>
|
||||
Christian Tanzer <tanzer@swing.co.at>
|
||||
Tyler Tarabula <tyler.tarabula@gmail.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.
|
||||
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.
|
||||
# The directory where this setting is pointing should contain subdirectories
|
||||
# named as the locales, containing a formats.py file
|
||||
|
|
|
@ -23,8 +23,8 @@ ul.actionlist li {
|
|||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.actionlist li.changelink {
|
||||
ul.actionlist li {
|
||||
overflow: hidden;
|
||||
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 import six
|
||||
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):
|
||||
"""
|
||||
|
@ -113,12 +113,20 @@ def get_deleted_objects(objs, opts, user, admin_site, using):
|
|||
has_admin = obj.__class__ in admin_site._registry
|
||||
opts = obj._meta
|
||||
|
||||
no_edit_link = '%s: %s' % (capfirst(opts.verbose_name),
|
||||
force_text(obj))
|
||||
|
||||
if has_admin:
|
||||
admin_url = reverse('%s:%s_%s_change'
|
||||
% (admin_site.name,
|
||||
opts.app_label,
|
||||
opts.model_name),
|
||||
None, (quote(obj._get_pk_val()),))
|
||||
try:
|
||||
admin_url = reverse('%s:%s_%s_change'
|
||||
% (admin_site.name,
|
||||
opts.app_label,
|
||||
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,
|
||||
get_permission_codename('delete', opts))
|
||||
if not user.has_perm(p):
|
||||
|
@ -131,8 +139,7 @@ def get_deleted_objects(objs, opts, user, admin_site, using):
|
|||
else:
|
||||
# Don't display link to edit, because it either has no
|
||||
# admin or is edited inline.
|
||||
return '%s: %s' % (capfirst(opts.verbose_name),
|
||||
force_text(obj))
|
||||
return no_edit_link
|
||||
|
||||
to_delete = collector.nested(format_callback)
|
||||
|
||||
|
@ -155,9 +162,6 @@ class NestedObjects(Collector):
|
|||
if source_attr:
|
||||
self.add_edge(getattr(obj, source_attr), obj)
|
||||
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)
|
||||
try:
|
||||
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)
|
||||
if 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(
|
||||
'<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,
|
||||
_('Change:'), html
|
||||
)
|
||||
|
|
|
@ -64,8 +64,12 @@ def permission_required(perm, login_url=None, raise_exception=False):
|
|||
is raised.
|
||||
"""
|
||||
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)
|
||||
if user.has_perm(perm):
|
||||
if user.has_perms(perms):
|
||||
return True
|
||||
# In case the 403 handler should be called raise the exception
|
||||
if raise_exception:
|
||||
|
|
|
@ -400,11 +400,11 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin):
|
|||
"Returns the short name for the user."
|
||||
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.
|
||||
"""
|
||||
send_mail(subject, message, from_email, [self.email])
|
||||
send_mail(subject, message, from_email, [self.email], **kwargs)
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
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.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
|
||||
|
@ -49,3 +54,54 @@ class LoginRequiredTestCase(AuthViewsTestCase):
|
|||
"""
|
||||
self.testLoginRequired(view_url='/login_required_login_url/',
|
||||
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'])])
|
||||
|
||||
def test_inactive_user_i18n(self):
|
||||
with self.settings(USE_I18N=True):
|
||||
with translation.override('pt-br', deactivate=True):
|
||||
# The user is inactive.
|
||||
data = {
|
||||
'username': 'inactive',
|
||||
'password': 'password',
|
||||
}
|
||||
form = AuthenticationForm(None, data)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEqual(form.non_field_errors(),
|
||||
[force_text(form.error_messages['inactive'])])
|
||||
with self.settings(USE_I18N=True), translation.override('pt-br', deactivate=True):
|
||||
# The user is inactive.
|
||||
data = {
|
||||
'username': 'inactive',
|
||||
'password': 'password',
|
||||
}
|
||||
form = AuthenticationForm(None, data)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEqual(form.non_field_errors(),
|
||||
[force_text(form.error_messages['inactive'])])
|
||||
|
||||
def test_custom_login_allowed_policy(self):
|
||||
# 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)
|
||||
|
||||
def test_default_permissions(self):
|
||||
permission_content_type = ContentType.objects.get_by_natural_key('auth', 'permission')
|
||||
models.Permission._meta.permissions = [
|
||||
('my_custom_permission', 'Some permission'),
|
||||
]
|
||||
create_permissions(models, [], verbosity=0)
|
||||
|
||||
# add/change/delete permission by default + custom permission
|
||||
self.assertEqual(models.Permission.objects.filter(content_type=
|
||||
ContentType.objects.get_by_natural_key('auth', 'permission')
|
||||
self.assertEqual(models.Permission.objects.filter(
|
||||
content_type=permission_content_type,
|
||||
).count(), 4)
|
||||
|
||||
models.Permission.objects.all().delete()
|
||||
models.Permission.objects.filter(content_type=permission_content_type).delete()
|
||||
models.Permission._meta.default_permissions = []
|
||||
create_permissions(models, [], verbosity=0)
|
||||
|
||||
# custom permission only since default permissions is empty
|
||||
self.assertEqual(models.Permission.objects.filter(content_type=
|
||||
ContentType.objects.get_by_natural_key('auth', 'permission')
|
||||
self.assertEqual(models.Permission.objects.filter(
|
||||
content_type=permission_content_type,
|
||||
).count(), 1)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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.core import mail
|
||||
from django.db.models.signals import post_save
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
@ -73,6 +74,29 @@ class UserManagerTestCase(TestCase):
|
|||
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):
|
||||
"""
|
||||
Tests the behavior of the guaranteed is_active attribute
|
||||
|
|
|
@ -446,7 +446,8 @@ class LoginTest(AuthViewsTestCase):
|
|||
for bad_url in ('http://example.com',
|
||||
'https://example.com',
|
||||
'ftp://exampel.com',
|
||||
'//example.com'):
|
||||
'//example.com',
|
||||
'javascript:alert("XSS")'):
|
||||
|
||||
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
|
||||
'url': login_url,
|
||||
|
@ -467,6 +468,7 @@ class LoginTest(AuthViewsTestCase):
|
|||
'/view?param=ftp://exampel.com',
|
||||
'view/?param=//example.com',
|
||||
'https:///',
|
||||
'HTTPS:///',
|
||||
'//testserver/',
|
||||
'/url%20with%20spaces/'): # see ticket #12534
|
||||
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
|
||||
|
@ -661,7 +663,8 @@ class LogoutTest(AuthViewsTestCase):
|
|||
for bad_url in ('http://example.com',
|
||||
'https://example.com',
|
||||
'ftp://exampel.com',
|
||||
'//example.com'):
|
||||
'//example.com',
|
||||
'javascript:alert("XSS")'):
|
||||
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
|
||||
'url': logout_url,
|
||||
'next': REDIRECT_FIELD_NAME,
|
||||
|
@ -680,6 +683,7 @@ class LogoutTest(AuthViewsTestCase):
|
|||
'/view?param=ftp://exampel.com',
|
||||
'view/?param=//example.com',
|
||||
'https:///',
|
||||
'HTTPS:///',
|
||||
'//testserver/',
|
||||
'/url%20with%20spaces/'): # see ticket #12534
|
||||
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
|
||||
|
|
|
@ -9,6 +9,14 @@ class PostGISIntrospection(DatabaseIntrospection):
|
|||
# introspection is actually performed.
|
||||
postgis_types_reverse = {}
|
||||
|
||||
ignored_tables = DatabaseIntrospection.ignored_tables + [
|
||||
'geography_columns',
|
||||
'geometry_columns',
|
||||
'raster_columns',
|
||||
'spatial_ref_sys',
|
||||
'raster_overviews',
|
||||
]
|
||||
|
||||
def get_postgis_types(self):
|
||||
"""
|
||||
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 value
|
||||
|
||||
def get_aggregation(self, using):
|
||||
def get_aggregation(self, using, force_subq=False):
|
||||
# Remove any aggregates marked for reduction from the subquery
|
||||
# and move them to the outer AggregateQuery.
|
||||
connection = connections[using]
|
||||
|
@ -84,7 +84,7 @@ class GeoQuery(sql.Query):
|
|||
if isinstance(aggregate, gis_aggregates.GeoAggregate):
|
||||
if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -40,7 +40,7 @@ class Track(models.Model):
|
|||
def __str__(self): return self.name
|
||||
|
||||
class Truth(models.Model):
|
||||
val = models.BooleanField()
|
||||
val = models.BooleanField(default=False)
|
||||
objects = models.GeoManager()
|
||||
|
||||
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',
|
||||
None)
|
||||
|
||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False):
|
||||
with translation.override('en'):
|
||||
self.humanize_tester(test_list, result_list, 'intcomma')
|
||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False), \
|
||||
translation.override('en'):
|
||||
self.humanize_tester(test_list, result_list, 'intcomma')
|
||||
|
||||
def test_intcomma_without_number_grouping(self):
|
||||
# Regression for #17414
|
||||
with translation.override('ja'):
|
||||
with self.settings(USE_L10N=True):
|
||||
self.humanize_tester([100], ['100'], 'intcomma')
|
||||
with translation.override('ja'), self.settings(USE_L10N=True):
|
||||
self.humanize_tester([100], ['100'], 'intcomma')
|
||||
|
||||
def test_intword(self):
|
||||
test_list = ('100', '1000000', '1200000', '1290000',
|
||||
|
@ -104,18 +103,18 @@ class HumanizeTests(TransRealMixin, TestCase):
|
|||
'100', '1000', '10123', '10311', '1000000', None)
|
||||
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)
|
||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True):
|
||||
with translation.override('de'):
|
||||
self.humanize_tester(test_list, result_list, 'intcomma')
|
||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True), \
|
||||
translation.override('de'):
|
||||
self.humanize_tester(test_list, result_list, 'intcomma')
|
||||
|
||||
def test_i18n_intword(self):
|
||||
test_list = ('100', '1000000', '1200000', '1290000',
|
||||
'1000000000', '2000000000', '6000000000000')
|
||||
result_list = ('100', '1,0 Million', '1,2 Millionen', '1,3 Millionen',
|
||||
'1,0 Milliarde', '2,0 Milliarden', '6,0 Billionen')
|
||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True):
|
||||
with translation.override('de'):
|
||||
self.humanize_tester(test_list, result_list, 'intword')
|
||||
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True), \
|
||||
translation.override('de'):
|
||||
self.humanize_tester(test_list, result_list, 'intword')
|
||||
|
||||
def test_apnumber(self):
|
||||
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
|
||||
try:
|
||||
with override_settings(TIME_ZONE="America/Chicago", USE_TZ=True):
|
||||
with translation.override('en'):
|
||||
self.humanize_tester([dt], ['yesterday'], 'naturalday')
|
||||
with override_settings(TIME_ZONE="America/Chicago", USE_TZ=True), \
|
||||
translation.override('en'):
|
||||
self.humanize_tester([dt], ['yesterday'], 'naturalday')
|
||||
finally:
|
||||
humanize.datetime = orig_humanize_datetime
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
|
||||
def check_test_runner():
|
||||
"""
|
||||
|
@ -24,6 +25,31 @@ def check_test_runner():
|
|||
]
|
||||
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():
|
||||
"""
|
||||
|
@ -31,7 +57,8 @@ def run_checks():
|
|||
messages from all the relevant check functions for this version of Django.
|
||||
"""
|
||||
checks = [
|
||||
check_test_runner()
|
||||
check_test_runner(),
|
||||
check_boolean_field_default_value(),
|
||||
]
|
||||
# Filter out the ``None`` or empty strings.
|
||||
return [output for output in checks if output]
|
||||
|
|
|
@ -172,7 +172,16 @@ class FileSystemStorage(Storage):
|
|||
directory = os.path.dirname(full_path)
|
||||
if not os.path.exists(directory):
|
||||
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:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
|
|
@ -26,6 +26,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
|||
1700: 'DecimalField',
|
||||
}
|
||||
|
||||
ignored_tables = []
|
||||
|
||||
def get_table_list(self, cursor):
|
||||
"Returns a list of table names in the current database."
|
||||
cursor.execute("""
|
||||
|
@ -35,7 +37,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
|||
WHERE c.relkind IN ('r', 'v', '')
|
||||
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
||||
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):
|
||||
"Returns a description of the table, with the DB-API cursor.description interface."
|
||||
|
|
|
@ -184,10 +184,21 @@ class ModelBase(type):
|
|||
else:
|
||||
new_class._meta.concrete_model = new_class
|
||||
|
||||
# Do the appropriate setup for any model parents.
|
||||
o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
|
||||
if isinstance(f, OneToOneField)])
|
||||
# Collect the parent links for multi-table inheritance.
|
||||
parent_links = {}
|
||||
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:
|
||||
original_base = base
|
||||
if not hasattr(base, '_meta'):
|
||||
|
@ -208,8 +219,8 @@ class ModelBase(type):
|
|||
if not base._meta.abstract:
|
||||
# Concrete classes...
|
||||
base = base._meta.concrete_model
|
||||
if base in o2o_map:
|
||||
field = o2o_map[base]
|
||||
if base in parent_links:
|
||||
field = parent_links[base]
|
||||
elif not is_proxy:
|
||||
attr_name = '%s_ptr' % base._meta.model_name
|
||||
field = OneToOneField(base, name=attr_name,
|
||||
|
@ -448,7 +459,9 @@ class Model(six.with_metaclass(ModelBase)):
|
|||
return '%s object' % self.__class__.__name__
|
||||
|
||||
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):
|
||||
return not self.__eq__(other)
|
||||
|
|
|
@ -313,14 +313,13 @@ class QuerySet(object):
|
|||
kwargs[arg.default_alias] = arg
|
||||
|
||||
query = self.query.clone()
|
||||
|
||||
force_subq = query.low_mark != 0 or query.high_mark is not None
|
||||
aggregate_names = []
|
||||
for (alias, aggregate_expr) in kwargs.items():
|
||||
query.add_aggregate(aggregate_expr, self.model, alias,
|
||||
is_summary=True)
|
||||
aggregate_names.append(alias)
|
||||
|
||||
return query.get_aggregation(using=self.db)
|
||||
return query.get_aggregation(using=self.db, force_subq=force_subq)
|
||||
|
||||
def count(self):
|
||||
"""
|
||||
|
|
|
@ -167,7 +167,6 @@ class SQLCompiler(object):
|
|||
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
|
||||
obj.clear_ordering(True)
|
||||
obj.bump_prefix()
|
||||
return obj.get_compiler(connection=self.connection).as_sql()
|
||||
|
||||
def get_columns(self, with_aliases=False):
|
||||
|
@ -808,13 +807,14 @@ class SQLCompiler(object):
|
|||
return result
|
||||
|
||||
def as_subquery_condition(self, alias, columns, qn):
|
||||
inner_qn = self.quote_name_unless_alias
|
||||
qn2 = self.connection.ops.quote_name
|
||||
if len(columns) == 1:
|
||||
sql, params = self.as_sql()
|
||||
return '%s.%s IN (%s)' % (qn(alias), qn2(columns[0]), sql), params
|
||||
|
||||
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]))
|
||||
self.query.where.add(
|
||||
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
|
||||
# from other tables.
|
||||
query = self.query.clone(klass=Query)
|
||||
query.bump_prefix()
|
||||
query.extra = {}
|
||||
query.select = []
|
||||
query.add_fields([query.get_meta().pk.name])
|
||||
|
|
|
@ -97,6 +97,7 @@ class Query(object):
|
|||
LOUTER = 'LEFT OUTER JOIN'
|
||||
|
||||
alias_prefix = 'T'
|
||||
subq_aliases = frozenset([alias_prefix])
|
||||
query_terms = QUERY_TERMS
|
||||
aggregates_module = base_aggregates_module
|
||||
|
||||
|
@ -273,6 +274,10 @@ class Query(object):
|
|||
else:
|
||||
obj.used_aliases = set()
|
||||
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)
|
||||
if hasattr(obj, '_setup_query'):
|
||||
|
@ -310,7 +315,7 @@ class Query(object):
|
|||
# Return value depends on the type of the field being processed.
|
||||
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.
|
||||
"""
|
||||
|
@ -320,18 +325,26 @@ class Query(object):
|
|||
# If there is a group by clause, aggregating does not add useful
|
||||
# information but retrieves only the first row. Aggregate
|
||||
# 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
|
||||
query = AggregateQuery(self.model)
|
||||
|
||||
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
|
||||
# and move them to the outer AggregateQuery.
|
||||
for alias, aggregate in self.aggregate_select.items():
|
||||
if aggregate.is_summary:
|
||||
query.aggregate_select[alias] = aggregate
|
||||
query.aggregate_select[alias] = aggregate.relabeled_clone(relabels)
|
||||
del obj.aggregate_select[alias]
|
||||
|
||||
try:
|
||||
|
@ -780,28 +793,22 @@ class Query(object):
|
|||
data = data._replace(lhs_alias=change_map[lhs])
|
||||
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
|
||||
relabels all the aliases. Even tables that previously had no alias will
|
||||
get an alias after this call (it's mostly used for nested queries and
|
||||
the outer query will already be using the non-aliased table name).
|
||||
|
||||
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.
|
||||
Changes the alias prefix to the next letter in the alphabet in a way
|
||||
that the outer query's aliases and this query's aliases will not
|
||||
conflict. Even tables that previously had no alias will get an alias
|
||||
after this call.
|
||||
"""
|
||||
current = ord(self.alias_prefix)
|
||||
assert current < ord('Z')
|
||||
prefix = chr(current + 1)
|
||||
self.alias_prefix = prefix
|
||||
self.alias_prefix = chr(ord(self.alias_prefix) + 1)
|
||||
while self.alias_prefix in self.subq_aliases:
|
||||
self.alias_prefix = chr(ord(self.alias_prefix) + 1)
|
||||
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()
|
||||
for pos, alias in enumerate(self.tables):
|
||||
if alias in exceptions:
|
||||
continue
|
||||
new_alias = '%s%d' % (prefix, pos)
|
||||
new_alias = '%s%d' % (self.alias_prefix, pos)
|
||||
change_map[alias] = new_alias
|
||||
self.tables[pos] = new_alias
|
||||
self.change_aliases(change_map)
|
||||
|
@ -1005,6 +1012,65 @@ class Query(object):
|
|||
# Add the aggregate to the query
|
||||
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,
|
||||
can_reuse=None):
|
||||
"""
|
||||
|
@ -1033,58 +1099,15 @@ class Query(object):
|
|||
is responsible for unreffing the joins used.
|
||||
"""
|
||||
arg, value = filter_expr
|
||||
parts = arg.split(LOOKUP_SEP)
|
||||
lookup_type, parts = self.solve_lookup_type(arg)
|
||||
if not parts:
|
||||
raise FieldError("Cannot parse keyword query %r" % arg)
|
||||
|
||||
# Work out the lookup type and remove it from the end of 'parts',
|
||||
# if necessary.
|
||||
lookup_type = 'exact' # Default lookup type
|
||||
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
|
||||
value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse)
|
||||
|
||||
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():
|
||||
if alias in (parts[0], LOOKUP_SEP.join(parts)):
|
||||
clause.add((aggregate, lookup_type, value), AND)
|
||||
|
@ -1096,7 +1119,7 @@ class Query(object):
|
|||
|
||||
try:
|
||||
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:
|
||||
can_reuse.update(join_list)
|
||||
except MultiJoin as e:
|
||||
|
@ -1404,7 +1427,6 @@ class Query(object):
|
|||
# Generate the inner query.
|
||||
query = Query(self.model)
|
||||
query.where.add(query.build_filter(filter_expr), AND)
|
||||
query.bump_prefix()
|
||||
query.clear_ordering(True)
|
||||
# Try to have as simple as possible subquery -> trim leading joins from
|
||||
# the subquery.
|
||||
|
|
|
@ -434,7 +434,9 @@ class BoundField(object):
|
|||
This really is only useful for RadioSelect widgets, so that you can
|
||||
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
|
||||
|
||||
def __len__(self):
|
||||
|
|
|
@ -601,16 +601,15 @@ class ChoiceInput(SubWidget):
|
|||
self.choice_value = force_text(choice[0])
|
||||
self.choice_label = force_text(choice[1])
|
||||
self.index = index
|
||||
if 'id' in self.attrs:
|
||||
self.attrs['id'] += "_%d" % self.index
|
||||
|
||||
def __str__(self):
|
||||
return self.render()
|
||||
|
||||
def render(self, name=None, value=None, attrs=None, choices=()):
|
||||
name = name or self.name
|
||||
value = value or self.value
|
||||
attrs = attrs or self.attrs
|
||||
if 'id' in self.attrs:
|
||||
label_for = format_html(' for="{0}_{1}"', self.attrs['id'], self.index)
|
||||
if self.id_for_label:
|
||||
label_for = format_html(' for="{0}"', self.id_for_label)
|
||||
else:
|
||||
label_for = ''
|
||||
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
|
||||
|
||||
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)
|
||||
if self.is_checked():
|
||||
final_attrs['checked'] = 'checked'
|
||||
return format_html('<input{0} />', flatatt(final_attrs))
|
||||
|
||||
@property
|
||||
def id_for_label(self):
|
||||
return self.attrs.get('id', '')
|
||||
|
||||
|
||||
class RadioChoiceInput(ChoiceInput):
|
||||
input_type = 'radio'
|
||||
|
|
|
@ -6,7 +6,7 @@ from importlib import import_module
|
|||
from inspect import getargspec
|
||||
|
||||
from django.conf import settings
|
||||
from django.template.context import (Context, RequestContext,
|
||||
from django.template.context import (BaseContext, Context, RequestContext,
|
||||
ContextPopException)
|
||||
from django.utils.itercompat import is_iterable
|
||||
from django.utils.text import (smart_split, unescape_string_literal,
|
||||
|
@ -765,6 +765,9 @@ class Variable(object):
|
|||
current = current[bit]
|
||||
except (TypeError, AttributeError, KeyError, ValueError):
|
||||
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)
|
||||
except (TypeError, AttributeError):
|
||||
try: # list-index lookup
|
||||
|
|
|
@ -458,10 +458,11 @@ class VerbatimNode(Node):
|
|||
return self.content
|
||||
|
||||
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.max_expr = max_expr
|
||||
self.max_width = max_width
|
||||
self.asvar = asvar
|
||||
|
||||
def render(self, context):
|
||||
try:
|
||||
|
@ -480,7 +481,13 @@ class WidthRatioNode(Node):
|
|||
return '0'
|
||||
except (ValueError, TypeError):
|
||||
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):
|
||||
def __init__(self, var, name, nodelist, extra_context=None):
|
||||
|
@ -1353,20 +1360,34 @@ def widthratio(parser, token):
|
|||
|
||||
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,
|
||||
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).
|
||||
|
||||
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()
|
||||
if len(bits) != 4:
|
||||
raise TemplateSyntaxError("widthratio takes three arguments")
|
||||
tag, this_value_expr, max_value_expr, max_width = bits
|
||||
if len(bits) == 4:
|
||||
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),
|
||||
parser.compile_filter(max_value_expr),
|
||||
parser.compile_filter(max_width))
|
||||
parser.compile_filter(max_width),
|
||||
asvar=asvar)
|
||||
|
||||
@register.tag('with')
|
||||
def do_with(parser, token):
|
||||
|
|
|
@ -37,6 +37,7 @@ BOUNDARY = 'BoUnDaRyStRiNg'
|
|||
MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
|
||||
CONTENT_TYPE_RE = re.compile('.*; charset=([\w\d-]+);?')
|
||||
|
||||
|
||||
class FakePayload(object):
|
||||
"""
|
||||
A wrapper around BytesIO that restricts what can be read since data from
|
||||
|
@ -123,6 +124,7 @@ class ClientHandler(BaseHandler):
|
|||
|
||||
return response
|
||||
|
||||
|
||||
def store_rendered_templates(store, signal, sender, template, context, **kwargs):
|
||||
"""
|
||||
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('context', ContextList()).append(copy(context))
|
||||
|
||||
|
||||
def encode_multipart(boundary, data):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
def encode_file(boundary, key, file):
|
||||
to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
|
||||
if hasattr(file, 'content_type'):
|
||||
|
@ -189,8 +193,8 @@ def encode_file(boundary, key, file):
|
|||
content_type = 'application/octet-stream'
|
||||
return [
|
||||
to_bytes('--%s' % boundary),
|
||||
to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"' \
|
||||
% (key, os.path.basename(file.name))),
|
||||
to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"'
|
||||
% (key, os.path.basename(file.name))),
|
||||
to_bytes('Content-Type: %s' % content_type),
|
||||
b'',
|
||||
file.read()
|
||||
|
@ -274,14 +278,11 @@ class RequestFactory(object):
|
|||
def get(self, path, data={}, **extra):
|
||||
"Construct a GET request."
|
||||
|
||||
parsed = urlparse(path)
|
||||
r = {
|
||||
'PATH_INFO': self._get_path(parsed),
|
||||
'QUERY_STRING': urlencode(data, doseq=True) or force_str(parsed[4]),
|
||||
'REQUEST_METHOD': str('GET'),
|
||||
'QUERY_STRING': urlencode(data, doseq=True),
|
||||
}
|
||||
r.update(extra)
|
||||
return self.request(**r)
|
||||
return self.generic('GET', path, **r)
|
||||
|
||||
def post(self, path, data={}, content_type=MULTIPART_CONTENT,
|
||||
**extra):
|
||||
|
@ -289,32 +290,19 @@ class RequestFactory(object):
|
|||
|
||||
post_data = self._encode_data(data, content_type)
|
||||
|
||||
parsed = urlparse(path)
|
||||
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)
|
||||
return self.generic('POST', path, post_data, content_type, **extra)
|
||||
|
||||
def head(self, path, data={}, **extra):
|
||||
"Construct a HEAD request."
|
||||
|
||||
parsed = urlparse(path)
|
||||
r = {
|
||||
'PATH_INFO': self._get_path(parsed),
|
||||
'QUERY_STRING': urlencode(data, doseq=True) or force_str(parsed[4]),
|
||||
'REQUEST_METHOD': str('HEAD'),
|
||||
'QUERY_STRING': urlencode(data, doseq=True),
|
||||
}
|
||||
r.update(extra)
|
||||
return self.request(**r)
|
||||
return self.generic('HEAD', path, **r)
|
||||
|
||||
def options(self, path, data='', content_type='application/octet-stream',
|
||||
**extra):
|
||||
**extra):
|
||||
"Construct an OPTIONS request."
|
||||
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)
|
||||
|
||||
def patch(self, path, data='', content_type='application/octet-stream',
|
||||
**extra):
|
||||
**extra):
|
||||
"Construct a PATCH request."
|
||||
return self.generic('PATCH', path, data, content_type, **extra)
|
||||
|
||||
def delete(self, path, data='', content_type='application/octet-stream',
|
||||
**extra):
|
||||
**extra):
|
||||
"Construct a DELETE request."
|
||||
return self.generic('DELETE', path, data, content_type, **extra)
|
||||
|
||||
def generic(self, method, path,
|
||||
data='', content_type='application/octet-stream', **extra):
|
||||
"""Constructs an arbitrary HTTP request."""
|
||||
parsed = urlparse(path)
|
||||
data = force_bytes(data, settings.DEFAULT_CHARSET)
|
||||
r = {
|
||||
'PATH_INFO': self._get_path(parsed),
|
||||
'QUERY_STRING': force_str(parsed[4]),
|
||||
'REQUEST_METHOD': str(method),
|
||||
}
|
||||
if data:
|
||||
|
@ -349,8 +337,12 @@ class RequestFactory(object):
|
|||
'wsgi.input': FakePayload(data),
|
||||
})
|
||||
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)
|
||||
|
||||
|
||||
class Client(RequestFactory):
|
||||
"""
|
||||
A class that can act as a client for testing purposes.
|
||||
|
@ -392,7 +384,6 @@ class Client(RequestFactory):
|
|||
return {}
|
||||
session = property(_session)
|
||||
|
||||
|
||||
def request(self, **request):
|
||||
"""
|
||||
The master request method. Composes the environment dictionary
|
||||
|
@ -406,7 +397,8 @@ class Client(RequestFactory):
|
|||
# callback function.
|
||||
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.
|
||||
got_request_exception.connect(self.store_exc_info, dispatch_uid="request-exception")
|
||||
try:
|
||||
|
@ -452,7 +444,7 @@ class Client(RequestFactory):
|
|||
|
||||
return response
|
||||
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")
|
||||
|
||||
def get(self, path, data={}, follow=False, **extra):
|
||||
|
@ -484,12 +476,11 @@ class Client(RequestFactory):
|
|||
return response
|
||||
|
||||
def options(self, path, data='', content_type='application/octet-stream',
|
||||
follow=False, **extra):
|
||||
follow=False, **extra):
|
||||
"""
|
||||
Request a response from the server using OPTIONS.
|
||||
"""
|
||||
response = super(Client, self).options(path,
|
||||
data=data, content_type=content_type, **extra)
|
||||
response = super(Client, self).options(path, data=data, content_type=content_type, **extra)
|
||||
if follow:
|
||||
response = self._handle_redirects(response, **extra)
|
||||
return response
|
||||
|
@ -499,14 +490,13 @@ class Client(RequestFactory):
|
|||
"""
|
||||
Send a resource to the server using PUT.
|
||||
"""
|
||||
response = super(Client, self).put(path,
|
||||
data=data, content_type=content_type, **extra)
|
||||
response = super(Client, self).put(path, data=data, content_type=content_type, **extra)
|
||||
if follow:
|
||||
response = self._handle_redirects(response, **extra)
|
||||
return response
|
||||
|
||||
def patch(self, path, data='', content_type='application/octet-stream',
|
||||
follow=False, **extra):
|
||||
follow=False, **extra):
|
||||
"""
|
||||
Send a resource to the server using PATCH.
|
||||
"""
|
||||
|
@ -517,12 +507,12 @@ class Client(RequestFactory):
|
|||
return response
|
||||
|
||||
def delete(self, path, data='', content_type='application/octet-stream',
|
||||
follow=False, **extra):
|
||||
follow=False, **extra):
|
||||
"""
|
||||
Send a DELETE request to the server.
|
||||
"""
|
||||
response = super(Client, self).delete(path,
|
||||
data=data, content_type=content_type, **extra)
|
||||
response = super(Client, self).delete(
|
||||
path, data=data, content_type=content_type, **extra)
|
||||
if follow:
|
||||
response = self._handle_redirects(response, **extra)
|
||||
return response
|
||||
|
|
|
@ -263,17 +263,12 @@ class LazyObject(object):
|
|||
__dir__ = new_method_proxy(dir)
|
||||
|
||||
# Dictionary methods support
|
||||
@new_method_proxy
|
||||
def __getitem__(self, key):
|
||||
return self[key]
|
||||
__getitem__ = new_method_proxy(operator.getitem)
|
||||
__setitem__ = new_method_proxy(operator.setitem)
|
||||
__delitem__ = new_method_proxy(operator.delitem)
|
||||
|
||||
@new_method_proxy
|
||||
def __setitem__(self, key, value):
|
||||
self[key] = value
|
||||
|
||||
@new_method_proxy
|
||||
def __delitem__(self, key):
|
||||
del self[key]
|
||||
__len__ = new_method_proxy(len)
|
||||
__contains__ = new_method_proxy(operator.contains)
|
||||
|
||||
|
||||
# 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'.
|
||||
"""
|
||||
rfcdate = formatdate(epoch_seconds)
|
||||
return '%s GMT' % rfcdate[:25]
|
||||
return formatdate(epoch_seconds, usegmt=True)
|
||||
|
||||
def parse_http_date(date):
|
||||
"""
|
||||
|
@ -253,11 +252,12 @@ def same_origin(url1, url2):
|
|||
def is_safe_url(url, host=None):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if not url:
|
||||
return False
|
||||
netloc = urllib_parse.urlparse(url)[1]
|
||||
return not netloc or netloc == host
|
||||
url_info = urllib_parse.urlparse(url)
|
||||
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"
|
||||
|
||||
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):
|
||||
from django.template.loader import template_source_loaders
|
||||
|
@ -295,13 +295,13 @@ class ExceptionReporter(object):
|
|||
def get_traceback_html(self):
|
||||
"Return HTML version of debug 500 HTTP error page."
|
||||
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)
|
||||
|
||||
def get_traceback_text(self):
|
||||
"Return plain text version of debug 500 HTTP error page."
|
||||
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)
|
||||
|
||||
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
|
||||
custom field performs whatever input validation and data cleaning is
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
and especially scaling in more detail. However, note that this edition
|
||||
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/
|
||||
|
|
|
@ -16,7 +16,7 @@ version >= 2.2 and mod_wsgi >= 2.0. For example, you could:
|
|||
|
||||
.. note::
|
||||
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
|
||||
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
|
||||
|
|
|
@ -193,7 +193,7 @@ other approaches:
|
|||
configuration).
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
|
|
@ -370,8 +370,11 @@ these changes.
|
|||
* Remove the backward compatible shims introduced to rename the attributes
|
||||
``ChangeList.root_query_set`` and ``ChangeList.query_set``.
|
||||
|
||||
* ``django.conf.urls.shortcut`` and ``django.views.defaults.shortcut`` will be
|
||||
removed.
|
||||
* ``django.views.defaults.shortcut`` will be removed, as part of the
|
||||
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
|
||||
no longer appears to be actively maintained & does not work on Python 3.
|
||||
|
@ -442,11 +445,5 @@ these changes.
|
|||
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
|
||||
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
|
||||
any time leading up to the actual release:
|
||||
|
||||
#. If this is a security release, send out pre-notification **one week**
|
||||
before the release. We maintain a list of who gets these pre-notification
|
||||
emails at *FIXME WHERE?*. This email should be signed by the key you'll use
|
||||
for the release, and should include patches for each issue being fixed.
|
||||
#. If this is a security release, send out pre-notification **one week** before
|
||||
the release. We maintain a list of who gets these pre-notification emails in
|
||||
the private ``django-core`` repository. This email should be signed by the
|
||||
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
|
||||
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
|
||||
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 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 --
|
||||
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
|
||||
emails will be signed with the same key used to sign Django releases;
|
||||
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
|
||||
admin.
|
||||
|
||||
.. admonition:: `__unicode__` or `__str__`?
|
||||
.. admonition:: ``__unicode__`` or ``__str__``?
|
||||
|
||||
On Python 3, things are simpler, just use
|
||||
: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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -104,12 +104,6 @@ TemplateView
|
|||
Renders a given template, with the context containing parameters captured
|
||||
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)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
|
|
@ -138,24 +138,16 @@ YearArchiveView
|
|||
* ``year``: A :class:`~datetime.date` object
|
||||
representing the given year.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
Previously, this returned a string.
|
||||
|
||||
* ``next_year``: A :class:`~datetime.date` object
|
||||
representing the first day of the next year, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
* ``previous_year``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous year, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
**Notes**
|
||||
|
||||
* Uses a default ``template_name_suffix`` of ``_archive_year``.
|
||||
|
|
|
@ -328,8 +328,3 @@ BaseDateListView
|
|||
:meth:`~BaseDateListView.get_date_list_period` is used. ``date_type``
|
||||
and ``ordering`` are simply passed to
|
||||
: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
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
A string specifying the name to use for the page parameter.
|
||||
The view will expect this prameter to be available either as a query
|
||||
string parameter (via ``request.GET``) or as a kwarg variable specified
|
||||
|
|
|
@ -7,8 +7,6 @@ ContextMixin
|
|||
|
||||
.. class:: django.views.generic.base.ContextMixin
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: get_context_data(**kwargs)
|
||||
|
@ -77,8 +75,6 @@ TemplateResponseMixin
|
|||
|
||||
.. attribute:: content_type
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
The content type to use for the response. ``content_type`` is passed
|
||||
as a keyword argument to ``response_class``. Default is ``None`` --
|
||||
meaning that Django uses :setting:`DEFAULT_CONTENT_TYPE`.
|
||||
|
|
|
@ -269,8 +269,8 @@ Making actions available site-wide
|
|||
|
||||
admin.site.add_action(export_selected_objects)
|
||||
|
||||
This makes the `export_selected_objects` action globally available as an
|
||||
action named `"export_selected_objects"`. You can explicitly give the action
|
||||
This makes the ``export_selected_objects`` action globally available as an
|
||||
action named "export_selected_objects". You can explicitly give the action
|
||||
a name -- good if you later want to programmatically :ref:`remove the action
|
||||
<disabling-admin-actions>` -- by passing a second argument to
|
||||
:meth:`AdminSite.add_action()`::
|
||||
|
|
|
@ -156,6 +156,6 @@ Edit this object
|
|||
Using these bookmarklets requires that you are either logged into the
|
||||
:mod:`Django admin <django.contrib.admin>` as a
|
||||
: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
|
||||
address listed in :setting:`INTERNAL_IPS`.
|
||||
|
|
|
@ -1235,8 +1235,6 @@ templates used by the :class:`ModelAdmin` views:
|
|||
|
||||
.. method:: ModelAdmin.get_list_filter(self, request)
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
The ``get_list_filter`` method is given the ``HttpRequest`` and is expected
|
||||
to return the same kind of sequence type as for the
|
||||
: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)
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
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
|
||||
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`
|
||||
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
|
||||
tags, or fail silently if the ``contrib.messages`` framework is not
|
||||
installed. These keyword arguments match those for
|
||||
|
|
|
@ -215,11 +215,16 @@ Methods
|
|||
(the Django app label). If the user is inactive, this method will
|
||||
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
|
||||
the :setting:`DEFAULT_FROM_EMAIL`.
|
||||
|
||||
.. versionchanged:: 1.7
|
||||
|
||||
Any ``**kwargs`` are passed to the underlying
|
||||
:meth:`~django.core.mail.send_mail()` call.
|
||||
|
||||
Manager methods
|
||||
---------------
|
||||
|
||||
|
@ -384,8 +389,6 @@ can be used for notification when a user logs in or out.
|
|||
|
||||
.. function:: user_login_failed
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Sent when the user failed to login successfully
|
||||
|
||||
``sender``
|
||||
|
|
|
@ -199,14 +199,18 @@ The ``ContentTypeManager``
|
|||
|
||||
Takes either a model class or an instance of a model, and returns the
|
||||
: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])
|
||||
|
||||
Takes a variadic number of model classes, and returns a dictionary
|
||||
mapping the model classes to the
|
||||
: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)
|
||||
|
||||
|
@ -232,21 +236,6 @@ lookup::
|
|||
|
||||
.. _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
|
||||
=================
|
||||
|
||||
|
|
|
@ -949,10 +949,6 @@ __ http://geohash.org/
|
|||
|
||||
*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
|
||||
`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
|
||||
:attr:`GEOSGeometry.hexewkb` property instead).
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
Prior to Django 1.5, the Z value of the geometry was dropped.
|
||||
|
||||
.. attribute:: GEOSGeometry.hexewkb
|
||||
|
||||
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
|
||||
:attr:`GEOSGeometry.ewkb` property instead.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
Prior to Django 1.5, the Z value of the geometry was dropped.
|
||||
|
||||
.. _ewkb:
|
||||
|
||||
.. attribute:: GEOSGeometry.ewkb
|
||||
|
@ -426,8 +418,6 @@ geometry that do not make up other.
|
|||
.. method:: GEOSGeometry.interpolate(distance)
|
||||
.. method:: GEOSGeometry.interpolate_normalized(distance)
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Given a distance (float), returns the point (or closest point) within the
|
||||
geometry (:class:`LineString` or :class:`MultiLineString`) at that distance.
|
||||
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_normalized(point)
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Returns the distance (float) from the origin of the geometry
|
||||
(: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).
|
||||
|
|
|
@ -54,20 +54,21 @@ Storage backends
|
|||
|
||||
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
|
||||
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
|
||||
to prevent manipulation) to persist notifications across requests. Old
|
||||
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
|
||||
``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'
|
||||
|
||||
.. class:: storage.base.BaseStorage
|
||||
|
||||
To write your own storage class, subclass the ``BaseStorage`` class in
|
||||
``django.contrib.messages.storage.base`` and implement the ``_get`` and
|
||||
``_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
|
||||
``{{ site_name }}``.
|
||||
|
||||
* The shortcut view (``django.views.defaults.shortcut``) uses the domain
|
||||
of the current :class:`~django.contrib.sites.models.Site` object when
|
||||
calculating an object's URL.
|
||||
* The shortcut view (``django.contrib.contenttypes.views.shortcut``)
|
||||
uses the domain of the current
|
||||
: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
|
||||
:class:`~django.contrib.sites.models.Site` to work out the domain for the
|
||||
|
|
|
@ -255,8 +255,6 @@ CachedStaticFilesStorage
|
|||
|
||||
.. method:: file_hash(name, content=None)
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
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.
|
||||
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
|
||||
in :ref:`staticfiles-from-cdn`.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
If you'd like to retrieve a static URL without displaying it, you can use a
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -254,8 +254,6 @@ to flush.
|
|||
``--no-initial-data``
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
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
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
The :djadminopt:`--ignorenonexistent` option can be used to ignore fields that
|
||||
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
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
The ``--interface`` option was added in Django 1.5.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
The ``--no-startup`` option was added in Django 1.6.
|
||||
|
@ -1337,8 +1329,6 @@ clearsessions
|
|||
|
||||
.. django-admin:: clearsessions
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Can be run as a cron job or directly to clean out expired sessions.
|
||||
|
||||
``django.contrib.sitemaps``
|
||||
|
|
|
@ -100,10 +100,6 @@ The ``ContentFile`` Class
|
|||
f1 = ContentFile("esta sentencia está en español")
|
||||
f2 = ContentFile(b"these are bytes")
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
ContentFile also accepts Unicode strings.
|
||||
|
||||
.. currentmodule:: django.core.files.images
|
||||
|
||||
The ``ImageFile`` Class
|
||||
|
|
|
@ -211,11 +211,6 @@ only the valid fields::
|
|||
>>> f.cleaned_data
|
||||
{'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
|
||||
``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,
|
||||
|
|
|
@ -573,16 +573,12 @@ For each field, we describe the default widget used if you don't specify
|
|||
|
||||
.. attribute:: allow_files
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
|
||||
whether files in the specified location should be included. Either this or
|
||||
:attr:`allow_folders` must be ``True``.
|
||||
|
||||
.. attribute:: allow_folders
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
||||
whether folders in the specified location should be included. Either this or
|
||||
: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``,
|
||||
``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
|
||||
|
||||
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
|
||||
considering aren't valid, we must remember to remove them from the
|
||||
``cleaned_data``.
|
||||
|
||||
.. 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.
|
||||
``cleaned_data``. `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
|
||||
``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``
|
||||
~~~~~~~~~~
|
||||
|
||||
|
@ -590,25 +585,26 @@ Selector and checkbox widgets
|
|||
.. code-block:: html
|
||||
|
||||
<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 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 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 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>
|
||||
|
||||
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
|
||||
|
||||
{% for radio in myform.beatles %}
|
||||
<label>
|
||||
<label for="{{ radio.id_for_label }}">
|
||||
{{ radio.choice_label }}
|
||||
<span class="radio">{{ radio.tag }}</span>
|
||||
</label>
|
||||
|
@ -618,31 +614,41 @@ Selector and checkbox widgets
|
|||
|
||||
.. code-block:: html
|
||||
|
||||
<label>
|
||||
John
|
||||
<span class="radio"><input type="radio" name="beatles" value="john" /></span>
|
||||
</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>
|
||||
<label for="id_beatles_0">
|
||||
John
|
||||
<span class="radio"><input id="id_beatles_0" name="beatles" type="radio" value="john" /></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.
|
||||
<label for="id_beatles_1">
|
||||
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
|
||||
|
||||
The outer ``<ul>`` container will now receive the ``id`` attribute defined on
|
||||
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``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -666,6 +672,12 @@ the widget.
|
|||
Like :class:`RadioSelect`, you can now loop over the individual checkboxes making
|
||||
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
|
||||
|
|
|
@ -757,21 +757,16 @@ directory on the filesystem. Has three special arguments, of which the first is
|
|||
|
||||
.. attribute:: FilePathField.allow_files
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
|
||||
whether files in the specified location should be included. Either this or
|
||||
:attr:`~FilePathField.allow_folders` must be ``True``.
|
||||
|
||||
.. attribute:: FilePathField.allow_folders
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
||||
whether folders in the specified location should be included. Either this
|
||||
or :attr:`~FilePathField.allow_files` must be ``True``.
|
||||
|
||||
|
||||
Of course, these arguments can be used together.
|
||||
|
||||
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`, 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
|
||||
===================
|
||||
|
||||
|
|
|
@ -388,8 +388,6 @@ For more details, see the documentation on :ref:`F() expressions
|
|||
Specifying which fields to save
|
||||
-------------------------------
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
If ``save()`` is passed a list of field names in keyword argument
|
||||
``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
|
||||
|
@ -494,6 +492,40 @@ using ``__str__()`` like this::
|
|||
# first_name and last_name will be unicode strings.
|
||||
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``
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -286,8 +286,6 @@ Django quotes column and table names behind the scenes.
|
|||
|
||||
.. attribute:: Options.index_together
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Sets of field names that, taken together, are indexed::
|
||||
|
||||
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.
|
||||
|
||||
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::
|
||||
|
||||
>>> non_prefetched = qs.prefetch_related(None)
|
||||
|
@ -1149,13 +1149,11 @@ to ``defer()``::
|
|||
# Load all fields immediately.
|
||||
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
|
||||
:meth:`select_related()` to retrieve related models, you shouldn't defer the
|
||||
loading of the field that connects from the primary model to the related
|
||||
one, doing so will result in an error.
|
||||
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
|
||||
:meth:`select_related()` to retrieve related models, you shouldn't defer the
|
||||
loading of the field that connects from the primary model to the related
|
||||
one, doing so will result in an error.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -1178,8 +1176,6 @@ to ``defer()``::
|
|||
reader, is slightly faster and consumes a little less memory in the Python
|
||||
process.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
.. note::
|
||||
|
||||
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
|
||||
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
|
||||
:meth:`select_related` is an error as well.
|
||||
.. note::
|
||||
|
||||
.. note::
|
||||
|
||||
When calling :meth:`~django.db.models.Model.save()` for instances with
|
||||
deferred fields, only the loaded fields will be saved. See
|
||||
:meth:`~django.db.models.Model.save()` for more details.
|
||||
When calling :meth:`~django.db.models.Model.save()` for instances with
|
||||
deferred fields, only the loaded fields will be saved. See
|
||||
:meth:`~django.db.models.Model.save()` for more details.
|
||||
|
||||
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
|
||||
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
|
||||
~~~~~
|
||||
|
||||
|
@ -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
|
||||
(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.
|
||||
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
|
||||
|
@ -1911,7 +1897,7 @@ deletes this can result in significantly reduced memory usage. The amount of
|
|||
executed queries can be reduced, too.
|
||||
|
||||
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
|
||||
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
|
||||
: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``
|
||||
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``
|
||||
|
@ -196,8 +192,6 @@ All attributes should be considered read-only, unless stated otherwise below.
|
|||
|
||||
.. attribute:: HttpRequest.resolver_match
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
An instance of :class:`~django.core.urlresolvers.ResolverMatch` representing
|
||||
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
|
||||
|
@ -824,8 +818,6 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
|
|||
StreamingHttpResponse objects
|
||||
=============================
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
.. class:: StreamingHttpResponse
|
||||
|
||||
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 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
|
||||
|
||||
FILE_UPLOAD_PERMISSIONS
|
||||
|
@ -1529,6 +1542,8 @@ unpredictable value.
|
|||
:djadmin:`django-admin.py startproject <startproject>` automatically adds a
|
||||
randomly-generated ``SECRET_KEY`` to each new project.
|
||||
|
||||
Django will refuse to start if :setting:`SECRET_KEY` is not set.
|
||||
|
||||
.. warning::
|
||||
|
||||
**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
|
||||
execution vulnerabilities.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
Django will now refuse to start if :setting:`SECRET_KEY` is not set.
|
||||
|
||||
.. setting:: 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`
|
||||
decorator, for example.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
This setting now also accepts view function names and
|
||||
: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.
|
||||
This setting also accepts view function names and :ref:`named URL patterns
|
||||
<naming-url-patterns>` which can be used to reduce configuration duplication
|
||||
since you don't have to define the URL in two places (``settings`` and URLconf).
|
||||
|
||||
.. setting:: LOGIN_URL
|
||||
|
||||
|
@ -2113,13 +2120,9 @@ Default: ``'/accounts/login/'``
|
|||
The URL where requests are redirected for login, especially when using the
|
||||
:func:`~django.contrib.auth.decorators.login_required` decorator.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
This setting now also accepts view function names and
|
||||
: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.
|
||||
This setting also accepts view function names and :ref:`named URL patterns
|
||||
<naming-url-patterns>` which can be used to reduce configuration duplication
|
||||
since you don't have to define the URL in two places (``settings`` and URLconf).
|
||||
|
||||
.. 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.
|
||||
|
||||
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.
|
||||
|
||||
.. setting:: MESSAGE_TAGS
|
||||
|
|
|
@ -118,8 +118,6 @@ Arguments sent with this signal:
|
|||
``using``
|
||||
The database alias being used.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
``update_fields``
|
||||
The set of fields to update explicitly specified in the ``save()`` method.
|
||||
``None`` if this argument was not used in the ``save()`` call.
|
||||
|
@ -153,8 +151,6 @@ Arguments sent with this signal:
|
|||
``using``
|
||||
The database alias being used.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
``update_fields``
|
||||
The set of fields to update explicitly specified in the ``save()`` method.
|
||||
``None`` if this argument was not used in the ``save()`` call.
|
||||
|
@ -522,14 +518,7 @@ request_finished
|
|||
.. data:: django.core.signals.request_finished
|
||||
:module:
|
||||
|
||||
Sent when Django finishes processing an HTTP request.
|
||||
|
||||
.. 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.
|
||||
Sent when Django finishes delvering an HTTP response to the client.
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
@ -75,16 +75,10 @@ Methods
|
|||
The HTTP Status code for the response.
|
||||
|
||||
``content_type``
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
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,
|
||||
:setting:`DEFAULT_CONTENT_TYPE` is used.
|
||||
|
||||
The value included in the HTTP ``Content-Type`` header, including the
|
||||
MIME type specification and the character set encoding. If
|
||||
``content_type`` is specified, then its value is used. Otherwise,
|
||||
:setting:`DEFAULT_CONTENT_TYPE` is used.
|
||||
|
||||
.. method:: SimpleTemplateResponse.resolve_context(context)
|
||||
|
||||
|
@ -167,13 +161,8 @@ Methods
|
|||
The HTTP Status code for the response.
|
||||
|
||||
``content_type``
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
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
|
||||
The value included in the HTTP ``Content-Type`` header, including the
|
||||
MIME type specification and the character set encoding. If
|
||||
``content_type`` is specified, then its value is used. Otherwise,
|
||||
:setting:`DEFAULT_CONTENT_TYPE` is used.
|
||||
|
||||
|
|
|
@ -274,11 +274,6 @@ Builtin variables
|
|||
Every context contains ``True``, ``False`` and ``None``. As you would expect,
|
||||
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
|
||||
----------------------------
|
||||
|
||||
|
|
|
@ -1059,22 +1059,14 @@ by the context as to the current application.
|
|||
|
||||
.. warning::
|
||||
|
||||
Don't forget to put quotes around the function path or pattern name!
|
||||
|
||||
.. 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.
|
||||
Don't forget to put quotes around the function path or pattern name,
|
||||
otherwise the value will be interpreted as a context variable!
|
||||
|
||||
.. templatetag:: verbatim
|
||||
|
||||
verbatim
|
||||
^^^^^^^^
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
(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
|
||||
|
||||
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
|
||||
slightly different call:
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% 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
|
||||
"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
|
||||
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::
|
||||
|
||||
Python 2 legacy::
|
||||
my_string = "This is a bytestring"
|
||||
my_unicode = u"This is an Unicode string"
|
||||
|
||||
my_string = "This is a bytestring"
|
||||
my_unicode = u"This is an Unicode string"
|
||||
Python 2 with unicode literals or Python 3::
|
||||
|
||||
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"
|
||||
my_unicode = "This is an Unicode string"
|
||||
|
||||
See also :doc:`Python 3 compatibility </topics/python3>`.
|
||||
See also :doc:`Python 3 compatibility </topics/python3>`.
|
||||
|
||||
.. 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')
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Returns a text object representing ``s`` -- ``unicode`` on Python 2 and
|
||||
``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')
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Similar to ``smart_text``, except that lazy instances are resolved to
|
||||
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')
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Returns a bytestring version of ``s``, encoded as specified in
|
||||
``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')
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Similar to ``smart_bytes``, except that lazy instances are resolved to
|
||||
bytestrings, rather than kept as lazy objects.
|
||||
|
||||
|
@ -745,8 +737,6 @@ appropriate entities.
|
|||
|
||||
.. class:: SafeBytes
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
A ``bytes`` subclass that has been specifically marked as "safe"
|
||||
(requires no further escaping) for HTML output purposes.
|
||||
|
||||
|
@ -758,8 +748,6 @@ appropriate entities.
|
|||
|
||||
.. class:: SafeText
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
A ``str`` (in Python 3) or ``unicode`` (in Python 2) subclass
|
||||
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
|
||||
on entry with :func:`deactivate()` instead.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
.. function:: localtime(value, timezone=None)
|
||||
|
||||
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,
|
||||
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
|
||||
example, the following Host header would be accepted by Django when running on
|
||||
"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
|
||||
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 django.core.exceptions.SuspiciousOperation
|
||||
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`_.
|
||||
|
||||
|
|
|
@ -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
|
||||
========================
|
||||
|
||||
*February 26, 2013*
|
||||
|
||||
Welcome to Django 1.5!
|
||||
|
||||
These release notes cover the `new features`_, as well
|
||||
|
|
|
@ -130,7 +130,7 @@ Minor features
|
|||
context level.
|
||||
|
||||
* 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
|
||||
relies on ``pubdate``).
|
||||
|
||||
|
@ -190,6 +190,30 @@ Minor features
|
|||
``Meta`` option allows you to customize (or disable) creation of the default
|
||||
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
|
||||
=====================================
|
||||
|
||||
|
@ -237,6 +261,11 @@ Miscellaneous
|
|||
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.
|
||||
|
||||
* 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
|
||||
==========================
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ Final releases
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
1.5.2
|
||||
1.5.1
|
||||
1.5
|
||||
|
||||
|
@ -44,6 +45,10 @@ Final releases
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
1.4.6
|
||||
1.4.5
|
||||
1.4.4
|
||||
1.4.3
|
||||
1.4.2
|
||||
1.4.1
|
||||
1.4
|
||||
|
@ -53,6 +58,11 @@ Final releases
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
1.3.7
|
||||
1.3.6
|
||||
1.3.5
|
||||
1.3.4
|
||||
1.3.3
|
||||
1.3.2
|
||||
1.3.1
|
||||
1.3
|
||||
|
|
|
@ -359,8 +359,6 @@ the extra database load.
|
|||
Substituting a custom User model
|
||||
================================
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Some kinds of projects may have authentication requirements for which Django's
|
||||
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
|
||||
|
@ -684,13 +682,13 @@ auth views.
|
|||
* :class:`~django.contrib.auth.forms.AuthenticationForm`
|
||||
|
||||
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`
|
||||
|
||||
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
|
||||
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`
|
||||
|
||||
|
|
|
@ -434,12 +434,10 @@ The login_required decorator
|
|||
|
||||
(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 to freely remap your login view within your URLconf
|
||||
without having to update the setting.
|
||||
The :setting:`settings.LOGIN_URL <LOGIN_URL>` also accepts view function
|
||||
names and :ref:`named URL patterns <naming-url-patterns>`. This allows you
|
||||
to freely remap your login view within your URLconf without having to
|
||||
update the setting.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -528,6 +526,11 @@ The permission_required decorator
|
|||
(HTTP Forbidden) view<http_forbidden_view>` instead of redirecting to the
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -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>
|
||||
{% endif %}
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Permission lookup by "if in".
|
||||
|
||||
It is possible to also look permissions up by ``{% if in %}`` statements.
|
||||
For example:
|
||||
|
||||
|
|
|
@ -198,6 +198,7 @@ A similar class-based view might look like::
|
|||
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.views.generic.base import View
|
||||
|
||||
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
|
||||
objects.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
:class:`~django.views.generic.base.ContextMixin`
|
||||
Every built in view which needs context data, such as for rendering a
|
||||
template (including ``TemplateResponseMixin`` above), should call
|
||||
|
|
|
@ -265,8 +265,8 @@ Methods are copied according to the following rules:
|
|||
|
||||
- Public methods are 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 `True` are never 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.
|
||||
|
||||
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
|
||||
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
|
||||
objects in more than one database. Common setups include master / slave and
|
||||
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
|
||||
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
``.bitand()`` and ``.bitor()``
|
||||
|
||||
The ``F()`` objects now support bitwise operations by ``.bitand()`` and
|
||||
The ``F()`` objects support bitwise operations by ``.bitand()`` and
|
||||
``.bitor()``, for example::
|
||||
|
||||
>>> 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
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ upload 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`
|
||||
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.**
|
||||
|
||||
: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`
|
||||
The actual handlers for uploaded files. Changing this setting allows
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
``response`` may also be an :class:`~django.http.StreamingHttpResponse`
|
||||
object.
|
||||
|
||||
Unlike :class:`~django.http.HttpResponse`,
|
||||
:class:`~django.http.StreamingHttpResponse` does not have a ``content``
|
||||
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
|
||||
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
|
||||
the cache:
|
||||
|
||||
|
@ -302,8 +298,6 @@ You can edit it multiple times.
|
|||
|
||||
.. method:: SessionBase.clear_expired
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
Removes expired sessions from the session store. This class method is
|
||||
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
|
||||
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:
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ introduce controlled coupling for convenience's sake.
|
|||
:class:`~django.http.HttpResponse` object with that rendered text.
|
||||
|
||||
: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`.
|
||||
|
||||
Required arguments
|
||||
|
@ -50,10 +50,6 @@ Optional arguments
|
|||
The MIME type to use for the resulting document. Defaults to the value of
|
||||
the :setting:`DEFAULT_CONTENT_TYPE` setting.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
This parameter used to be called ``mimetype``.
|
||||
|
||||
``status``
|
||||
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 :setting:`DEFAULT_CONTENT_TYPE` setting.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
This parameter used to be called ``mimetype``.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
|
@ -169,7 +160,8 @@ This example is equivalent to::
|
|||
|
||||
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
|
||||
<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