summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
Diffstat (limited to 'django')
-rw-r--r--django/__init__.py2
-rw-r--r--django/contrib/admin/actions.py2
-rw-r--r--django/contrib/admin/options.py123
-rw-r--r--django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js4
-rw-r--r--django/contrib/admin/templates/admin/popup_response.html9
-rw-r--r--django/contrib/admin/templates/admin/search_form.html2
-rw-r--r--django/contrib/admin/templates/registration/password_reset_email.html2
-rw-r--r--django/contrib/admin/templatetags/admin_list.py6
-rw-r--r--django/contrib/admin/templatetags/admin_urls.py6
-rw-r--r--django/contrib/admin/validation.py3
-rw-r--r--django/contrib/admin/views/main.py28
-rw-r--r--django/contrib/admindocs/views.py1
-rw-r--r--django/contrib/auth/__init__.py15
-rw-r--r--django/contrib/auth/admin.py5
-rw-r--r--django/contrib/auth/forms.py5
-rw-r--r--django/contrib/auth/management/__init__.py13
-rw-r--r--django/contrib/auth/models.py27
-rw-r--r--django/contrib/auth/tests/templates/registration/password_reset_email.html2
-rw-r--r--django/contrib/auth/tests/test_custom_user.py22
-rw-r--r--django/contrib/auth/tests/test_models.py25
-rw-r--r--django/contrib/auth/tests/test_remote_user.py2
-rw-r--r--django/contrib/auth/tests/test_views.py27
-rw-r--r--django/contrib/auth/tests/urls.py5
-rw-r--r--django/contrib/auth/urls.py3
-rw-r--r--django/contrib/auth/views.py21
-rw-r--r--django/contrib/comments/admin.py2
-rw-r--r--django/contrib/flatpages/tests/test_csrf.py1
-rw-r--r--django/contrib/flatpages/tests/test_templatetags.py1
-rw-r--r--django/contrib/formtools/tests/tests.py4
-rw-r--r--django/contrib/formtools/tests/wizard/test_cookiestorage.py2
-rw-r--r--django/contrib/gis/admin/widgets.py1
-rw-r--r--django/contrib/gis/db/backends/postgis/creation.py1
-rw-r--r--django/contrib/gis/db/models/aggregates.py1
-rw-r--r--django/contrib/gis/geos/tests/test_io.py1
-rw-r--r--django/contrib/gis/management/commands/ogrinspect.py1
-rw-r--r--django/contrib/gis/tests/relatedapp/tests.py2
-rw-r--r--django/contrib/sitemaps/tests/base.py1
-rw-r--r--django/contrib/sitemaps/tests/urls/http.py1
-rw-r--r--django/core/checks/__init__.py (renamed from django/core/compat_checks/__init__.py)0
-rw-r--r--django/core/checks/compatibility/__init__.py0
-rw-r--r--django/core/checks/compatibility/base.py (renamed from django/core/compat_checks/base.py)2
-rw-r--r--django/core/checks/compatibility/django_1_6_0.py (renamed from django/core/compat_checks/django_1_6_0.py)2
-rw-r--r--django/core/exceptions.py1
-rw-r--r--django/core/files/move.py4
-rw-r--r--django/core/handlers/wsgi.py1
-rw-r--r--django/core/management/__init__.py2
-rw-r--r--django/core/management/commands/check.py (renamed from django/core/management/commands/checksetup.py)2
-rw-r--r--django/core/management/commands/makemessages.py6
-rw-r--r--django/core/management/templates.py2
-rw-r--r--django/core/serializers/base.py1
-rw-r--r--django/db/backends/__init__.py14
-rw-r--r--django/db/backends/oracle/base.py66
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py2
-rw-r--r--django/db/backends/postgresql_psycopg2/creation.py2
-rw-r--r--django/db/backends/postgresql_psycopg2/operations.py2
-rw-r--r--django/db/backends/sqlite3/base.py7
-rw-r--r--django/db/models/fields/related.py22
-rw-r--r--django/db/models/options.py24
-rw-r--r--django/db/models/query.py5
-rw-r--r--django/db/transaction.py20
-rw-r--r--django/forms/fields.py10
-rw-r--r--django/forms/formsets.py17
-rw-r--r--django/forms/util.py1
-rw-r--r--django/forms/widgets.py5
-rw-r--r--django/http/request.py4
-rw-r--r--django/middleware/csrf.py2
-rw-r--r--django/template/base.py1
-rw-r--r--django/template/defaultfilters.py1
-rw-r--r--django/template/defaulttags.py5
-rw-r--r--django/test/utils.py25
-rw-r--r--django/utils/html.py1
-rw-r--r--django/utils/http.py21
-rw-r--r--django/utils/text.py1
-rw-r--r--django/views/debug.py1
-rw-r--r--django/views/decorators/csrf.py2
-rw-r--r--django/views/generic/edit.py19
76 files changed, 455 insertions, 227 deletions
diff --git a/django/__init__.py b/django/__init__.py
index 5a1c74efa7..b8077e17fa 100644
--- a/django/__init__.py
+++ b/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.
diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py
index a56c6a6168..a4c5828f37 100644
--- a/django/contrib/admin/actions.py
+++ b/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
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 942385e1c6..fd516cb512 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -4,18 +4,15 @@ from functools import partial, reduce, update_wrapper
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.constants import LOOKUP_SEP
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.text import capfirst, get_text_list
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 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = {
'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 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
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 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
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 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
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 @@ class ModelAdmin(BaseModelAdmin):
"""
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 @@ class ModelAdmin(BaseModelAdmin):
"""
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 @@ class ModelAdmin(BaseModelAdmin):
"""
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 @@ class ModelAdmin(BaseModelAdmin):
"""
# 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 @@ class ModelAdmin(BaseModelAdmin):
'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 @@ class ModelAdmin(BaseModelAdmin):
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 @@ class ModelAdmin(BaseModelAdmin):
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 @@ class ModelAdmin(BaseModelAdmin):
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 @@ class ModelAdmin(BaseModelAdmin):
'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 @@ class ModelAdmin(BaseModelAdmin):
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 @@ class ModelAdmin(BaseModelAdmin):
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 @@ class ModelAdmin(BaseModelAdmin):
(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 @@ class ModelAdmin(BaseModelAdmin):
(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 @@ class ModelAdmin(BaseModelAdmin):
"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 @@ class InlineModelAdmin(BaseModelAdmin):
# 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 @@ class InlineModelAdmin(BaseModelAdmin):
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 @@ class InlineModelAdmin(BaseModelAdmin):
# 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):
diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
index ce54fa5083..6d1748663e 100644
--- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
+++ b/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();
diff --git a/django/contrib/admin/templates/admin/popup_response.html b/django/contrib/admin/templates/admin/popup_response.html
new file mode 100644
index 0000000000..44833b2f93
--- /dev/null
+++ b/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>
diff --git a/django/contrib/admin/templates/admin/search_form.html b/django/contrib/admin/templates/admin/search_form.html
index f5a9e7b8b9..c9b626d18f 100644
--- a/django/contrib/admin/templates/admin/search_form.html
+++ b/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 %}
diff --git a/django/contrib/admin/templates/registration/password_reset_email.html b/django/contrib/admin/templates/registration/password_reset_email.html
index 44ae5850b1..01b3bccbbc 100644
--- a/django/contrib/admin/templates/registration/password_reset_email.html
+++ b/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 }}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index e81b13cda4..8596dfb825 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -11,7 +11,7 @@ from django.contrib.admin.templatetags.admin_static import static
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)
diff --git a/django/contrib/admin/templatetags/admin_urls.py b/django/contrib/admin/templatetags/admin_urls.py
index bb1b16883d..19da87d61f 100644
--- a/django/contrib/admin/templatetags/admin_urls.py
+++ b/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 @@ except ImportError:
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)
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index 222d433e53..fb9634d114 100644
--- a/django/contrib/admin/validation.py
+++ b/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
"""
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index f676706a89..56462dece8 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -15,7 +15,7 @@ from django.utils.http import urlencode
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 @@ ORDER_TYPE_VAR = 'ot'
PAGE_VAR = 'p'
SEARCH_VAR = 'q'
TO_FIELD_VAR = 't'
-IS_POPUP_VAR = 'pop'
ERROR_FLAG = 'e'
IGNORED_PARAMS = (
@@ -36,6 +35,29 @@ IGNORED_PARAMS = (
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 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
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:
diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
index c03883def7..b3faf06e25 100644
--- a/django/contrib/admindocs/views.py
+++ b/django/contrib/admindocs/views.py
@@ -17,7 +17,6 @@ from django.utils.importlib import import_module
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_')
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index 029193d582..2f620a34fe 100644
--- a/django/contrib/auth/__init__.py
+++ b/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)
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 409078fa02..5a1db68613 100644
--- a/django/contrib/auth/admin.py
+++ b/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 @@ class UserAdmin(admin.ModelAdmin):
'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 @@ class UserAdmin(admin.ModelAdmin):
# 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)
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index a9ecba45c2..43f5303b63 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -6,8 +6,9 @@ from django import forms
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 @@ class PasswordResetForm(forms.Form):
'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',
diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
index ecf6c1b635..1f338469f8 100644
--- a/django/contrib/auth/management/__init__.py
+++ b/django/contrib/auth/management/__init__.py
@@ -4,10 +4,10 @@ Creates permissions for all installed apps that need permissions.
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 import six
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')
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index f6380b9f24..5fec99a783 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -170,7 +170,8 @@ class BaseUserManager(models.Manager):
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 @@ class UserManager(BaseUserManager):
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
diff --git a/django/contrib/auth/tests/templates/registration/password_reset_email.html b/django/contrib/auth/tests/templates/registration/password_reset_email.html
index 1b9a48255a..baac2fc2dd 100644
--- a/django/contrib/auth/tests/templates/registration/password_reset_email.html
+++ b/django/contrib/auth/tests/templates/registration/password_reset_email.html
@@ -1 +1 @@
-{{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/ \ No newline at end of file
+{{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/
diff --git a/django/contrib/auth/tests/test_custom_user.py b/django/contrib/auth/tests/test_custom_user.py
index 0f6ebbda55..394baa3204 100644
--- a/django/contrib/auth/tests/test_custom_user.py
+++ b/django/contrib/auth/tests/test_custom_user.py
@@ -4,7 +4,9 @@ from django.contrib.auth.models import (
AbstractBaseUser,
AbstractUser,
UserManager,
- PermissionsMixin
+ PermissionsMixin,
+ Group,
+ Permission,
)
@@ -81,6 +83,20 @@ class CustomUser(AbstractBaseUser):
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
diff --git a/django/contrib/auth/tests/test_models.py b/django/contrib/auth/tests/test_models.py
index cf412c96e6..b0a4559b42 100644
--- a/django/contrib/auth/tests/test_models.py
+++ b/django/contrib/auth/tests/test_models.py
@@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model
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 @@ class IsActiveTestCase(TestCase):
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)
diff --git a/django/contrib/auth/tests/test_remote_user.py b/django/contrib/auth/tests/test_remote_user.py
index dae61afed5..5c5024eae9 100644
--- a/django/contrib/auth/tests/test_remote_user.py
+++ b/django/contrib/auth/tests/test_remote_user.py
@@ -3,7 +3,7 @@ from datetime import datetime
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
diff --git a/django/contrib/auth/tests/test_views.py b/django/contrib/auth/tests/test_views.py
index ef305ac8f1..ba06a6af4d 100644
--- a/django/contrib/auth/tests/test_views.py
+++ b/django/contrib/auth/tests/test_views.py
@@ -13,8 +13,7 @@ from django.core import mail
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.sessions.middleware import SessionMiddleware
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 @@ class AuthViewNamedURLTests(AuthViewsTestCase):
('password_reset', [], {}),
('password_reset_done', [], {}),
('password_reset_confirm', [], {
- 'uidb36': 'aaaaaaa',
+ 'uidb64': 'aaaaaaa',
'token': '1111-aaaaa',
}),
('password_reset_complete', [], {}),
@@ -194,6 +193,16 @@ class PasswordResetTest(AuthViewsTestCase):
# 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 @@ class PasswordResetTest(AuthViewsTestCase):
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")
diff --git a/django/contrib/auth/tests/urls.py b/django/contrib/auth/tests/urls.py
index 835ff41de7..502fc659d4 100644
--- a/django/contrib/auth/tests/urls.py
+++ b/django/contrib/auth/tests/urls.py
@@ -67,10 +67,10 @@ urlpatterns = urlpatterns + patterns('',
(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 @@ urlpatterns = urlpatterns + patterns('',
(r'^custom_request_auth_login/$', custom_request_auth_login),
url(r'^userpage/(.+)/$', userpage, name="userpage"),
)
-
diff --git a/django/contrib/auth/urls.py b/django/contrib/auth/urls.py
index c5e87ed2eb..801d133437 100644
--- a/django/contrib/auth/urls.py
+++ b/django/contrib/auth/urls.py
@@ -12,7 +12,10 @@ urlpatterns = patterns('',
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'),
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index fe21683323..e9affb33cd 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -7,9 +7,10 @@ from django.conf import settings
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',
diff --git a/django/contrib/comments/admin.py b/django/contrib/comments/admin.py
index bca638182c..391889cd65 100644
--- a/django/contrib/comments/admin.py
+++ b/django/contrib/comments/admin.py
@@ -3,7 +3,7 @@ from __future__ import unicode_literals
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
diff --git a/django/contrib/flatpages/tests/test_csrf.py b/django/contrib/flatpages/tests/test_csrf.py
index 59996d9824..cb51c124b8 100644
--- a/django/contrib/flatpages/tests/test_csrf.py
+++ b/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
diff --git a/django/contrib/flatpages/tests/test_templatetags.py b/django/contrib/flatpages/tests/test_templatetags.py
index 1a6f3c2975..23fa8faf2d 100644
--- a/django/contrib/flatpages/tests/test_templatetags.py
+++ b/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
diff --git a/django/contrib/formtools/tests/tests.py b/django/contrib/formtools/tests/tests.py
index 56ef901127..58c59f4041 100644
--- a/django/contrib/formtools/tests/tests.py
+++ b/django/contrib/formtools/tests/tests.py
@@ -3,15 +3,11 @@ from __future__ import unicode_literals
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
diff --git a/django/contrib/formtools/tests/wizard/test_cookiestorage.py b/django/contrib/formtools/tests/wizard/test_cookiestorage.py
index 060e8260b5..4c1ff97475 100644
--- a/django/contrib/formtools/tests/wizard/test_cookiestorage.py
+++ b/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
diff --git a/django/contrib/gis/admin/widgets.py b/django/contrib/gis/admin/widgets.py
index ceb8e9c9bd..056d25952a 100644
--- a/django/contrib/gis/admin/widgets.py
+++ b/django/contrib/gis/admin/widgets.py
@@ -2,7 +2,6 @@ import logging
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
diff --git a/django/contrib/gis/db/backends/postgis/creation.py b/django/contrib/gis/db/backends/postgis/creation.py
index 43ae9a0331..4f64ecce29 100644
--- a/django/contrib/gis/db/backends/postgis/creation.py
+++ b/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
diff --git a/django/contrib/gis/db/models/aggregates.py b/django/contrib/gis/db/models/aggregates.py
index cd26839eb5..d0fc6d319d 100644
--- a/django/contrib/gis/db/models/aggregates.py
+++ b/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'
diff --git a/django/contrib/gis/geos/tests/test_io.py b/django/contrib/gis/geos/tests/test_io.py
index 38ca2e0923..34eeaf95b9 100644
--- a/django/contrib/gis/geos/tests/test_io.py
+++ b/django/contrib/gis/geos/tests/test_io.py
@@ -4,7 +4,6 @@ import binascii
import unittest
from django.contrib.gis import memoryview
-from django.utils import six
from django.utils.unittest import skipUnless
from ..import HAS_GEOS
diff --git a/django/contrib/gis/management/commands/ogrinspect.py b/django/contrib/gis/management/commands/ogrinspect.py
index 6037cc7ea6..44513e3463 100644
--- a/django/contrib/gis/management/commands/ogrinspect.py
+++ b/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
diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py
index 6320edcff5..d93adbcd4c 100644
--- a/django/contrib/gis/tests/relatedapp/tests.py
+++ b/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
diff --git a/django/contrib/sitemaps/tests/base.py b/django/contrib/sitemaps/tests/base.py
index 099dba7256..8e027d491e 100644
--- a/django/contrib/sitemaps/tests/base.py
+++ b/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
diff --git a/django/contrib/sitemaps/tests/urls/http.py b/django/contrib/sitemaps/tests/urls/http.py
index 56103f4ec2..a8b804fd4b 100644
--- a/django/contrib/sitemaps/tests/urls/http.py
+++ b/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
diff --git a/django/core/compat_checks/__init__.py b/django/core/checks/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/django/core/compat_checks/__init__.py
+++ b/django/core/checks/__init__.py
diff --git a/django/core/checks/compatibility/__init__.py b/django/core/checks/compatibility/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/core/checks/compatibility/__init__.py
diff --git a/django/core/compat_checks/base.py b/django/core/checks/compatibility/base.py
index e54b50f287..7fe52d2af9 100644
--- a/django/core/compat_checks/base.py
+++ b/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 = [
diff --git a/django/core/compat_checks/django_1_6_0.py b/django/core/checks/compatibility/django_1_6_0.py
index bb0dabedac..1998c5ba77 100644
--- a/django/core/compat_checks/django_1_6_0.py
+++ b/django/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 = [
diff --git a/django/core/exceptions.py b/django/core/exceptions.py
index 829d6e774e..efec22850b 100644
--- a/django/core/exceptions.py
+++ b/django/core/exceptions.py
@@ -1,7 +1,6 @@
"""
Global Django exception and warning classes.
"""
-import logging
from functools import reduce
import operator
diff --git a/django/core/files/move.py b/django/core/files/move.py
index 4519dedf97..4bd739b4c4 100644
--- a/django/core/files/move.py
+++ b/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:
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index af78d1d269..38d8154ac9 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -103,6 +103,7 @@ class WSGIRequest(http.HttpRequest):
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'
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index 77d5e1b264..8fd46aa759 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -3,13 +3,11 @@ import os
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.
diff --git a/django/core/management/commands/checksetup.py b/django/core/management/commands/check.py
index d37e826757..05f48c82bc 100644
--- a/django/core/management/commands/checksetup.py
+++ b/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
diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
index 060def5d5a..695cad419b 100644
--- a/django/core/management/commands/makemessages.py
+++ b/django/core/management/commands/makemessages.py
@@ -402,11 +402,11 @@ class Command(NoArgsCommand):
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
diff --git a/django/core/management/templates.py b/django/core/management/templates.py
index 893e5c95af..1de508d749 100644
--- a/django/core/management/templates.py
+++ b/django/core/management/templates.py
@@ -105,7 +105,7 @@ class TemplateCommand(BaseCommand):
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]
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index 1e78026c40..cd4f7ffb2b 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -3,7 +3,6 @@ Module for abstract serializer/unserializer base classes.
"""
from django.db import models
-from django.utils.encoding import smart_text
from django.utils import six
class SerializerDoesNotExist(KeyError):
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 2fcf6b9604..26ca9a7fa1 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -330,6 +330,15 @@ class BaseDatabaseWrapper(object):
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
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 3105ad17e3..5fb806807d 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -762,20 +762,37 @@ class FormatStylePlaceholderCursor(object):
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 @@ class FormatStylePlaceholderCursor(object):
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 @@ class FormatStylePlaceholderCursor(object):
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,
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index de86758653..bf75459b4a 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -6,7 +6,6 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
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.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
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:
diff --git a/django/db/backends/postgresql_psycopg2/creation.py b/django/db/backends/postgresql_psycopg2/creation.py
index 819dd7a29e..1bfda6354d 100644
--- a/django/db/backends/postgresql_psycopg2/creation.py
+++ b/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
diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py
index f96757da8b..c5aab84693 100644
--- a/django/db/backends/postgresql_psycopg2/operations.py
+++ b/django/db/backends/postgresql_psycopg2/operations.py
@@ -69,7 +69,7 @@ class DatabaseOperations(BaseDatabaseOperations):
# 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.
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index a53d3ee53e..64da0a9f25 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -20,6 +20,7 @@ from django.db.backends.sqlite3.schema import DatabaseSchemaEditor
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 @@ class DatabaseOperations(BaseDatabaseOperations):
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
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 64890fff47..9f2e01e781 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -137,7 +137,7 @@ class RelatedField(Field):
# 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 @@ class ReverseManyRelatedObjectsDescriptor(object):
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 @@ class ForeignObjectRel(object):
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 @@ class ForeignObjectRel(object):
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 @@ class ManyToOneRel(ForeignObjectRel):
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 @@ class ForeignObject(RelatedField):
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 @@ class ForeignKey(ForeignObject):
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 @@ class ManyToManyField(RelatedField):
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 @@ class ManyToManyField(RelatedField):
# 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):
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 1a9421c0fa..b173da9538 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -422,12 +422,36 @@ class Options(object):
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,
diff --git a/django/db/models/query.py b/django/db/models/query.py
index b0ce25f5b5..27a87a3f65 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1445,7 +1445,10 @@ class RawQuerySet(object):
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]
diff --git a/django/db/transaction.py b/django/db/transaction.py
index f770f2efa7..95b9ae165e 100644
--- a/django/db/transaction.py
+++ b/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 #
#################################
diff --git a/django/forms/fields.py b/django/forms/fields.py
index 52bcf9485c..c4bc3fa88c 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -370,14 +370,8 @@ class DecimalField(IntegerField):
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
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index edd362c595..cb3126e6d7 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -4,8 +4,9 @@ from django.core.exceptions import ValidationError
from django.forms import Form
from django.forms.fields import IntegerField, BooleanField
from django.forms.util import ErrorList
-from django.forms.widgets import Media, HiddenInput
+from django.forms.widgets import HiddenInput
from django.utils.encoding import python_2_unicode_compatible
+from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.six.moves import xrange
@@ -55,8 +56,6 @@ class BaseFormSet(object):
self.error_class = error_class
self._errors = None
self._non_form_errors = None
- # construct the forms in the formset
- self._construct_forms()
def __str__(self):
return self.as_table()
@@ -125,12 +124,14 @@ class BaseFormSet(object):
initial_forms = len(self.initial) if self.initial else 0
return initial_forms
- def _construct_forms(self):
- # instantiate all the forms and put them in self.forms
- self.forms = []
+ @cached_property
+ def forms(self):
+ """
+ Instantiate forms at first property access.
+ """
# DoS protection is included in total_form_count()
- for i in xrange(self.total_form_count()):
- self.forms.append(self._construct_form(i))
+ forms = [self._construct_form(i) for i in xrange(self.total_form_count())]
+ return forms
def _construct_form(self, i, **kwargs):
"""
diff --git a/django/forms/util.py b/django/forms/util.py
index 568cdd1086..0a73320f83 100644
--- a/django/forms/util.py
+++ b/django/forms/util.py
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
from django.conf import settings
from django.utils.html import format_html, format_html_join
from django.utils.encoding import force_text, python_2_unicode_compatible
-from django.utils.safestring import mark_safe
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils import six
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index aca4a457af..38d1b99b0d 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -5,7 +5,6 @@ HTML Widget classes
from __future__ import absolute_import, unicode_literals
import copy
-import datetime
from itertools import chain
try:
from urllib.parse import urljoin
@@ -16,8 +15,8 @@ import warnings
from django.conf import settings
from django.forms.util import flatatt, to_current_timezone
from django.utils.datastructures import MultiValueDict, MergeDict
-from django.utils.html import conditional_escape, format_html, format_html_join
-from django.utils.translation import ugettext, ugettext_lazy
+from django.utils.html import conditional_escape, format_html
+from django.utils.translation import ugettext_lazy
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils import datetime_safe, formats, six
diff --git a/django/http/request.py b/django/http/request.py
index 37aa1a355a..b7f9d241a7 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -39,6 +39,10 @@ class HttpRequest(object):
_upload_handlers = []
def __init__(self):
+ # WARNING: The `WSGIRequest` subclass doesn't call `super`.
+ # Any variable assignment made here should also happen in
+ # `WSGIRequest.__init__()`.
+
self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
self.path = ''
self.path_info = ''
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index 1b5732fbbf..c13715eeb9 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -6,10 +6,8 @@ against request forgeries from other sites.
"""
from __future__ import unicode_literals
-import hashlib
import logging
import re
-import random
from django.conf import settings
from django.core.urlresolvers import get_callable
diff --git a/django/template/base.py b/django/template/base.py
index dc6b0c366c..364b428070 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -1101,6 +1101,7 @@ class Library(object):
# for decorators that need it e.g. stringfilter
if hasattr(filter_func, "_decorated_function"):
setattr(filter_func._decorated_function, attr, value)
+ filter_func._filter_name = name
return filter_func
else:
raise InvalidTemplateLibrary("Unsupported arguments to "
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 4201cfeb67..76c0121126 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
import re
import random as random_module
-import unicodedata
from decimal import Decimal, InvalidOperation, Context, ROUND_HALF_UP
from functools import wraps
from pprint import pformat
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 04e7a37d8e..959de3dea1 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -665,8 +665,9 @@ def do_filter(parser, token):
_, rest = token.contents.split(None, 1)
filter_expr = parser.compile_filter("var|%s" % (rest))
for func, unused in filter_expr.filters:
- if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
- raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__)
+ filter_name = getattr(func, '_filter_name', None)
+ if filter_name in ('escape', 'safe'):
+ raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % filter_name)
nodelist = parser.parse(('endfilter',))
parser.delete_first_token()
return FilterNode(filter_expr, nodelist)
diff --git a/django/test/utils.py b/django/test/utils.py
index be586c75a6..591c588933 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -10,6 +10,7 @@ from django.conf import settings, UserSettingsHolder
from django.core import mail
from django.core.signals import request_started
from django.db import reset_queries
+from django.http import request
from django.template import Template, loader, TemplateDoesNotExist
from django.template.loaders import cached
from django.test.signals import template_rendered, setting_changed
@@ -87,13 +88,16 @@ def setup_test_environment():
- Set the email backend to the locmem email backend.
- Setting the active locale to match the LANGUAGE_CODE setting.
"""
- Template.original_render = Template._render
+ Template._original_render = Template._render
Template._render = instrumented_test_render
- mail.original_email_backend = settings.EMAIL_BACKEND
+ # Storing previous values in the settings module itself is problematic.
+ # Store them in arbitrary (but related) modules instead. See #20636.
+
+ mail._original_email_backend = settings.EMAIL_BACKEND
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
- settings._original_allowed_hosts = settings.ALLOWED_HOSTS
+ request._original_allowed_hosts = settings.ALLOWED_HOSTS
settings.ALLOWED_HOSTS = ['*']
mail.outbox = []
@@ -108,14 +112,14 @@ def teardown_test_environment():
- Restoring the email sending functions
"""
- Template._render = Template.original_render
- del Template.original_render
+ Template._render = Template._original_render
+ del Template._original_render
- settings.EMAIL_BACKEND = mail.original_email_backend
- del mail.original_email_backend
+ settings.EMAIL_BACKEND = mail._original_email_backend
+ del mail._original_email_backend
- settings.ALLOWED_HOSTS = settings._original_allowed_hosts
- del settings._original_allowed_hosts
+ settings.ALLOWED_HOSTS = request._original_allowed_hosts
+ del request._original_allowed_hosts
del mail.outbox
@@ -207,7 +211,6 @@ class override_settings(object):
"""
def __init__(self, **kwargs):
self.options = kwargs
- self.wrapped = settings._wrapped
def __enter__(self):
self.enable()
@@ -246,6 +249,7 @@ class override_settings(object):
override = UserSettingsHolder(settings._wrapped)
for key, new_value in self.options.items():
setattr(override, key, new_value)
+ self.wrapped = settings._wrapped
settings._wrapped = override
for key, new_value in self.options.items():
setting_changed.send(sender=settings._wrapped.__class__,
@@ -253,6 +257,7 @@ class override_settings(object):
def disable(self):
settings._wrapped = self.wrapped
+ del self.wrapped
for key in self.options:
new_value = getattr(settings, key, None)
setting_changed.send(sender=settings._wrapped.__class__,
diff --git a/django/utils/html.py b/django/utils/html.py
index 0d28c77a61..4893b6b18a 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -3,7 +3,6 @@
from __future__ import unicode_literals
import re
-import string
try:
from urllib.parse import quote, urlsplit, urlunsplit
except ImportError: # Python 2
diff --git a/django/utils/http.py b/django/utils/http.py
index f4911b4ec0..4647d89847 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+import base64
import calendar
import datetime
import re
@@ -11,7 +12,7 @@ except ImportError: # Python 2
import urlparse
urllib_parse.urlparse = urlparse.urlparse
-
+from binascii import Error as BinasciiError
from email.utils import formatdate
from django.utils.datastructures import MultiValueDict
@@ -202,6 +203,24 @@ def int_to_base36(i):
factor -= 1
return ''.join(base36)
+def urlsafe_base64_encode(s):
+ """
+ Encodes a bytestring in base64 for use in URLs, stripping any trailing
+ equal signs.
+ """
+ return base64.urlsafe_b64encode(s).rstrip(b'\n=')
+
+def urlsafe_base64_decode(s):
+ """
+ Decodes a base64 encoded string, adding back any trailing equal signs that
+ might have been stripped.
+ """
+ s = s.encode('utf-8') # base64encode should only return ASCII.
+ try:
+ return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b'='))
+ except (LookupError, BinasciiError) as e:
+ raise ValueError(e)
+
def parse_etags(etag_str):
"""
Parses a string with one or several etags passed in If-None-Match and
diff --git a/django/utils/text.py b/django/utils/text.py
index 4fe92ab509..7bb8e7eb3e 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -2,7 +2,6 @@ from __future__ import unicode_literals
import re
import unicodedata
-import warnings
from gzip import GzipFile
from io import BytesIO
diff --git a/django/views/debug.py b/django/views/debug.py
index 0458580221..2129a83d67 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -7,7 +7,6 @@ import sys
import types
from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
from django.http import (HttpResponse, HttpResponseServerError,
HttpResponseNotFound, HttpRequest, build_request_repr)
from django.template import Template, Context, TemplateDoesNotExist
diff --git a/django/views/decorators/csrf.py b/django/views/decorators/csrf.py
index a6bd7d8526..1ebed2a6a1 100644
--- a/django/views/decorators/csrf.py
+++ b/django/views/decorators/csrf.py
@@ -1,5 +1,3 @@
-import warnings
-
from django.middleware.csrf import CsrfViewMiddleware, get_token
from django.utils.decorators import decorator_from_middleware, available_attrs
from functools import wraps
diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py
index b31d7a218f..fccacf0bd3 100644
--- a/django/views/generic/edit.py
+++ b/django/views/generic/edit.py
@@ -17,6 +17,7 @@ class FormMixin(ContextMixin):
initial = {}
form_class = None
success_url = None
+ prefix = None
def get_initial(self):
"""
@@ -24,6 +25,12 @@ class FormMixin(ContextMixin):
"""
return self.initial.copy()
+ def get_prefix(self):
+ """
+ Returns the prefix to use for forms on this view
+ """
+ return self.prefix
+
def get_form_class(self):
"""
Returns the form class to use in this view
@@ -40,7 +47,11 @@ class FormMixin(ContextMixin):
"""
Returns the keyword arguments for instantiating the form.
"""
- kwargs = {'initial': self.get_initial()}
+ kwargs = {
+ 'initial': self.get_initial(),
+ 'prefix': self.get_prefix(),
+ }
+
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
@@ -78,6 +89,7 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
"""
A mixin that provides a way to show and handle a modelform in a request.
"""
+ fields = None
def get_form_class(self):
"""
@@ -98,13 +110,12 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
# from that
model = self.get_queryset().model
- fields = getattr(self, 'fields', None)
- if fields is None:
+ if self.fields is None:
warnings.warn("Using ModelFormMixin (base class of %s) without "
"the 'fields' attribute is deprecated." % self.__class__.__name__,
PendingDeprecationWarning)
- return model_forms.modelform_factory(model, fields=fields)
+ return model_forms.modelform_factory(model, fields=self.fields)
def get_form_kwargs(self):
"""