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:
	django/db/backends/__init__.py
	django/db/models/fields/related.py
	tests/field_deconstruction/tests.py
  • Loading branch information...
commit 7a47ba6f6aeca36b8b092dbafd36abb342d34d4b 2 parents e2d7e83 + 48dd1e6
@andrewgodwin andrewgodwin authored
Showing with 1,166 additions and 419 deletions.
  1. +6 −1 AUTHORS
  2. +1 −1  django/__init__.py
  3. +1 −1  django/contrib/admin/actions.py
  4. +67 −56 django/contrib/admin/options.py
  5. +2 −2 django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
  6. +9 −0 django/contrib/admin/templates/admin/popup_response.html
  7. +1 −1  django/contrib/admin/templates/admin/search_form.html
  8. +1 −1  django/contrib/admin/templates/registration/password_reset_email.html
  9. +3 −3 django/contrib/admin/templatetags/admin_list.py
  10. +3 −3 django/contrib/admin/templatetags/admin_urls.py
  11. +1 −2  django/contrib/admin/validation.py
  12. +25 −3 django/contrib/admin/views/main.py
  13. +0 −1  django/contrib/admindocs/views.py
  14. +14 −1 django/contrib/auth/__init__.py
  15. +3 −2 django/contrib/auth/admin.py
  16. +3 −2 django/contrib/auth/forms.py
  17. +6 −7 django/contrib/auth/management/__init__.py
  18. +15 −12 django/contrib/auth/models.py
  19. +1 −1  django/contrib/auth/tests/templates/registration/password_reset_email.html
  20. +21 −1 django/contrib/auth/tests/test_custom_user.py
  21. +25 −0 django/contrib/auth/tests/test_models.py
  22. +1 −1  django/contrib/auth/tests/test_remote_user.py
  23. +23 −4 django/contrib/auth/tests/test_views.py
  24. +2 −3 django/contrib/auth/tests/urls.py
  25. +3 −0  django/contrib/auth/urls.py
  26. +15 −6 django/contrib/auth/views.py
  27. +1 −1  django/contrib/comments/admin.py
  28. +0 −1  django/contrib/flatpages/tests/test_csrf.py
  29. +0 −1  django/contrib/flatpages/tests/test_templatetags.py
  30. +0 −4 django/contrib/formtools/tests/tests.py
  31. +0 −2  django/contrib/formtools/tests/wizard/test_cookiestorage.py
  32. +0 −1  django/contrib/gis/admin/widgets.py
  33. +0 −1  django/contrib/gis/db/backends/postgis/creation.py
  34. +0 −1  django/contrib/gis/db/models/aggregates.py
  35. +0 −1  django/contrib/gis/geos/tests/test_io.py
  36. +0 −1  django/contrib/gis/management/commands/ogrinspect.py
  37. +0 −2  django/contrib/gis/tests/relatedapp/tests.py
  38. +0 −1  django/contrib/sitemaps/tests/base.py
  39. +0 −1  django/contrib/sitemaps/tests/urls/http.py
  40. 0  {tests/compat_checks → django/core/checks}/__init__.py
  41. 0  django/core/{compat_checks → checks/compatibility}/__init__.py
  42. +1 −1  django/core/{compat_checks → checks/compatibility}/base.py
  43. +1 −1  django/core/{compat_checks → checks/compatibility}/django_1_6_0.py
  44. +0 −1  django/core/exceptions.py
  45. +4 −0 django/core/files/move.py
  46. +1 −0  django/core/handlers/wsgi.py
  47. +0 −2  django/core/management/__init__.py
  48. +1 −1  django/core/management/commands/{checksetup.py → check.py}
  49. +3 −3 django/core/management/commands/makemessages.py
  50. +1 −1  django/core/management/templates.py
  51. +0 −1  django/core/serializers/base.py
  52. +14 −0 django/db/backends/__init__.py
  53. +42 −24 django/db/backends/oracle/base.py
  54. +0 −2  django/db/backends/postgresql_psycopg2/base.py
  55. +0 −2  django/db/backends/postgresql_psycopg2/creation.py
  56. +1 −1  django/db/backends/postgresql_psycopg2/operations.py
  57. +6 −1 django/db/backends/sqlite3/base.py
  58. +14 −8 django/db/models/fields/related.py
  59. +24 −0 django/db/models/options.py
  60. +4 −1 django/db/models/query.py
  61. +20 −0 django/db/transaction.py
  62. +2 −8 django/forms/fields.py
  63. +9 −8 django/forms/formsets.py
  64. +0 −1  django/forms/util.py
  65. +2 −3 django/forms/widgets.py
  66. +4 −0 django/http/request.py
  67. +0 −2  django/middleware/csrf.py
  68. +1 −0  django/template/base.py
  69. +0 −1  django/template/defaultfilters.py
  70. +3 −2 django/template/defaulttags.py
  71. +15 −10 django/test/utils.py
  72. +0 −1  django/utils/html.py
  73. +20 −1 django/utils/http.py
  74. +0 −1  django/utils/text.py
  75. +0 −1  django/views/debug.py
  76. +0 −2  django/views/decorators/csrf.py
  77. +15 −4 django/views/generic/edit.py
  78. +2 −2 docs/conf.py
  79. +10 −17 docs/faq/install.txt
  80. +1 −1  docs/howto/custom-model-fields.txt
  81. +3 −2 docs/howto/deployment/wsgi/modwsgi.txt
  82. +1 −1  docs/howto/static-files/index.txt
  83. +5 −1 docs/internals/contributing/triaging-tickets.txt
  84. +8 −0 docs/internals/deprecation.txt
  85. +1 −1  docs/intro/reusable-apps.txt
  86. +3 −3 docs/intro/tutorial03.txt
  87. +5 −0 docs/ref/class-based-views/flattened-index.txt
  88. +13 −0 docs/ref/class-based-views/mixins-editing.txt
  89. +6 −1 docs/ref/contrib/admin/index.txt
  90. +1 −1  docs/ref/contrib/sitemaps.txt
  91. +8 −0 docs/ref/databases.txt
  92. +22 −0 docs/ref/models/fields.txt
  93. +5 −2 docs/ref/models/instances.txt
  94. +11 −3 docs/ref/settings.txt
  95. +4 −4 docs/ref/signals.txt
  96. +14 −0 docs/ref/utils.txt
  97. +82 −3 docs/releases/1.6.txt
  98. +16 −6 docs/topics/auth/default.txt
  99. +21 −17 docs/topics/class-based-views/generic-display.txt
  100. +3 −2 docs/topics/class-based-views/generic-editing.txt
  101. +1 −1  docs/topics/class-based-views/intro.txt
  102. +79 −78 docs/topics/class-based-views/mixins.txt
  103. +20 −5 docs/topics/db/sql.txt
  104. +21 −0 docs/topics/db/transactions.txt
  105. +2 −2 docs/topics/http/file-uploads.txt
  106. +2 −2 docs/topics/http/shortcuts.txt
  107. +6 −4 docs/topics/testing/overview.txt
  108. +1 −1  tests/admin_views/models.py
  109. +23 −3 tests/admin_views/tests.py
  110. +78 −3 tests/backends/tests.py
  111. 0  tests/check/__init__.py
  112. 0  tests/{compat_checks → check}/models.py
  113. +11 −11 tests/{compat_checks → check}/tests.py
  114. +1 −1  tests/field_deconstruction/tests.py
  115. +14 −0 tests/files/tests.py
  116. +8 −0 tests/foreign_object/models.py
  117. +20 −1 tests/foreign_object/tests.py
  118. +2 −2 tests/forms_tests/tests/test_fields.py
  119. +2 −1  tests/forms_tests/tests/test_formsets.py
  120. +20 −1 tests/generic_views/test_edit.py
  121. +5 −1 tests/i18n/commands/__init__.py
  122. +19 −0 tests/i18n/commands/extraction.py
  123. +6 −0 tests/i18n/commands/templates/plural.djtpl
  124. +22 −0 tests/lookup/tests.py
  125. +19 −2 tests/model_formsets/tests.py
  126. +19 −2 tests/raw_query/tests.py
  127. +5 −0 tests/servers/tests.py
  128. +26 −1 tests/settings_tests/tests.py
  129. +4 −0 tests/template_tests/tests.py
  130. +24 −2 tests/transactions/tests.py
