Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote-tracking branch 'core/master' into schema-alteration

Conflicts:
	docs/ref/django-admin.txt
  • Loading branch information...
commit b6a957f0ba8a2ed1b24d7ee042a9c4beaf51ab03 2 parents 52edc16 + 3c03004
@andrewgodwin andrewgodwin authored
Showing with 1,862 additions and 1,065 deletions.
  1. +5 −0 AUTHORS
  2. +5 −0 django/conf/global_settings.py
  3. +2 −2 django/contrib/admin/static/admin/css/dashboard.css
  4. +15 −11 django/contrib/admin/util.py
  5. +2 −2 django/contrib/admin/widgets.py
  6. +5 −1 django/contrib/auth/decorators.py
  7. +2 −2 django/contrib/auth/models.py
  8. +57 −1 django/contrib/auth/tests/test_decorators.py
  9. +10 −11 django/contrib/auth/tests/test_forms.py
  10. +6 −5 django/contrib/auth/tests/test_management.py
  11. +25 −1 django/contrib/auth/tests/test_models.py
  12. +6 −2 django/contrib/auth/tests/test_views.py
  13. +8 −0 django/contrib/gis/db/backends/postgis/introspection.py
  14. +2 −2 django/contrib/gis/db/models/sql/query.py
  15. +1 −1  django/contrib/gis/tests/geoapp/models.py
  16. +14 −15 django/contrib/humanize/tests.py
  17. +28 −1 django/core/checks/compatibility/django_1_6_0.py
  18. +10 −1 django/core/files/storage.py
  19. +3 −1 django/db/backends/postgresql_psycopg2/introspection.py
  20. +19 −6 django/db/models/base.py
  21. +2 −3 django/db/models/query.py
  22. +2 −3 django/db/models/sql/compiler.py
  23. +92 −70 django/db/models/sql/query.py
  24. +3 −1 django/forms/forms.py
  25. +8 −7 django/forms/widgets.py
  26. +4 −1 django/template/base.py
  27. +28 −7 django/template/defaulttags.py
  28. +29 −39 django/test/client.py
  29. +5 −10 django/utils/functional.py
  30. +5 −5 django/utils/http.py
  31. +3 −3 django/views/debug.py
  32. +2 −2 docs/howto/custom-model-fields.txt
  33. +2 −1  docs/howto/deployment/index.txt
  34. +1 −1  docs/howto/deployment/wsgi/apache-auth.txt
  35. +1 −1  docs/howto/deployment/wsgi/modwsgi.txt
  36. +1 −1  docs/internals/contributing/writing-code/working-with-git.txt
  37. +5 −8 docs/internals/deprecation.txt
  38. +5 −4 docs/internals/howto-release-django.txt
  39. +5 −1 docs/internals/security.txt
  40. +1 −1  docs/intro/tutorial01.txt
  41. +0 −9 docs/intro/tutorial02.txt
  42. +0 −6 docs/ref/class-based-views/base.txt
  43. +0 −8 docs/ref/class-based-views/generic-date-based.txt
  44. +0 −5 docs/ref/class-based-views/mixins-date-based.txt
  45. +0 −2  docs/ref/class-based-views/mixins-multiple-object.txt
  46. +0 −4 docs/ref/class-based-views/mixins-simple.txt
  47. +2 −2 docs/ref/contrib/admin/actions.txt
  48. +1 −1  docs/ref/contrib/admin/admindocs.txt
  49. +0 −8 docs/ref/contrib/admin/index.txt
  50. +6 −3 docs/ref/contrib/auth.txt
  51. +6 −17 docs/ref/contrib/contenttypes.txt
  52. +0 −4 docs/ref/contrib/gis/geoquerysets.txt
  53. +0 −12 docs/ref/contrib/gis/geos.txt
  54. +7 −4 docs/ref/contrib/messages.txt
  55. +4 −3 docs/ref/contrib/sites.txt
  56. +0 −4 docs/ref/contrib/staticfiles.txt
  57. +0 −4 docs/ref/databases.txt
  58. +0 −10 docs/ref/django-admin.txt
  59. +0 −4 docs/ref/files/file.txt
  60. +0 −5 docs/ref/forms/api.txt
  61. +0 −9 docs/ref/forms/fields.txt
  62. +2 −8 docs/ref/forms/validation.txt
  63. +41 −29 docs/ref/forms/widgets.txt
  64. +0 −11 docs/ref/models/fields.txt
  65. +34 −2 docs/ref/models/instances.txt
  66. +0 −2  docs/ref/models/options.txt
  67. +13 −27 docs/ref/models/querysets.txt
  68. +0 −8 docs/ref/request-response.txt
  69. +24 −19 docs/ref/settings.txt
  70. +1 −12 docs/ref/signals.txt
  71. +6 −17 docs/ref/template-response.txt
  72. +0 −5 docs/ref/templates/api.txt
  73. +10 −12 docs/ref/templates/builtins.txt
  74. +14 −16 docs/ref/unicode.txt
  75. +0 −14 docs/ref/utils.txt
  76. +11 −0 docs/releases/1.3.3.txt
  77. +37 −0 docs/releases/1.3.4.txt
  78. +60 −0 docs/releases/1.3.5.txt
  79. +78 −0 docs/releases/1.3.6.txt
  80. +13 −0 docs/releases/1.3.7.txt
  81. +5 −4 docs/releases/1.4.2.txt
  82. +60 −0 docs/releases/1.4.3.txt
  83. +88 −0 docs/releases/1.4.4.txt
  84. +13 −0 docs/releases/1.4.5.txt
  85. +31 −0 docs/releases/1.4.6.txt
  86. +62 −0 docs/releases/1.5.2.txt
  87. +2 −0  docs/releases/1.5.txt
  88. +30 −1 docs/releases/1.7.txt
  89. +10 −0 docs/releases/index.txt
  90. +2 −4 docs/topics/auth/customizing.txt
  91. +9 −10 docs/topics/auth/default.txt
  92. +1 −0  docs/topics/class-based-views/intro.txt
  93. +0 −2  docs/topics/class-based-views/mixins.txt
  94. +2 −2 docs/topics/db/managers.txt
  95. +0 −5 docs/topics/db/multi-db.txt
  96. +1 −10 docs/topics/db/queries.txt
  97. +6 −1 docs/topics/http/file-uploads.txt
  98. +0 −5 docs/topics/http/middleware.txt
  99. +1 −9 docs/topics/http/sessions.txt
  100. +3 −11 docs/topics/http/shortcuts.txt
  101. +0 −2  docs/topics/i18n/translation.txt
  102. +0 −7 docs/topics/logging.txt
  103. +4 −10 docs/topics/pagination.txt
  104. +4 −7 docs/topics/serialization.txt
  105. +0 −4 docs/topics/signals.txt
  106. +0 −2  docs/topics/testing/advanced.txt
  107. +0 −54 docs/topics/testing/overview.txt
  108. +9 −1 tests/admin_views/admin.py
  109. +10 −4 tests/admin_views/models.py
  110. +29 −15 tests/admin_views/tests.py
  111. +38 −34 tests/admin_widgets/tests.py
  112. +25 −0 tests/aggregation/tests.py
  113. +1 −1  tests/aggregation_regress/models.py
  114. +4 −0 tests/basic/tests.py
  115. +9 −1 tests/check/models.py
  116. +20 −0 tests/check/tests.py
  117. +1 −1  tests/comment_tests/models.py
  118. +2 −2 tests/custom_managers/models.py
  119. +18 −19 tests/defaultfilters/tests.py
  120. +6 −0 tests/defer/tests.py
  121. +12 −0 tests/file_storage/tests.py
  122. +18 −1 tests/foreign_object/tests.py
  123. +8 −10 tests/forms_tests/tests/test_fields.py
  124. +16 −0 tests/forms_tests/tests/test_widgets.py
  125. +1 −1  tests/generic_relations/models.py
  126. +15 −20 tests/i18n/tests.py
  127. +1 −1  tests/inspectdb/models.py
  128. +9 −10 tests/logging_tests/tests.py
  129. +2 −2 tests/model_fields/models.py
  130. +18 −5 tests/model_fields/tests.py
  131. +3 −3 tests/model_formsets/models.py
  132. +4 −4 tests/model_inheritance/models.py
  133. +65 −26 tests/model_inheritance/tests.py
  134. +17 −4 tests/model_inheritance_regress/models.py
  135. +15 −1 tests/model_inheritance_regress/tests.py
  136. +2 −2 tests/model_inheritance_select_related/models.py
  137. +1 −1  tests/modeladmin/models.py
  138. +2 −2 tests/one_to_one/models.py
  139. +2 −2 tests/one_to_one_regress/models.py
  140. +14 −21 tests/one_to_one_regress/tests.py
  141. +6 −0 tests/proxy_models/admin.py
  142. +19 −1 tests/proxy_models/fixtures/myhorses.json
  143. +5 −1 tests/proxy_models/models.py
  144. +30 −0 tests/proxy_models/tests.py
  145. +7 −0 tests/proxy_models/urls.py
  146. +24 −2 tests/queries/tests.py
  147. +1 −1  tests/raw_query/models.py
  148. +1 −1  tests/reverse_single_related/models.py
  149. +1 −0  tests/runtests.py
  150. +2 −2 tests/serializers_regress/models.py
  151. 0  tests/servers/another_app/__init__.py
  152. 0  tests/servers/another_app/models.py
  153. +1 −0  tests/servers/another_app/static/another_app/another_app_static_file.txt
  154. +8 −0 tests/servers/tests.py
  155. +10 −1 tests/template_tests/test_context.py
  156. +7 −0 tests/template_tests/tests.py
  157. +8 −0 tests/test_client_regress/tests.py
  158. +1 −0  tests/test_client_regress/urls.py
  159. +12 −1 tests/test_client_regress/views.py
  160. +32 −39 tests/test_utils/tests.py
  161. +82 −97 tests/transactions/tests.py
  162. +13 −0 tests/utils_tests/test_simplelazyobject.py
  163. +16 −0 tests/view_tests/tests/test_debug.py
  164. +31 −33 tests/view_tests/tests/test_i18n.py
  165. +6 −2 tests/view_tests/tests/test_static.py
  166. +1 −0  tests/view_tests/urls.py
  167. +8 −0 tests/view_tests/views.py
