mirror of https://github.com/django/django.git
[1.8.x] Fixed #24334 -- Allowed admin password reset to work with non-digit custom user model primary key.
Thanks Loic for help and Simon for review.
Backport of fdf20093e0
from master
This commit is contained in:
parent
bd80fa6b0f
commit
8fc4840289
|
@ -2,6 +2,7 @@ from django.conf import settings
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.admin.options import IS_POPUP_VAR
|
from django.contrib.admin.options import IS_POPUP_VAR
|
||||||
|
from django.contrib.admin.utils import unquote
|
||||||
from django.contrib.auth import update_session_auth_hash
|
from django.contrib.auth import update_session_auth_hash
|
||||||
from django.contrib.auth.forms import (
|
from django.contrib.auth.forms import (
|
||||||
AdminPasswordChangeForm, UserChangeForm, UserCreationForm,
|
AdminPasswordChangeForm, UserChangeForm, UserCreationForm,
|
||||||
|
@ -10,9 +11,9 @@ from django.contrib.auth.models import Group, User
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.utils.encoding import force_text
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
from django.views.decorators.csrf import csrf_protect
|
from django.views.decorators.csrf import csrf_protect
|
||||||
|
@ -79,7 +80,7 @@ class UserAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
return [
|
return [
|
||||||
url(r'^(\d+)/password/$', self.admin_site.admin_view(self.user_change_password), name='auth_user_password_change'),
|
url(r'^(.+)/password/$', self.admin_site.admin_view(self.user_change_password), name='auth_user_password_change'),
|
||||||
] + super(UserAdmin, self).get_urls()
|
] + super(UserAdmin, self).get_urls()
|
||||||
|
|
||||||
def lookup_allowed(self, lookup, value):
|
def lookup_allowed(self, lookup, value):
|
||||||
|
@ -123,7 +124,12 @@ class UserAdmin(admin.ModelAdmin):
|
||||||
def user_change_password(self, request, id, form_url=''):
|
def user_change_password(self, request, id, form_url=''):
|
||||||
if not self.has_change_permission(request):
|
if not self.has_change_permission(request):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
user = get_object_or_404(self.get_queryset(request), pk=id)
|
user = self.get_object(request, unquote(id))
|
||||||
|
if user is None:
|
||||||
|
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {
|
||||||
|
'name': force_text(self.model._meta.verbose_name),
|
||||||
|
'key': escape(id),
|
||||||
|
})
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = self.change_password_form(user, request.POST)
|
form = self.change_password_form(user, request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
|
@ -18,6 +18,7 @@ from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.contrib.sites.requests import RequestSite
|
from django.contrib.sites.requests import RequestSite
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.urlresolvers import NoReverseMatch, reverse, reverse_lazy
|
from django.core.urlresolvers import NoReverseMatch, reverse, reverse_lazy
|
||||||
|
from django.db import connection
|
||||||
from django.http import HttpRequest, QueryDict
|
from django.http import HttpRequest, QueryDict
|
||||||
from django.middleware.csrf import CsrfViewMiddleware
|
from django.middleware.csrf import CsrfViewMiddleware
|
||||||
from django.test import (
|
from django.test import (
|
||||||
|
@ -30,6 +31,7 @@ from django.utils.http import urlquote
|
||||||
from django.utils.six.moves.urllib.parse import ParseResult, urlparse
|
from django.utils.six.moves.urllib.parse import ParseResult, urlparse
|
||||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||||
|
|
||||||
|
from .models import UUIDUser
|
||||||
from .settings import AUTH_TEMPLATES
|
from .settings import AUTH_TEMPLATES
|
||||||
|
|
||||||
|
|
||||||
|
@ -892,3 +894,38 @@ class ChangelistTests(AuthViewsTestCase):
|
||||||
self.assertEqual(row.user_id, self.admin.pk)
|
self.assertEqual(row.user_id, self.admin.pk)
|
||||||
self.assertEqual(row.object_id, str(u.pk))
|
self.assertEqual(row.object_id, str(u.pk))
|
||||||
self.assertEqual(row.change_message, 'Changed password.')
|
self.assertEqual(row.change_message, 'Changed password.')
|
||||||
|
|
||||||
|
def test_password_change_bad_url(self):
|
||||||
|
response = self.client.get(reverse('auth_test_admin:auth_user_password_change', args=('foobar',)))
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
AUTH_USER_MODEL='auth.UUIDUser',
|
||||||
|
ROOT_URLCONF='auth_tests.urls_custom_user_admin',
|
||||||
|
)
|
||||||
|
class UUIDUserTests(TestCase):
|
||||||
|
|
||||||
|
def test_admin_password_change(self):
|
||||||
|
u = UUIDUser.objects.create_superuser(username='uuid', email='foo@bar.com', password='test')
|
||||||
|
self.assertTrue(self.client.login(username='uuid', password='test'))
|
||||||
|
|
||||||
|
user_change_url = reverse('custom_user_admin:auth_uuiduser_change', args=(u.pk,))
|
||||||
|
response = self.client.get(user_change_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
password_change_url = reverse('custom_user_admin:auth_user_password_change', args=(u.pk,))
|
||||||
|
response = self.client.get(password_change_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# A LogEntry is created with pk=1 which breaks a FK constraint on MySQL
|
||||||
|
with connection.constraint_checks_disabled():
|
||||||
|
response = self.client.post(password_change_url, {
|
||||||
|
'password1': 'password1',
|
||||||
|
'password2': 'password1',
|
||||||
|
})
|
||||||
|
self.assertRedirects(response, user_change_url)
|
||||||
|
row = LogEntry.objects.latest('id')
|
||||||
|
self.assertEqual(row.user_id, 1) # harcoded in CustomUserAdmin.log_change()
|
||||||
|
self.assertEqual(row.object_id, str(u.pk))
|
||||||
|
self.assertEqual(row.change_message, 'Changed password.')
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
from django.contrib.auth.urls import urlpatterns
|
||||||
|
|
||||||
|
site = admin.AdminSite(name='custom_user_admin')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserAdmin(UserAdmin):
|
||||||
|
def log_change(self, request, object, message):
|
||||||
|
# LogEntry.user column doesn't get altered to expect a UUID, so set an
|
||||||
|
# integer manually to avoid causing an error.
|
||||||
|
request.user.pk = 1
|
||||||
|
super(CustomUserAdmin, self).log_change(request, object, message)
|
||||||
|
|
||||||
|
site.register(get_user_model(), CustomUserAdmin)
|
||||||
|
|
||||||
|
urlpatterns += [
|
||||||
|
url(r'^admin/', include(site.urls)),
|
||||||
|
]
|
Loading…
Reference in New Issue