View
7 AUTHORS
@@ -97,6 +97,7 @@ answer newbie questions, and generally made Django that much better:
Ned Batchelder <http://www.nedbatchelder.com/>
batiste@dosimple.ch
Batman
+ Oliver Beattie <oliver@obeattie.com>
Brian Beck <http://blog.brianbeck.com/>
Shannon -jj Behrens <http://jjinux.blogspot.com/>
Esdras Beleza <linux@esdrasbeleza.com>
@@ -151,6 +152,7 @@ answer newbie questions, and generally made Django that much better:
Antonis Christofides <anthony@itia.ntua.gr>
Michal Chruszcz <troll@pld-linux.org>
Can Burak Çilingir <canburak@cs.bilgi.edu.tr>
+ Andrew Clark <amclark7@gmail.com>
Ian Clelland <clelland@gmail.com>
Travis Cline <travis.cline@gmail.com>
Russell Cloran <russell@rucus.net>
@@ -248,6 +250,7 @@ answer newbie questions, and generally made Django that much better:
martin.glueck@gmail.com
Ben Godfrey <http://aftnn.org>
GomoX <gomo@datafull.com>
+ Gil Gonçalves <lursty@gmail.com>
Guilherme Mesquita Gondim <semente@taurinus.org>
Mario Gonzalez <gonzalemario@gmail.com>
David Gouldin <dgouldin@gmail.com>
@@ -271,6 +274,7 @@ answer newbie questions, and generally made Django that much better:
Brian Harring <ferringb@gmail.com>
Brant Harris
Ronny Haryanto <http://ronny.haryan.to/>
+ Axel Haustant <noirbizarre@gmail.com>
Hawkeye
Kent Hauser <kent@khauser.net>
Joe Heck <http://www.rhonabwy.com/wp/>
@@ -486,7 +490,7 @@ answer newbie questions, and generally made Django that much better:
Brian Ray <http://brianray.chipy.org/>
Lee Reilly <lee@leereilly.net>
Łukasz Rekucki <lrekucki@gmail.com>
- remco@diji.biz
+ Remco Wendt <remco.wendt@gmail.com>
Marc Remolt <m.remolt@webmasters.de>
Bruno Renié <buburno@gmail.com>
David Reynolds <david@reynoldsfamily.org.uk>
@@ -608,6 +612,7 @@ answer newbie questions, and generally made Django that much better:
Filip Wasilewski <filip.wasilewski@gmail.com>
Dan Watson <http://danwatson.net/>
Joel Watts <joel@joelwatts.com>
+ Russ Webber
Lakin Wecker <lakin@structuredabstraction.com>
Chris Wesseling <Chris.Wesseling@cwi.nl>
Benjamin Wohlwend <piquadrat@gmail.com>
View
2  django/__init__.py
@@ -1,4 +1,4 @@
-VERSION = (1, 6, 0, 'alpha', 1)
+VERSION = (1, 7, 0, 'alpha', 0)
def get_version(*args, **kwargs):
# Don't litter django/__init__.py with all the get_version stuff.
View
2  django/contrib/admin/actions.py
@@ -19,7 +19,7 @@ def delete_selected(modeladmin, request, queryset):
deleteable objects, or, if the user has no permission one of the related
childs (foreignkeys), a "permission denied" message.
- Next, it delets all selected objects and redirects back to the change list.
+ Next, it deletes all selected objects and redirects back to the change list.
"""
opts = modeladmin.model._meta
app_label = opts.app_label
View
123 django/contrib/admin/options.py
@@ -4,18 +4,15 @@
from django import forms
from django.conf import settings
-from django.forms.formsets import all_valid, DELETION_FIELD_NAME
-from django.forms.models import (modelform_factory, modelformset_factory,
- inlineformset_factory, BaseInlineFormSet, modelform_defines_fields)
-from django.contrib.contenttypes.models import ContentType
+from django.contrib import messages
from django.contrib.admin import widgets, helpers
from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
model_format_dict, NestedObjects, lookup_needs_distinct)
from django.contrib.admin import validation
from django.contrib.admin.templatetags.admin_static import static
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
-from django.contrib import messages
-from django.views.decorators.csrf import csrf_protect
+from django.contrib.auth import get_permission_codename
+from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied, ValidationError, FieldError
from django.core.paginator import Paginator
from django.core.urlresolvers import reverse
@@ -24,7 +21,10 @@
from django.db.models.related import RelatedObject
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
from django.db.models.sql.constants import QUERY_TERMS
-from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.forms.formsets import all_valid, DELETION_FIELD_NAME
+from django.forms.models import (modelform_factory, modelformset_factory,
+ inlineformset_factory, BaseInlineFormSet, modelform_defines_fields)
+from django.http import Http404, HttpResponseRedirect
from django.http.response import HttpResponseBase
from django.shortcuts import get_object_or_404
from django.template.response import SimpleTemplateResponse, TemplateResponse
@@ -39,6 +39,10 @@
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.utils.encoding import force_text
+from django.views.decorators.csrf import csrf_protect
+
+
+IS_POPUP_VAR = '_popup'
HORIZONTAL, VERTICAL = 1, 2
# returns the <ul> class for a given radio_admin field
@@ -56,15 +60,15 @@ class IncorrectLookupParameters(Exception):
'form_class': forms.SplitDateTimeField,
'widget': widgets.AdminSplitDateTime
},
- models.DateField: {'widget': widgets.AdminDateWidget},
- models.TimeField: {'widget': widgets.AdminTimeWidget},
- models.TextField: {'widget': widgets.AdminTextareaWidget},
- models.URLField: {'widget': widgets.AdminURLFieldWidget},
- models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget},
+ models.DateField: {'widget': widgets.AdminDateWidget},
+ models.TimeField: {'widget': widgets.AdminTimeWidget},
+ models.TextField: {'widget': widgets.AdminTextareaWidget},
+ models.URLField: {'widget': widgets.AdminURLFieldWidget},
+ models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget},
models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget},
- models.CharField: {'widget': widgets.AdminTextInputWidget},
- models.ImageField: {'widget': widgets.AdminFileWidget},
- models.FileField: {'widget': widgets.AdminFileWidget},
+ models.CharField: {'widget': widgets.AdminTextInputWidget},
+ models.ImageField: {'widget': widgets.AdminFileWidget},
+ models.FileField: {'widget': widgets.AdminFileWidget},
}
csrf_protect_m = method_decorator(csrf_protect)
@@ -350,7 +354,8 @@ def has_add_permission(self, request):
Can be overridden by the user in subclasses.
"""
opts = self.opts
- return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
+ codename = get_permission_codename('add', opts)
+ return request.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_change_permission(self, request, obj=None):
"""
@@ -364,7 +369,8 @@ def has_change_permission(self, request, obj=None):
request has permission to change *any* object of the given type.
"""
opts = self.opts
- return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
+ codename = get_permission_codename('change', opts)
+ return request.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_delete_permission(self, request, obj=None):
"""
@@ -378,7 +384,9 @@ def has_delete_permission(self, request, obj=None):
request has permission to delete *any* object of the given type.
"""
opts = self.opts
- return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
+ codename = get_permission_codename('delete', opts)
+ return request.user.has_perm("%s.%s" % (opts.app_label, codename))
+
class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model."
@@ -606,11 +614,11 @@ def log_addition(self, request, object):
"""
from django.contrib.admin.models import LogEntry, ADDITION
LogEntry.objects.log_action(
- user_id = request.user.pk,
- content_type_id = ContentType.objects.get_for_model(object).pk,
- object_id = object.pk,
- object_repr = force_text(object),
- action_flag = ADDITION
+ user_id=request.user.pk,
+ content_type_id=ContentType.objects.get_for_model(object).pk,
+ object_id=object.pk,
+ object_repr=force_text(object),
+ action_flag=ADDITION
)
def log_change(self, request, object, message):
@@ -621,12 +629,12 @@ def log_change(self, request, object, message):
"""
from django.contrib.admin.models import LogEntry, CHANGE
LogEntry.objects.log_action(
- user_id = request.user.pk,
- content_type_id = ContentType.objects.get_for_model(object).pk,
- object_id = object.pk,
- object_repr = force_text(object),
- action_flag = CHANGE,
- change_message = message
+ user_id=request.user.pk,
+ content_type_id=ContentType.objects.get_for_model(object).pk,
+ object_id=object.pk,
+ object_repr=force_text(object),
+ action_flag=CHANGE,
+ change_message=message
)
def log_deletion(self, request, object, object_repr):
@@ -638,11 +646,11 @@ def log_deletion(self, request, object, object_repr):
"""
from django.contrib.admin.models import LogEntry, DELETION
LogEntry.objects.log_action(
- user_id = request.user.pk,
- content_type_id = ContentType.objects.get_for_model(self.model).pk,
- object_id = object.pk,
- object_repr = object_repr,
- action_flag = DELETION
+ user_id=request.user.pk,
+ content_type_id=ContentType.objects.get_for_model(self.model).pk,
+ object_id=object.pk,
+ object_repr=object_repr,
+ action_flag=DELETION
)
def action_checkbox(self, obj):
@@ -660,8 +668,8 @@ def get_actions(self, request):
"""
# If self.actions is explicitly set to None that means that we don't
# want *any* actions enabled on this page.
- from django.contrib.admin.views.main import IS_POPUP_VAR
- if self.actions is None or IS_POPUP_VAR in request.GET:
+ from django.contrib.admin.views.main import _is_changelist_popup
+ if self.actions is None or _is_changelist_popup(request):
return SortedDict()
actions = []
@@ -878,7 +886,7 @@ def render_change_form(self, request, context, add=False, change=False, form_url
'has_add_permission': self.has_add_permission(request),
'has_change_permission': self.has_change_permission(request, obj),
'has_delete_permission': self.has_delete_permission(request, obj),
- 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
+ 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
'form_url': form_url,
'opts': opts,
@@ -908,12 +916,11 @@ def response_add(self, request, obj, post_url_continue=None):
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
- if "_popup" in request.POST:
- return HttpResponse(
- '<!DOCTYPE html><html><head><title></title></head><body>'
- '<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \
- # escape() calls force_text.
- (escape(pk_value), escapejs(obj)))
+ if IS_POPUP_VAR in request.POST:
+ return SimpleTemplateResponse('admin/popup_response.html', {
+ 'pk_value': escape(pk_value),
+ 'obj': escapejs(obj)
+ })
elif "_continue" in request.POST:
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
@@ -1049,7 +1056,7 @@ def response_action(self, request, queryset):
if action_form.is_valid():
action = action_form.cleaned_data['action']
select_across = action_form.cleaned_data['select_across']
- func, name, description = self.get_actions(request)[action]
+ func = self.get_actions(request)[action][0]
# Get the list of selected PKs. If nothing's selected, we can't
# perform an action on it, so bail. Except we want to perform
@@ -1158,7 +1165,7 @@ def add_view(self, request, form_url='', extra_context=None):
context = {
'title': _('Add %s') % force_text(opts.verbose_name),
'adminform': adminForm,
- 'is_popup': "_popup" in request.REQUEST,
+ 'is_popup': IS_POPUP_VAR in request.REQUEST,
'media': media,
'inline_admin_formsets': inline_admin_formsets,
'errors': helpers.AdminErrorList(form, formsets),
@@ -1251,7 +1258,7 @@ def change_view(self, request, object_id, form_url='', extra_context=None):
'adminform': adminForm,
'object_id': object_id,
'original': obj,
- 'is_popup': "_popup" in request.REQUEST,
+ 'is_popup': IS_POPUP_VAR in request.REQUEST,
'media': media,
'inline_admin_formsets': inline_admin_formsets,
'errors': helpers.AdminErrorList(form, formsets),
@@ -1280,7 +1287,7 @@ def changelist_view(self, request, extra_context=None):
actions = self.get_actions(request)
if actions:
# Add the action checkboxes if there are any actions available.
- list_display = ['action_checkbox'] + list(list_display)
+ list_display = ['action_checkbox'] + list(list_display)
ChangeList = self.get_changelist(request)
try:
@@ -1429,7 +1436,10 @@ def delete_view(self, request, object_id, extra_context=None):
raise PermissionDenied
if obj is None:
- raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)})
+ raise Http404(
+ _('%(name)s object with primary key %(key)r does not exist.') %
+ {'name': force_text(opts.verbose_name), 'key': escape(object_id)}
+ )
using = router.db_for_write(self.model)
@@ -1438,7 +1448,7 @@ def delete_view(self, request, object_id, extra_context=None):
(deleted_objects, perms_needed, protected) = get_deleted_objects(
[obj], opts, request.user, self.admin_site, using)
- if request.POST: # The user has already confirmed the deletion.
+ if request.POST: # The user has already confirmed the deletion.
if perms_needed:
raise PermissionDenied
obj_display = force_text(obj)
@@ -1456,7 +1466,9 @@ def delete_view(self, request, object_id, extra_context=None):
(opts.app_label, opts.model_name),
current_app=self.admin_site.name)
preserved_filters = self.get_preserved_filters(request)
- post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
+ post_url = add_preserved_filters(
+ {'preserved_filters': preserved_filters, 'opts': opts}, post_url
+ )
else:
post_url = reverse('admin:index',
current_app=self.admin_site.name)
@@ -1522,6 +1534,7 @@ def history_view(self, request, object_id, extra_context=None):
"admin/object_history.html"
], context, current_app=self.admin_site.name)
+
class InlineModelAdmin(BaseModelAdmin):
"""
Options for inline editing of ``model`` instances.
@@ -1665,8 +1678,7 @@ def has_add_permission(self, request):
# to have the change permission for the related model in order to
# be able to do anything with the intermediate model.
return self.has_change_permission(request)
- return request.user.has_perm(
- self.opts.app_label + '.' + self.opts.get_add_permission())
+ return super(InlineModelAdmin, self).has_add_permission(request)
def has_change_permission(self, request, obj=None):
opts = self.opts
@@ -1677,8 +1689,8 @@ def has_change_permission(self, request, obj=None):
if field.rel and field.rel.to != self.parent_model:
opts = field.rel.to._meta
break
- return request.user.has_perm(
- opts.app_label + '.' + opts.get_change_permission())
+ codename = get_permission_codename('change', opts)
+ return request.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_delete_permission(self, request, obj=None):
if self.opts.auto_created:
@@ -1687,8 +1699,7 @@ def has_delete_permission(self, request, obj=None):
# to have the change permission for the related model in order to
# be able to do anything with the intermediate model.
return self.has_change_permission(request, obj)
- return request.user.has_perm(
- self.opts.app_label + '.' + self.opts.get_delete_permission())
+ return super(InlineModelAdmin, self).has_delete_permission(request, obj)
class StackedInline(InlineModelAdmin):
View
4 django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
@@ -32,9 +32,9 @@ function showRelatedObjectLookupPopup(triggeringLink) {
name = id_to_windowname(name);
var href;
if (triggeringLink.href.search(/\?/) >= 0) {
- href = triggeringLink.href + '&pop=1';
+ href = triggeringLink.href + '&_popup=1';
} else {
- href = triggeringLink.href + '?pop=1';
+ href = triggeringLink.href + '?_popup=1';
}
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus();
View
9 django/contrib/admin/templates/admin/popup_response.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head><title></title></head>
+ <body>
+ <script type="text/javascript">
+ opener.dismissAddAnotherPopup(window, "{{ pk_value }}", "{{ obj }}");
+ </script>
+ </body>
+</html>
View
2  django/contrib/admin/templates/admin/search_form.html
@@ -6,7 +6,7 @@
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" />
<input type="submit" value="{% trans 'Search' %}" />
{% if show_result_count %}
- <span class="small quiet">{% blocktrans count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}pop=1{% endif %}">{% blocktrans with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
+ <span class="small quiet">{% blocktrans count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}_popup=1{% endif %}">{% blocktrans with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
{% endif %}
{% for pair in cl.params.items %}
{% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}"/>{% endifnotequal %}
View
2  django/contrib/admin/templates/registration/password_reset_email.html
@@ -3,7 +3,7 @@
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
-{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb36=uid token=token %}
+{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
View
6 django/contrib/admin/templatetags/admin_list.py
@@ -11,7 +11,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils import formats
-from django.utils.html import format_html
+from django.utils.html import escapejs, format_html
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
@@ -226,12 +226,12 @@ def items_for_result(cl, result, form):
else:
attr = pk
value = result.serializable_value(attr)
- result_id = repr(force_text(value))[1:]
+ result_id = escapejs(value)
yield format_html('<{0}{1}><a href="{2}"{3}>{4}</a></{5}>',
table_tag,
row_class,
url,
- format_html(' onclick="opener.dismissRelatedLookupPopup(window, {0}); return false;"', result_id)
+ format_html(' onclick="opener.dismissRelatedLookupPopup(window, &#39;{0}&#39;); return false;"', result_id)
if cl.is_popup else '',
result_repr,
table_tag)
View
6 django/contrib/admin/templatetags/admin_urls.py
@@ -1,5 +1,3 @@
-from django.utils.http import urlencode
-
try:
from urllib.parse import parse_qsl, urlparse, urlunparse
except ImportError:
@@ -8,6 +6,7 @@
from django import template
from django.contrib.admin.util import quote
from django.core.urlresolvers import resolve, Resolver404
+from django.utils.http import urlencode
register = template.Library()
@@ -47,7 +46,8 @@ def add_preserved_filters(context, url, popup=False):
merged_qs.update(preserved_filters)
if popup:
- merged_qs['_popup'] = 1
+ from django.contrib.admin.options import IS_POPUP_VAR
+ merged_qs[IS_POPUP_VAR] = 1
merged_qs.update(parsed_qs)
View
3  django/contrib/admin/validation.py
@@ -1,8 +1,7 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models.fields import FieldDoesNotExist
-from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
- _get_foreign_key)
+from django.forms.models import BaseModelForm, BaseModelFormSet, _get_foreign_key
from django.contrib.admin.util import get_fields_from_path, NotRelationField
"""
View
28 django/contrib/admin/views/main.py
@@ -15,7 +15,7 @@
from django.contrib.admin import FieldListFilter
from django.contrib.admin.exceptions import DisallowedModelAdminLookup
-from django.contrib.admin.options import IncorrectLookupParameters
+from django.contrib.admin.options import IncorrectLookupParameters, IS_POPUP_VAR
from django.contrib.admin.util import (quote, get_fields_from_path,
lookup_needs_distinct, prepare_lookup_value)
@@ -26,7 +26,6 @@
PAGE_VAR = 'p'
SEARCH_VAR = 'q'
TO_FIELD_VAR = 't'
-IS_POPUP_VAR = 'pop'
ERROR_FLAG = 'e'
IGNORED_PARAMS = (
@@ -36,6 +35,29 @@
EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
+def _is_changelist_popup(request):
+ """
+ Returns True if the popup GET parameter is set.
+
+ This function is introduced to facilitate deprecating the legacy
+ value for IS_POPUP_VAR and should be removed at the end of the
+ deprecation cycle.
+ """
+
+ if IS_POPUP_VAR in request.GET:
+ return True
+
+ IS_LEGACY_POPUP_VAR = 'pop'
+ if IS_LEGACY_POPUP_VAR in request.GET:
+ warnings.warn(
+ "The `%s` GET parameter has been renamed to `%s`." %
+ (IS_LEGACY_POPUP_VAR, IS_POPUP_VAR),
+ PendingDeprecationWarning, 2)
+ return True
+
+ return False
+
+
class RenameChangeListMethods(RenameMethodsBase):
renamed_methods = (
('get_query_set', 'get_queryset', PendingDeprecationWarning),
@@ -67,7 +89,7 @@ def __init__(self, request, model, list_display, list_display_links,
except ValueError:
self.page_num = 0
self.show_all = ALL_VAR in request.GET
- self.is_popup = IS_POPUP_VAR in request.GET
+ self.is_popup = _is_changelist_popup(request)
self.to_field = request.GET.get(TO_FIELD_VAR)
self.params = dict(request.GET.items())
if PAGE_VAR in self.params:
View
1  django/contrib/admindocs/views.py
@@ -17,7 +17,6 @@
from django.utils._os import upath
from django.utils import six
from django.utils.translation import ugettext as _
-from django.utils.safestring import mark_safe
# Exclude methods starting with these strings from documentation
MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
View
15 django/contrib/auth/__init__.py
@@ -108,7 +108,9 @@ def logout(request):
def get_user_model():
- "Return the User model that is active in this project"
+ """
+ Returns the User model that is active in this project.
+ """
from django.db.models import get_model
try:
@@ -122,6 +124,10 @@ def get_user_model():
def get_user(request):
+ """
+ Returns the user model instance associated with the given request session.
+ If no user is retrieved an instance of `AnonymousUser` is returned.
+ """
from .models import AnonymousUser
try:
user_id = request.session[SESSION_KEY]
@@ -132,3 +138,10 @@ def get_user(request):
except (KeyError, AssertionError):
user = AnonymousUser()
return user
+
+
+def get_permission_codename(action, opts):
+ """
+ Returns the codename of the permission for the specified action.
+ """
+ return '%s_%s' % (action, opts.model_name)
View
5 django/contrib/auth/admin.py
@@ -1,6 +1,7 @@
from django.db import transaction
from django.conf import settings
from django.contrib import admin
+from django.contrib.admin.options import IS_POPUP_VAR
from django.contrib.auth.forms import (UserCreationForm, UserChangeForm,
AdminPasswordChangeForm)
from django.contrib.auth.models import User, Group
@@ -143,7 +144,7 @@ def user_change_password(self, request, id, form_url=''):
'adminForm': adminForm,
'form_url': form_url,
'form': form,
- 'is_popup': '_popup' in request.REQUEST,
+ 'is_popup': IS_POPUP_VAR in request.REQUEST,
'add': True,
'change': False,
'has_delete_permission': False,
@@ -170,7 +171,7 @@ def response_add(self, request, obj, post_url_continue=None):
# button except in two scenarios:
# * The user has pressed the 'Save and add another' button
# * We are adding a user in a popup
- if '_addanother' not in request.POST and '_popup' not in request.POST:
+ if '_addanother' not in request.POST and IS_POPUP_VAR not in request.POST:
request.POST['_continue'] = 1
return super(UserAdmin, self).response_add(request, obj,
post_url_continue)
View
5 django/contrib/auth/forms.py
@@ -6,8 +6,9 @@
from django.forms.util import flatatt
from django.template import loader
from django.utils.datastructures import SortedDict
+from django.utils.encoding import force_bytes
from django.utils.html import format_html, format_html_join
-from django.utils.http import int_to_base36
+from django.utils.http import urlsafe_base64_encode
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext, ugettext_lazy as _
@@ -243,7 +244,7 @@ def save(self, domain_override=None,
'email': user.email,
'domain': domain,
'site_name': site_name,
- 'uid': int_to_base36(user.pk),
+ 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'user': user,
'token': token_generator.make_token(user),
'protocol': 'https' if use_https else 'http',
View
13 django/contrib/auth/management/__init__.py
@@ -4,10 +4,10 @@
from __future__ import unicode_literals
import getpass
-import locale
import unicodedata
-from django.contrib.auth import models as auth_app, get_user_model
+from django.contrib.auth import (models as auth_app, get_permission_codename,
+ get_user_model)
from django.core import exceptions
from django.core.management.base import CommandError
from django.db import DEFAULT_DB_ALIAS, router
@@ -17,10 +17,6 @@
from django.utils.six.moves import input
-def _get_permission_codename(action, opts):
- return '%s_%s' % (action, opts.model_name)
-
-
def _get_all_permissions(opts, ctype):
"""
Returns (codename, name) for all permissions in the given opts.
@@ -30,16 +26,18 @@ def _get_all_permissions(opts, ctype):
_check_permission_clashing(custom, builtin, ctype)
return builtin + custom
+
def _get_builtin_permissions(opts):
"""
Returns (codename, name) for all autogenerated permissions.
"""
perms = []
for action in ('add', 'change', 'delete'):
- perms.append((_get_permission_codename(action, opts),
+ perms.append((get_permission_codename(action, opts),
'Can %s %s' % (action, opts.verbose_name_raw)))
return perms
+
def _check_permission_clashing(custom, builtin, ctype):
"""
Check that permissions for a model do not clash. Raises CommandError if
@@ -59,6 +57,7 @@ def _check_permission_clashing(custom, builtin, ctype):
(codename, ctype.app_label, ctype.model_class().__name__))
pool.add(codename)
+
def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs):
try:
get_model('auth', 'Permission')
View
27 django/contrib/auth/models.py
@@ -170,7 +170,8 @@ def get_by_natural_key(self, username):
class UserManager(BaseUserManager):
- def create_user(self, username, email=None, password=None, **extra_fields):
+ def _create_user(self, username, email, password,
+ is_staff, is_superuser, **extra_fields):
"""
Creates and saves a User with the given username, email and password.
"""
@@ -179,20 +180,20 @@ def create_user(self, username, email=None, password=None, **extra_fields):
raise ValueError('The given username must be set')
email = self.normalize_email(email)
user = self.model(username=username, email=email,
- is_staff=False, is_active=True, is_superuser=False,
- last_login=now, date_joined=now, **extra_fields)
-
+ is_staff=is_staff, is_active=True,
+ is_superuser=is_superuser, last_login=now,
+ date_joined=now, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
+ def create_user(self, username, email=None, password=None, **extra_fields):
+ return self._create_user(username, email, password, False, False,
+ **extra_fields)
+
def create_superuser(self, username, email, password, **extra_fields):
- u = self.create_user(username, email, password, **extra_fields)
- u.is_staff = True
- u.is_active = True
- u.is_superuser = True
- u.save(using=self._db)
- return u
+ return self._create_user(username, email, password, True, True,
+ **extra_fields)
@python_2_unicode_compatible
@@ -294,10 +295,12 @@ class PermissionsMixin(models.Model):
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
- 'his/her group.'))
+ 'his/her group.'),
+ related_name="user_set", related_query_name="user")
user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True,
- help_text='Specific permissions for this user.')
+ help_text='Specific permissions for this user.',
+ related_name="user_set", related_query_name="user")
class Meta:
abstract = True
View
2  django/contrib/auth/tests/templates/registration/password_reset_email.html
@@ -1 +1 @@
-{{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/
+{{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/
View
22 django/contrib/auth/tests/test_custom_user.py
@@ -4,7 +4,9 @@
AbstractBaseUser,
AbstractUser,
UserManager,
- PermissionsMixin
+ PermissionsMixin,
+ Group,
+ Permission,
)
@@ -81,6 +83,20 @@ def is_staff(self):
return self.is_admin
+# At this point, temporarily remove the groups and user_permissions M2M
+# fields from the AbstractUser class, so they don't clash with the related_name
+# that sets.
+
+old_au_local_m2m = AbstractUser._meta.local_many_to_many
+old_pm_local_m2m = PermissionsMixin._meta.local_many_to_many
+groups = models.ManyToManyField(Group, blank=True)
+groups.contribute_to_class(PermissionsMixin, "groups")
+user_permissions = models.ManyToManyField(Permission, blank=True)
+user_permissions.contribute_to_class(PermissionsMixin, "user_permissions")
+PermissionsMixin._meta.local_many_to_many = [groups, user_permissions]
+AbstractUser._meta.local_many_to_many = [groups, user_permissions]
+
+
# The extension user is a simple extension of the built-in user class,
# adding a required date_of_birth field. This allows us to check for
# any hard references to the name "User" in forms/handlers etc.
@@ -178,3 +194,7 @@ class CustomUserBadRequiredFields(AbstractBaseUser):
class Meta:
app_label = 'auth'
+
+# Undo swap hack
+AbstractUser._meta.local_many_to_many = old_au_local_m2m
+PermissionsMixin._meta.local_many_to_many = old_pm_local_m2m
View
25 django/contrib/auth/tests/test_models.py
@@ -5,6 +5,7 @@
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
UserManager)
from django.contrib.auth.tests.utils import skipIfCustomUser
+from django.db.models.signals import post_save
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
@@ -140,3 +141,27 @@ def test_is_active_field_default(self):
user_fetched = UserModel._default_manager.get(pk=user.pk)
# the attribute is always true for newly retrieved instance
self.assertEqual(user_fetched.is_active, True)
+
+
+@skipIfCustomUser
+class TestCreateSuperUserSignals(TestCase):
+ """
+ Simple test case for ticket #20541
+ """
+ def post_save_listener(self, *args, **kwargs):
+ self.signals_count += 1
+
+ def setUp(self):
+ self.signals_count = 0
+ post_save.connect(self.post_save_listener, sender=User)
+
+ def tearDown(self):
+ post_save.disconnect(self.post_save_listener, sender=User)
+
+ def test_create_user(self):
+ User.objects.create_user("JohnDoe")
+ self.assertEqual(self.signals_count, 1)
+
+ def test_create_superuser(self):
+ User.objects.create_superuser("JohnDoe", "mail@example.com", "1")
+ self.assertEqual(self.signals_count, 1)
View
2  django/contrib/auth/tests/test_remote_user.py
@@ -3,7 +3,7 @@
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.backends import RemoteUserBackend
-from django.contrib.auth.models import User, AnonymousUser
+from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.utils import timezone
View
27 django/contrib/auth/tests/test_views.py
@@ -13,8 +13,7 @@
from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import QueryDict, HttpRequest
from django.utils.encoding import force_text
-from django.utils.html import escape
-from django.utils.http import urlquote
+from django.utils.http import int_to_base36, urlsafe_base64_decode, urlquote
from django.utils._os import upath
from django.test import TestCase
from django.test.utils import override_settings, patch_logger
@@ -23,7 +22,7 @@
from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
- SetPasswordForm, PasswordResetForm)
+ SetPasswordForm)
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.views import login as login_view
@@ -92,7 +91,7 @@ def test_named_urls(self):
('password_reset', [], {}),
('password_reset_done', [], {}),
('password_reset_confirm', [], {
- 'uidb36': 'aaaaaaa',
+ 'uidb64': 'aaaaaaa',
'token': '1111-aaaaa',
}),
('password_reset_complete', [], {}),
@@ -194,6 +193,16 @@ def test_confirm_valid(self):
# redirect to a 'complete' page:
self.assertContains(response, "Please enter your new password")
+ def test_confirm_valid_base36(self):
+ # Remove in Django 1.7
+ url, path = self._test_confirm_start()
+ path_parts = path.strip("/").split("/")
+ # construct an old style (base36) URL by converting the base64 ID
+ path_parts[1] = int_to_base36(int(urlsafe_base64_decode(path_parts[1])))
+ response = self.client.get("/%s/%s-%s/" % tuple(path_parts))
+ # redirect to a 'complete' page:
+ self.assertContains(response, "Please enter your new password")
+
def test_confirm_invalid(self):
url, path = self._test_confirm_start()
# Let's munge the token in the path, but keep the same length,
@@ -205,11 +214,21 @@ def test_confirm_invalid(self):
def test_confirm_invalid_user(self):
# Ensure that we get a 200 response for a non-existant user, not a 404
+ response = self.client.get('/reset/123456/1-1/')
+ self.assertContains(response, "The password reset link was invalid")
+
+ def test_confirm_invalid_user_base36(self):
+ # Remove in Django 1.7
response = self.client.get('/reset/123456-1-1/')
self.assertContains(response, "The password reset link was invalid")
def test_confirm_overflow_user(self):
# Ensure that we get a 200 response for a base36 user id that overflows int
+ response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/')
+ self.assertContains(response, "The password reset link was invalid")
+
+ def test_confirm_overflow_user_base36(self):
+ # Remove in Django 1.7
response = self.client.get('/reset/zzzzzzzzzzzzz-1-1/')
self.assertContains(response, "The password reset link was invalid")
View
5 django/contrib/auth/tests/urls.py
@@ -67,10 +67,10 @@ def custom_request_auth_login(request):
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
(r'^password_reset/custom_redirect/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='/custom/')),
(r'^password_reset/custom_redirect/named/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='password_reset')),
- (r'^reset/custom/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
+ (r'^reset/custom/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm',
dict(post_reset_redirect='/custom/')),
- (r'^reset/custom/named/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
+ (r'^reset/custom/named/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm',
dict(post_reset_redirect='password_reset')),
(r'^password_change/custom/$', 'django.contrib.auth.views.password_change', dict(post_change_redirect='/custom/')),
@@ -88,4 +88,3 @@ def custom_request_auth_login(request):
(r'^custom_request_auth_login/$', custom_request_auth_login),
url(r'^userpage/(.+)/$', userpage, name="userpage"),
)
-
View
3  django/contrib/auth/urls.py
@@ -12,7 +12,10 @@
url(r'^password_change/done/$', 'django.contrib.auth.views.password_change_done', name='password_change_done'),
url(r'^password_reset/$', 'django.contrib.auth.views.password_reset', name='password_reset'),
url(r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'),
+ # Support old style base36 password reset links; remove in Django 1.7
url(r'^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
+ 'django.contrib.auth.views.password_reset_confirm_uidb36'),
+ url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm',
name='password_reset_confirm'),
url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'),
View
21 django/contrib/auth/views.py
@@ -7,9 +7,10 @@
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, QueryDict
from django.template.response import TemplateResponse
-from django.utils.http import base36_to_int, is_safe_url
+from django.utils.http import base36_to_int, is_safe_url, urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import ugettext as _
from django.shortcuts import resolve_url
+from django.utils.encoding import force_bytes, force_text
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
@@ -184,7 +185,7 @@ def password_reset_done(request,
# Doesn't need csrf_protect since no-one can guess the URL
@sensitive_post_parameters()
@never_cache
-def password_reset_confirm(request, uidb36=None, token=None,
+def password_reset_confirm(request, uidb64=None, token=None,
template_name='registration/password_reset_confirm.html',
token_generator=default_token_generator,
set_password_form=SetPasswordForm,
@@ -195,15 +196,15 @@ def password_reset_confirm(request, uidb36=None, token=None,
form for entering a new password.
"""
UserModel = get_user_model()
- assert uidb36 is not None and token is not None # checked by URLconf
+ assert uidb64 is not None and token is not None # checked by URLconf
if post_reset_redirect is None:
post_reset_redirect = reverse('password_reset_complete')
else:
post_reset_redirect = resolve_url(post_reset_redirect)
try:
- uid_int = base36_to_int(uidb36)
- user = UserModel._default_manager.get(pk=uid_int)
- except (ValueError, OverflowError, UserModel.DoesNotExist):
+ uid = urlsafe_base64_decode(uidb64)
+ user = UserModel._default_manager.get(pk=uid)
+ except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
user = None
if user is not None and token_generator.check_token(user, token):
@@ -227,6 +228,14 @@ def password_reset_confirm(request, uidb36=None, token=None,
return TemplateResponse(request, template_name, context,
current_app=current_app)
+def password_reset_confirm_uidb36(request, uidb36=None, **kwargs):
+ # Support old password reset URLs that used base36 encoded user IDs.
+ # Remove in Django 1.7
+ try:
+ uidb64 = force_text(urlsafe_base64_encode(force_bytes(base36_to_int(uidb36))))
+ except ValueError:
+ uidb64 = '1' # dummy invalid ID (incorrect padding for base64)
+ return password_reset_confirm(request, uidb64=uidb64, **kwargs)
def password_reset_complete(request,
template_name='registration/password_reset_complete.html',
View
2  django/contrib/comments/admin.py
@@ -3,7 +3,7 @@
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.comments.models import Comment
-from django.utils.translation import ugettext_lazy as _, ungettext, ungettext_lazy
+from django.utils.translation import ugettext_lazy as _, ungettext_lazy
from django.contrib.comments import get_model
from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete
View
1  django/contrib/flatpages/tests/test_csrf.py
@@ -1,5 +1,4 @@
import os
-from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase, Client
View
1  django/contrib/flatpages/tests/test_templatetags.py
@@ -1,5 +1,4 @@
import os
-from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.template import Template, Context, TemplateSyntaxError
View
4 django/contrib/formtools/tests/tests.py
@@ -3,15 +3,11 @@
import datetime
import os
-import pickle
-import re
import warnings
from django import http
-from django.conf import settings
from django.contrib.formtools import preview, utils
from django.test import TestCase
-from django.test.html import parse_html
from django.test.utils import override_settings
from django.utils._os import upath
from django.utils import unittest
View
2  django/contrib/formtools/tests/wizard/test_cookiestorage.py
@@ -1,5 +1,3 @@
-import json
-
from django.test import TestCase
from django.core import signing
from django.core.exceptions import SuspiciousOperation
View
1  django/contrib/gis/admin/widgets.py
@@ -2,7 +2,6 @@
from django.forms.widgets import Textarea
from django.template import loader, Context
-from django.templatetags.static import static
from django.utils import six
from django.utils import translation
View
1  django/contrib/gis/db/backends/postgis/creation.py
@@ -1,5 +1,4 @@
from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
from django.utils.functional import cached_property
View
1  django/contrib/gis/db/models/aggregates.py
@@ -1,5 +1,4 @@
from django.db.models import Aggregate
-from django.contrib.gis.db.models.sql import GeomField
class Collect(Aggregate):
name = 'Collect'
View
1  django/contrib/gis/geos/tests/test_io.py
@@ -4,7 +4,6 @@
import unittest
from django.contrib.gis import memoryview
-from django.utils import six
from django.utils.unittest import skipUnless
from ..import HAS_GEOS
View
1  django/contrib/gis/management/commands/ogrinspect.py
@@ -1,4 +1,3 @@
-import os
from optparse import make_option
from django.contrib.gis import gdal
from django.core.management.base import LabelCommand, CommandError
View
2  django/contrib/gis/tests/relatedapp/tests.py
@@ -1,7 +1,5 @@
from __future__ import absolute_import
-from datetime import date
-
from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB, mysql, oracle, no_mysql, no_oracle, no_spatialite
from django.test import TestCase
View
1  django/contrib/sitemaps/tests/base.py
@@ -1,4 +1,3 @@
-from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.cache import cache
from django.db import models
View
1  django/contrib/sitemaps/tests/urls/http.py
@@ -1,7 +1,6 @@
from datetime import datetime
from django.conf.urls import patterns, url
from django.contrib.sitemaps import Sitemap, GenericSitemap, FlatPageSitemap, views
-from django.contrib.auth.models import User
from django.views.decorators.cache import cache_page
from django.contrib.sitemaps.tests.base import TestModel
View
0  tests/compat_checks/__init__.py → django/core/checks/__init__.py
File renamed without changes
View
0  django/core/compat_checks/__init__.py → django/core/checks/compatibility/__init__.py
File renamed without changes
View
2  django/core/compat_checks/base.py → django/core/checks/compatibility/base.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
import warnings
-from django.core.compat_checks import django_1_6_0
+from django.core.checks.compatibility import django_1_6_0
COMPAT_CHECKS = [
View
2  django/core/compat_checks/django_1_6_0.py → ...core/checks/compatibility/django_1_6_0.py
@@ -27,7 +27,7 @@ def check_test_runner():
def run_checks():
"""
- Required by the ``checksetup`` management command, this returns a list of
+ Required by the ``check`` management command, this returns a list of
messages from all the relevant check functions for this version of Django.
"""
checks = [
View
1  django/core/exceptions.py
@@ -1,7 +1,6 @@
"""
Global Django exception and warning classes.
"""
-import logging
from functools import reduce
import operator
View
4 django/core/files/move.py
@@ -51,6 +51,10 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove
return
try:
+ # If the destination file exists and allow_overwrite is False then raise an IOError
+ if not allow_overwrite and os.access(new_file_name, os.F_OK):
+ raise IOError("Destination file %s exists and allow_overwrite is False" % new_file_name)
+
os.rename(old_file_name, new_file_name)
return
except OSError:
View
1  django/core/handlers/wsgi.py
@@ -103,6 +103,7 @@ def __init__(self, environ):
content_length = 0
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
self._read_started = False
+ self.resolver_match = None
def _is_secure(self):
return 'wsgi.url_scheme' in self.environ and self.environ['wsgi.url_scheme'] == 'https'
View
2  django/core/management/__init__.py
@@ -3,13 +3,11 @@
import sys
from optparse import OptionParser, NO_DEFAULT
import imp
-import warnings
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError, handle_default_options
from django.core.management.color import color_style
from django.utils.importlib import import_module
-from django.utils._os import upath
from django.utils import six
# For backwards compatibility: get_version() used to be in this module.
View
2  ...go/core/management/commands/checksetup.py → django/core/management/commands/check.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
import warnings
-from django.core.compat_checks.base import check_compatibility
+from django.core.checks.compatibility.base import check_compatibility
from django.core.management.base import NoArgsCommand
View
6 django/core/management/commands/makemessages.py
@@ -402,11 +402,11 @@ def copy_plural_forms(self, msgs, locale):
if self.verbosity > 1:
self.stdout.write("copying plural forms: %s\n" % m.group('value'))
lines = []
- seen = False
+ found = False
for line in msgs.split('\n'):
- if not line and not seen:
+ if not found and (not line or plural_forms_re.search(line)):
line = '%s\n' % m.group('value')
- seen = True
+ found = True
lines.append(line)
msgs = '\n'.join(lines)
break
View
2  django/core/management/templates.py
@@ -105,7 +105,7 @@ def handle(self, app_or_project, name, target=None, **options):
base_name = '%s_name' % app_or_project
base_subdir = '%s_template' % app_or_project
base_directory = '%s_directory' % app_or_project
- if django.VERSION[-1] == 0:
+ if django.VERSION[-2] != 'final':
docs_version = 'dev'
else:
docs_version = '%d.%d' % django.VERSION[:2]
View
1  django/core/serializers/base.py
@@ -3,7 +3,6 @@
"""
from django.db import models
-from django.utils.encoding import smart_text
from django.utils import six
class SerializerDoesNotExist(KeyError):
View
14 django/db/backends/__init__.py
@@ -330,6 +330,15 @@ def set_autocommit(self, autocommit):
self._set_autocommit(autocommit)
self.autocommit = autocommit
+ def set_rollback(self, rollback):
+ """
+ Set or unset the "needs rollback" flag -- for *advanced use* only.
+ """
+ if not self.in_atomic_block:
+ raise TransactionManagementError(
+ "needs_rollback doesn't work outside of an 'atomic' block.")
+ self.needs_rollback = rollback
+
def validate_no_atomic_block(self):
"""
Raise an error if an atomic block is active.
@@ -623,6 +632,11 @@ class BaseDatabaseFeatures(object):
# Does it support CHECK constraints?
supports_check_constraints = True
+ # Does the backend support 'pyformat' style ("... %(name)s ...", {'name': value})
+ # parameter passing? Note this can be provided by the backend even if not
+ # supported by the Python driver
+ supports_paramstyle_pyformat = True
+
def __init__(self, connection):
self.connection = connection
View
66 django/db/backends/oracle/base.py
@@ -762,20 +762,37 @@ def __init__(self, connection):
self.cursor.arraysize = 100
def _format_params(self, params):
- return tuple([OracleParam(p, self, True) for p in params])
+ try:
+ return dict((k,OracleParam(v, self, True)) for k,v in params.items())
+ except AttributeError:
+ return tuple([OracleParam(p, self, True) for p in params])
def _guess_input_sizes(self, params_list):
- sizes = [None] * len(params_list[0])
- for params in params_list:
- for i, value in enumerate(params):
- if value.input_size:
- sizes[i] = value.input_size
- self.setinputsizes(*sizes)
+ # Try dict handling; if that fails, treat as sequence
+ if hasattr(params_list[0], 'keys'):
+ sizes = {}
+ for params in params_list:
+ for k, value in params.items():
+ if value.input_size:
+ sizes[k] = value.input_size
+ self.setinputsizes(**sizes)
+ else:
+ # It's not a list of dicts; it's a list of sequences
+ sizes = [None] * len(params_list[0])
+ for params in params_list:
+ for i, value in enumerate(params):
+ if value.input_size:
+ sizes[i] = value.input_size
+ self.setinputsizes(*sizes)
def _param_generator(self, params):
- return [p.force_bytes for p in params]
+ # Try dict handling; if that fails, treat as sequence
+ if hasattr(params, 'items'):
+ return dict((k, v.force_bytes) for k,v in params.items())
+ else:
+ return [p.force_bytes for p in params]
- def execute(self, query, params=None):
+ def _fix_for_params(self, query, params):
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query
@@ -785,10 +802,18 @@ def execute(self, query, params=None):
if params is None:
params = []
query = convert_unicode(query, self.charset)
+ elif hasattr(params, 'keys'):
+ # Handle params as dict
+ args = dict((k, ":%s"%k) for k in params.keys())
+ query = convert_unicode(query % args, self.charset)
else:
- params = self._format_params(params)
+ # Handle params as sequence
args = [(':arg%d' % i) for i in range(len(params))]
query = convert_unicode(query % tuple(args), self.charset)
+ return query, self._format_params(params)
+
+ def execute(self, query, params=None):
+ query, params = self._fix_for_params(query, params)
self._guess_input_sizes([params])
try:
return self.cursor.execute(query, self._param_generator(params))
@@ -799,22 +824,15 @@ def execute(self, query, params=None):
raise
def executemany(self, query, params=None):
- # cx_Oracle doesn't support iterators, convert them to lists
- if params is not None and not isinstance(params, (list, tuple)):
- params = list(params)
- try:
- args = [(':arg%d' % i) for i in range(len(params[0]))]
- except (IndexError, TypeError):
+ if not params:
# No params given, nothing to do
return None
- # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
- # it does want a trailing ';' but not a trailing '/'. However, these
- # characters must be included in the original query in case the query
- # is being passed to SQL*Plus.
- if query.endswith(';') or query.endswith('/'):
- query = query[:-1]
- query = convert_unicode(query % tuple(args), self.charset)
- formatted = [self._format_params(i) for i in params]
+ # uniform treatment for sequences and iterables
+ params_iter = iter(params)
+ query, firstparams = self._fix_for_params(query, next(params_iter))
+ # we build a list of formatted params; as we're going to traverse it
+ # more than once, we can't make it lazy by using a generator
+ formatted = [firstparams]+[self._format_params(p) for p in params_iter]
self._guess_input_sizes(formatted)
try:
return self.cursor.executemany(query,
View
2  django/db/backends/postgresql_psycopg2/base.py
@@ -6,7 +6,6 @@
import logging
import sys
-from django.db import utils
from django.db.backends import *
from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations
from django.db.backends.postgresql_psycopg2.client import DatabaseClient
@@ -17,7 +16,6 @@
from django.utils.encoding import force_str
from django.utils.functional import cached_property
from django.utils.safestring import SafeText, SafeBytes
-from django.utils import six
from django.utils.timezone import utc
try:
View
2  django/db/backends/postgresql_psycopg2/creation.py
@@ -1,5 +1,3 @@
-import psycopg2.extensions
-
from django.db.backends.creation import BaseDatabaseCreation
from django.db.backends.util import truncate_name
View
2  django/db/backends/postgresql_psycopg2/operations.py
@@ -69,7 +69,7 @@ def lookup_cast(self, lookup_type):
# Cast text lookups to text to allow things like filter(x__contains=4)
if lookup_type in ('iexact', 'contains', 'icontains', 'startswith',
- 'istartswith', 'endswith', 'iendswith'):
+ 'istartswith', 'endswith', 'iendswith', 'regex', 'iregex'):
lookup = "%s::text"
# Use UPPER(x) for case-insensitive lookups; it's faster.
View
7 django/db/backends/sqlite3/base.py
@@ -20,6 +20,7 @@
from django.db.models import fields
from django.db.models.sql import aggregates
from django.utils.dateparse import parse_date, parse_datetime, parse_time
+from django.utils.encoding import force_text
from django.utils.functional import cached_property
from django.utils.safestring import SafeBytes
from django.utils import six
@@ -103,6 +104,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_foreign_keys = False
supports_check_constraints = False
autocommits_when_autocommit_is_off = True
+ supports_paramstyle_pyformat = False
@cached_property
def uses_savepoints(self):
@@ -253,6 +255,9 @@ def convert_values(self, value, field):
and gets dates and datetimes wrong.
For consistency with other backends, coerce when required.
"""
+ if value is None:
+ return None
+
internal_type = field.get_internal_type()
if internal_type == 'DecimalField':
return util.typecast_decimal(field.format_number(value))
@@ -526,4 +531,4 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
return str(dt)
def _sqlite_regexp(re_pattern, re_string):
- return bool(re.search(re_pattern, re_string))
+ return bool(re.search(re_pattern, force_text(re_string))) if re_string is not None else False
View
22 django/db/models/fields/related.py
@@ -137,7 +137,7 @@ def related_query_name(self):
# related object in a table-spanning query. It uses the lower-cased
# object_name by default, but this can be overridden with the
# "related_name" option.
- return self.rel.related_name or self.opts.model_name
+ return self.rel.related_query_name or self.rel.related_name or self.opts.model_name
class RenameRelatedObjectDescriptorMethods(RenameMethodsBase):
@@ -824,7 +824,7 @@ def __set__(self, instance, value):
class ForeignObjectRel(object):
def __init__(self, field, to, related_name=None, limit_choices_to=None,
- parent_link=False, on_delete=None):
+ parent_link=False, on_delete=None, related_query_name=None):
try:
to._meta
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
@@ -833,6 +833,7 @@ def __init__(self, field, to, related_name=None, limit_choices_to=None,
self.field = field
self.to = to
self.related_name = related_name
+ self.related_query_name = related_query_name
self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to
self.multiple = True
self.parent_link = parent_link
@@ -860,10 +861,10 @@ def set_field_name(self):
class ManyToOneRel(ForeignObjectRel):
def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None,
- parent_link=False, on_delete=None):
+ parent_link=False, on_delete=None, related_query_name=None):
super(ManyToOneRel, self).__init__(
field, to, related_name=related_name, limit_choices_to=limit_choices_to,
- parent_link=parent_link, on_delete=on_delete)
+ parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name)
self.field_name = field_name
def get_related_field(self):
@@ -883,21 +884,22 @@ def set_field_name(self):
class OneToOneRel(ManyToOneRel):
def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None,
- parent_link=False, on_delete=None):
+ parent_link=False, on_delete=None, related_query_name=None):
super(OneToOneRel, self).__init__(field, to, field_name,
related_name=related_name, limit_choices_to=limit_choices_to,
- parent_link=parent_link, on_delete=on_delete
+ parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name,
)
self.multiple = False
class ManyToManyRel(object):
def __init__(self, to, related_name=None, limit_choices_to=None,
- symmetrical=True, through=None, db_constraint=True):
+ symmetrical=True, through=None, db_constraint=True, related_query_name=None):
if through and not db_constraint:
raise ValueError("Can't supply a through model and db_constraint=False")
self.to = to
self.related_name = related_name
+ self.related_query_name = related_query_name
if limit_choices_to is None:
limit_choices_to = {}
self.limit_choices_to = limit_choices_to
@@ -931,6 +933,7 @@ def __init__(self, to, from_fields, to_fields, **kwargs):
kwargs['rel'] = ForeignObjectRel(
self, to,
related_name=kwargs.pop('related_name', None),
+ related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
parent_link=kwargs.pop('parent_link', False),
on_delete=kwargs.pop('on_delete', CASCADE),
@@ -1141,6 +1144,7 @@ def __init__(self, to, to_field=None, rel_class=ManyToOneRel,
kwargs['rel'] = rel_class(
self, to, to_field,
related_name=kwargs.pop('related_name', None),
+ related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
parent_link=kwargs.pop('parent_link', False),
on_delete=kwargs.pop('on_delete', CASCADE),
@@ -1371,6 +1375,7 @@ def __init__(self, to, db_constraint=True, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
kwargs['rel'] = ManyToManyRel(to,
related_name=kwargs.pop('related_name', None),
+ related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT),
through=kwargs.pop('through', None),
@@ -1388,7 +1393,8 @@ def deconstruct(self):
# Handle the simpler arguments
if self.rel.db_constraint is not True:
kwargs['db_constraint'] = self.db_constraint
- del kwargs['help_text']
+ if "help_text" in kwargs:
+ del kwargs['help_text']
# Rel needs more work.
rel = self.rel
if isinstance(self.rel.to, basestring):
View
24 django/db/models/options.py
@@ -422,12 +422,36 @@ def init_name_map(self):
return cache
def get_add_permission(self):
+ """
+ This method has been deprecated in favor of
+ `django.contrib.auth.get_permission_codename`. refs #20642
+ """
+ warnings.warn(
+ "`Options.get_add_permission` has been deprecated in favor "
+ "of `django.contrib.auth.get_permission_codename`.",
+ PendingDeprecationWarning, stacklevel=2)
return 'add_%s' % self.model_name
def get_change_permission(self):
+ """
+ This method has been deprecated in favor of
+ `django.contrib.auth.get_permission_codename`. refs #20642
+ """
+ warnings.warn(
+ "`Options.get_change_permission` has been deprecated in favor "
+ "of `django.contrib.auth.get_permission_codename`.",
+ PendingDeprecationWarning, stacklevel=2)
return 'change_%s' % self.model_name
def get_delete_permission(self):
+ """
+ This method has been deprecated in favor of
+ `django.contrib.auth.get_permission_codename`. refs #20642
+ """
+ warnings.warn(
+ "`Options.get_delete_permission` has been deprecated in favor "
+ "of `django.contrib.auth.get_permission_codename`.",
+ PendingDeprecationWarning, stacklevel=2)
return 'delete_%s' % self.model_name
def get_all_related_objects(self, local_only=False, include_hidden=False,
View
5 django/db/models/query.py
@@ -1445,7 +1445,10 @@ def __iter__(self):
yield instance
def __repr__(self):
- return "<RawQuerySet: %r>" % (self.raw_query % tuple(self.params))
+ text = self.raw_query
+ if self.params:
+ text = text % (self.params if hasattr(self.params, 'keys') else tuple(self.params))
+ return "<RawQuerySet: %r>" % text
def __getitem__(self, k):
return list(self)[k]
View
20 django/db/transaction.py
@@ -171,6 +171,26 @@ def clean_savepoints(using=None):
"""
get_connection(using).clean_savepoints()
+def get_rollback(using=None):
+ """
+ Gets the "needs rollback" flag -- for *advanced use* only.
+ """
+ return get_connection(using).needs_rollback
+
+def set_rollback(rollback, using=None):
+ """
+ Sets or unsets the "needs rollback" flag -- for *advanced use* only.
+
+ When `rollback` is `True`, it triggers a rollback when exiting the
+ innermost enclosing atomic block that has `savepoint=True` (that's the
+ default). Use this to force a rollback without raising an exception.
+
+ When `rollback` is `False`, it prevents such a rollback. Use this only
+ after rolling back to a known-good state! Otherwise, you break the atomic
+ block and data corruption may occur.
+ """
+ return get_connection(using).set_rollback(rollback)
+
#################################
# Decorators / context managers #
#################################
View
10 django/forms/fields.py
@@ -370,14 +370,8 @@ def validate(self, value):
def widget_attrs(self, widget):
attrs = super(DecimalField, self).widget_attrs(widget)
- if isinstance(widget, NumberInput):
- if self.max_digits is not None:
- max_length = self.max_digits + 1 # for the sign
- if self.decimal_places is None or self.decimal_places > 0:
- max_length += 1 # for the dot
- attrs['maxlength'] = max_length
- if self.decimal_places:
- attrs['step'] = '0.%s1' % ('0' * (self.decimal_places-1))
+ if isinstance(widget, NumberInput) and self.decimal_places:
+ attrs['step'] = '0.%s1' % ('0' * (self.decimal_places - 1))
return attrs
</