View
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>
View
5 django/conf/global_settings.py
@@ -313,6 +313,11 @@
# 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
View
4 django/contrib/admin/static/admin/css/dashboard.css
@@ -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;
-}
+}
View
26 django/contrib/admin/util.py
@@ -16,7 +16,7 @@
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 format_callback(obj):
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 format_callback(obj):
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 @@ def collect(self, objs, source_attr=None, **kwargs):
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)
View
4 django/contrib/admin/widgets.py
@@ -305,9 +305,9 @@ def render(self, name, value, attrs=None):
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
)
View
6 django/contrib/auth/decorators.py
@@ -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:
View
4 django/contrib/auth/models.py
@@ -400,11 +400,11 @@ def get_short_name(self):
"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):
View
58 django/contrib/auth/tests/test_decorators.py
@@ -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 @@ def testLoginRequiredNextUrl(self):
"""
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)
View
21 django/contrib/auth/tests/test_forms.py
@@ -121,17 +121,16 @@ def test_inactive_user(self):
[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.
View
11 django/contrib/auth/tests/test_management.py
@@ -239,21 +239,22 @@ def test_duplicated_permissions(self):
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)
View
26 django/contrib/auth/tests/test_models.py
@@ -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 @@ def test_empty_username(self):
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
View
8 django/contrib/auth/tests/test_views.py
@@ -446,7 +446,8 @@ def test_security_check(self, password='password'):
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 @@ def test_security_check(self, password='password'):
'/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 @@ def test_security_check(self, password='password'):
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 @@ def test_security_check(self, password='password'):
'/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' % {
View
8 django/contrib/gis/db/backends/postgis/introspection.py
@@ -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
View
4 django/contrib/gis/db/models/sql/query.py
@@ -76,7 +76,7 @@ def convert_values(self, value, field, connection):
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 @@ def get_aggregation(self, using):
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):
"""
View
2  django/contrib/gis/tests/geoapp/models.py
@@ -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:
View
29 django/contrib/humanize/tests.py
@@ -77,15 +77,14 @@ def test_l10n_intcomma(self):
'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 @@ def test_i18n_intcomma(self):
'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 @@ def test_naturalday_uses_localtime(self):
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
View
29 django/core/checks/compatibility/django_1_6_0.py
@@ -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]
View
11 django/core/files/storage.py
@@ -172,7 +172,16 @@ def _save(self, name, content):
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
View
4 django/db/backends/postgresql_psycopg2/introspection.py
@@ -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 @@ def get_table_list(self, cursor):
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."
View
25 django/db/models/base.py
@@ -184,10 +184,21 @@ def __new__(cls, name, bases, attrs):
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 @@ def __new__(cls, name, bases, attrs):
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 @@ def __str__(self):
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)
View
5 django/db/models/query.py
@@ -313,14 +313,13 @@ def aggregate(self, *args, **kwargs):
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):
"""
View
5 django/db/models/sql/compiler.py
@@ -167,7 +167,6 @@ def as_nested_sql(self):
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 @@ def execute_sql(self, result_type=MULTI):
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 @@ def pre_sql_setup(self):
# 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])
View
162 django/db/models/sql/query.py
@@ -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 @@ def clone(self, klass=None, memo=None, **kwargs):
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 @@ def resolve_aggregate(self, value, aggregate, connection):
# 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 @@ def get_aggregation(self, using):
# 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 @@ def relabel_column(col):
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 @@ def add_aggregate(self, aggregate, model, alias, is_summary):
# 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 @@ def build_filter(self, filter_expr, branch_negated=False, current_negated=False,
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 @@ def build_filter(self, filter_expr, branch_negated=False, current_negated=False,
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 @@ def split_exclude(self, filter_expr, prefix, can_reuse, names_with_path):
# 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.
View
4 django/forms/forms.py
@@ -434,7 +434,9 @@ def __iter__(self):
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):
View
15 django/forms/widgets.py
@@ -601,16 +601,15 @@ def __init__(self, name, value, attrs, choice, index):
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 @@ def is_checked(self):
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'
View
5 django/template/base.py
@@ -6,7 +6,7 @@
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 @@ def _resolve_lookup(self, context):
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
View
35 django/template/defaulttags.py
@@ -458,10 +458,11 @@ def render(self, context):
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 @@ def render(self, context):
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):
View
68 django/test/client.py
@@ -37,6 +37,7 @@
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 @@ def __call__(self, environ):
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 @@ def _get_path(self, parsed):
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 @@ def post(self, path, data={}, content_type=MULTIPART_CONTENT,
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 @@ def put(self, path, data='', content_type='application/octet-stream',
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 @@ def generic(self, method, path,
'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 @@ def _session(self):
return {}
session = property(_session)
-
def request(self, **request):
"""
The master request method. Composes the environment dictionary
@@ -406,7 +397,8 @@ def request(self, **request):
# 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 @@ def request(self, **request):
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 @@ def head(self, path, data={}, follow=False, **extra):
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 @@ def put(self, path, data='', content_type='application/octet-stream',
"""
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 @@ def patch(self, path, data='', content_type='application/octet-stream',
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
View
15 django/utils/functional.py
@@ -263,17 +263,12 @@ def _setup(self):
__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
View
10 django/utils/http.py
@@ -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'])
View
6 django/views/debug.py
@@ -227,7 +227,7 @@ def format_path_status(self, path):
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 @@ def get_traceback_data(self):
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):
View
4 docs/howto/custom-model-fields.txt
@@ -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
View
3  docs/howto/deployment/index.txt
@@ -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/
View
2  docs/howto/deployment/wsgi/apache-auth.txt
@@ -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
View
2  docs/howto/deployment/wsgi/modwsgi.txt
@@ -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
View
2  docs/internals/contributing/writing-code/working-with-git.txt
@@ -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.
View
13 docs/internals/deprecation.txt
@@ -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).
View
9 docs/internals/howto-release-django.txt
@@ -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.
View
6 docs/internals/security.txt
@@ -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.
View
2  docs/intro/tutorial01.txt
@@ -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
View
9 docs/intro/tutorial02.txt
@@ -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
View
6 docs/ref/class-based-views/base.txt
@@ -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:
View
8 docs/ref/class-based-views/generic-date-based.txt
@@ -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``.
View
5 docs/ref/class-based-views/mixins-date-based.txt
@@ -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.
View
2  docs/ref/class-based-views/mixins-multiple-object.txt
@@ -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
View
4 docs/ref/class-based-views/mixins-simple.txt
@@ -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`.
View
4 docs/ref/contrib/admin/actions.txt
@@ -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()`::
View
2  docs/ref/contrib/admin/admindocs.txt
@@ -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`.
View
8 docs/ref/contrib/admin/index.txt
@@ -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
View
9 docs/ref/contrib/auth.txt
@@ -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)