diff --git a/django/contrib/admin/forms.py b/django/contrib/admin/forms.py new file mode 100644 index 0000000000..5a245a39af --- /dev/null +++ b/django/contrib/admin/forms.py @@ -0,0 +1,43 @@ +from django import forms + +from django.contrib.auth import authenticate +from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.models import User + +from django.utils.translation import ugettext_lazy, ugettext as _ + +ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. " + "Note that both fields are case-sensitive.") + +class AdminAuthenticationForm(AuthenticationForm): + """ + A custom authentication form used in the admin app. + + """ + this_is_the_login_form = forms.BooleanField(widget=forms.HiddenInput, initial=1, + error_messages={'required': ugettext_lazy("Please log in again, because your session has expired.")}) + + def clean(self): + username = self.cleaned_data.get('username') + password = self.cleaned_data.get('password') + message = ERROR_MESSAGE + + if username and password: + self.user_cache = authenticate(username=username, password=password) + if self.user_cache is None: + if username is not None and u'@' in username: + # Mistakenly entered e-mail address instead of username? Look it up. + try: + user = User.objects.get(email=username) + except (User.DoesNotExist, User.MultipleObjectsReturned): + # Nothing to do here, moving along. + pass + else: + if user.check_password(password): + message = _("Your e-mail address is not your username." + " Try '%s' instead.") % user.username + raise forms.ValidationError(message) + elif not self.user_cache.is_active or not self.user_cache.is_staff: + raise forms.ValidationError(message) + self.check_for_test_cookie() + return self.cleaned_data diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index b995c0f2df..0aa730f79e 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -1,8 +1,8 @@ import re from django import http, template -from django.contrib.admin import ModelAdmin -from django.contrib.admin import actions -from django.contrib.auth import authenticate, login +from django.contrib.admin import ModelAdmin, actions +from django.contrib.admin.forms import AdminAuthenticationForm, ERROR_MESSAGE +from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login from django.views.decorators.csrf import csrf_protect from django.db.models.base import ModelBase from django.core.exceptions import ImproperlyConfigured @@ -15,7 +15,6 @@ from django.utils.translation import ugettext_lazy, ugettext as _ from django.views.decorators.cache import never_cache from django.conf import settings -ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") LOGIN_FORM_KEY = 'this_is_the_login_form' class AlreadyRegistered(Exception): @@ -32,7 +31,7 @@ class AdminSite(object): functions that present a full admin interface for the collection of registered models. """ - + login_form = None index_template = None app_index_template = None login_template = None @@ -127,12 +126,12 @@ class AdminSite(object): """ return self._global_actions[name] + @property def actions(self): """ Get all the enabled actions as an iterable of (name, func). """ return self._actions.iteritems() - actions = property(actions) def has_permission(self, request): """ @@ -240,9 +239,9 @@ class AdminSite(object): ) return urlpatterns + @property def urls(self): return self.get_urls(), self.app_name, self.name - urls = property(urls) def password_change(self, request): """ @@ -254,18 +253,22 @@ class AdminSite(object): else: url = reverse('admin:password_change_done', current_app=self.name) defaults = { + 'current_app': self.name, 'post_change_redirect': url } if self.password_change_template is not None: defaults['template_name'] = self.password_change_template return password_change(request, **defaults) - def password_change_done(self, request): + def password_change_done(self, request, extra_context=None): """ Displays the "success" page after a password change. """ from django.contrib.auth.views import password_change_done - defaults = {} + defaults = { + 'current_app': self.name, + 'extra_context': extra_context or {}, + } if self.password_change_done_template is not None: defaults['template_name'] = self.password_change_done_template return password_change_done(request, **defaults) @@ -283,69 +286,44 @@ class AdminSite(object): from django.views.i18n import null_javascript_catalog as javascript_catalog return javascript_catalog(request, packages='django.conf') - def logout(self, request): + @never_cache + def logout(self, request, extra_context=None): """ Logs out the user for the given HttpRequest. This should *not* assume the user is already logged in. """ from django.contrib.auth.views import logout - defaults = {} + defaults = { + 'current_app': self.name, + 'extra_context': extra_context or {}, + } if self.logout_template is not None: defaults['template_name'] = self.logout_template return logout(request, **defaults) - logout = never_cache(logout) - def login(self, request): + @never_cache + def login(self, request, extra_context=None): """ Displays the login form for the given HttpRequest. """ - from django.contrib.auth.models import User - - # If this isn't already the login page, display it. - if LOGIN_FORM_KEY not in request.POST: - if request.POST: - message = _("Please log in again, because your session has expired.") - else: - message = "" - return self.display_login_form(request, message) - - # Check that the user accepts cookies. - if not request.session.test_cookie_worked(): - message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.") - return self.display_login_form(request, message) - else: - request.session.delete_test_cookie() - - # Check the password. - username = request.POST.get('username', None) - password = request.POST.get('password', None) - user = authenticate(username=username, password=password) - if user is None: - message = ERROR_MESSAGE - if username is not None and u'@' in username: - # Mistakenly entered e-mail address instead of username? Look it up. - try: - user = User.objects.get(email=username) - except (User.DoesNotExist, User.MultipleObjectsReturned): - message = _("Usernames cannot contain the '@' character.") - else: - if user.check_password(password): - message = _("Your e-mail address is not your username." - " Try '%s' instead.") % user.username - else: - message = _("Usernames cannot contain the '@' character.") - return self.display_login_form(request, message) - - # The user data is correct; log in the user in and continue. - else: - if user.is_active and user.is_staff: - login(request, user) - return http.HttpResponseRedirect(request.get_full_path()) - else: - return self.display_login_form(request, ERROR_MESSAGE) - login = never_cache(login) + from django.contrib.auth.views import login + context = { + 'title': _('Log in'), + 'root_path': self.root_path, + 'app_path': request.get_full_path(), + REDIRECT_FIELD_NAME: request.get_full_path(), + } + context.update(extra_context or {}) + defaults = { + 'extra_context': context, + 'current_app': self.name, + 'authentication_form': self.login_form or AdminAuthenticationForm, + 'template_name': self.login_template or 'admin/login.html', + } + return login(request, **defaults) + @never_cache def index(self, request, extra_context=None): """ Displays the main admin index page, which lists all of the installed @@ -396,21 +374,6 @@ class AdminSite(object): return render_to_response(self.index_template or 'admin/index.html', context, context_instance=context_instance ) - index = never_cache(index) - - def display_login_form(self, request, error_message='', extra_context=None): - request.session.set_test_cookie() - context = { - 'title': _('Log in'), - 'app_path': request.get_full_path(), - 'error_message': error_message, - 'root_path': self.root_path, - } - context.update(extra_context or {}) - context_instance = template.RequestContext(request, current_app=self.name) - return render_to_response(self.login_template or 'admin/login.html', context, - context_instance=context_instance - ) def app_index(self, request, app_label, extra_context=None): user = request.user diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index df6212697a..ad7b14e965 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -12,17 +12,31 @@ {% block breadcrumbs %}{% endblock %} {% block content %} -{% if error_message %} -

{{ error_message }}

+{% if form.errors and not form.non_field_errors and not form.this_is_the_login_form.errors %} +

+{% blocktrans count form.errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} +

{% endif %} + +{% if form.non_field_errors or form.this_is_the_login_form.errors %} +{% for error in form.non_field_errors|add:form.this_is_the_login_form.errors %} +

+ {{ error }} +

+{% endfor %} +{% endif %} +
{% csrf_token %}
- + {% if not form.this_is_the_login_form.errors %}{{ form.username.errors }}{% endif %} + {{ form.username }}
- + {% if not form.this_is_the_login_form.errors %}{{ form.password.errors }}{% endif %} + {{ form.password }} +
diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py index 67f82edabc..1f5f20d605 100644 --- a/django/contrib/admin/views/decorators.py +++ b/django/contrib/admin/views/decorators.py @@ -3,22 +3,13 @@ try: except ImportError: from django.utils.functional import wraps # Python 2.4 fallback. -from django import http, template -from django.contrib.auth.models import User -from django.contrib.auth import authenticate, login +from django import template from django.shortcuts import render_to_response -from django.utils.translation import ugettext_lazy, ugettext as _ +from django.utils.translation import ugettext as _ -ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") -LOGIN_FORM_KEY = 'this_is_the_login_form' - -def _display_login_form(request, error_message=''): - request.session.set_test_cookie() - return render_to_response('admin/login.html', { - 'title': _('Log in'), - 'app_path': request.get_full_path(), - 'error_message': error_message - }, context_instance=template.RequestContext(request)) +from django.contrib.admin.forms import AdminAuthenticationForm +from django.contrib.auth.views import login +from django.contrib.auth import REDIRECT_FIELD_NAME def staff_member_required(view_func): """ @@ -31,45 +22,14 @@ def staff_member_required(view_func): return view_func(request, *args, **kwargs) assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." - - # If this isn't already the login page, display it. - if LOGIN_FORM_KEY not in request.POST: - if request.POST: - message = _("Please log in again, because your session has expired.") - else: - message = "" - return _display_login_form(request, message) - - # Check that the user accepts cookies. - if not request.session.test_cookie_worked(): - message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.") - return _display_login_form(request, message) - else: - request.session.delete_test_cookie() - - # Check the password. - username = request.POST.get('username', None) - password = request.POST.get('password', None) - user = authenticate(username=username, password=password) - if user is None: - message = ERROR_MESSAGE - if '@' in username: - # Mistakenly entered e-mail address instead of username? Look it up. - users = list(User.objects.filter(email=username)) - if len(users) == 1 and users[0].check_password(password): - message = _("Your e-mail address is not your username. Try '%s' instead.") % users[0].username - else: - # Either we cannot find the user, or if more than 1 - # we cannot guess which user is the correct one. - message = _("Usernames cannot contain the '@' character.") - return _display_login_form(request, message) - - # The user data is correct; log in the user in and continue. - else: - if user.is_active and user.is_staff: - login(request, user) - return http.HttpResponseRedirect(request.get_full_path()) - else: - return _display_login_form(request, ERROR_MESSAGE) - + defaults = { + 'template_name': 'admin/login.html', + 'authentication_form': AdminAuthenticationForm, + 'extra_context': { + 'title': _('Log in'), + 'app_path': request.get_full_path(), + REDIRECT_FIELD_NAME: request.get_full_path(), + }, + } + return login(request, **defaults) return wraps(view_func)(_checklogin) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index aa33640af0..74732799e7 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -87,14 +87,15 @@ class AuthenticationForm(forms.Form): raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive.")) elif not self.user_cache.is_active: raise forms.ValidationError(_("This account is inactive.")) - - # TODO: determine whether this should move to its own method. - if self.request: - if not self.request.session.test_cookie_worked(): - raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")) - + self.check_for_test_cookie() return self.cleaned_data + def check_for_test_cookie(self): + if self.request and not self.request.session.test_cookie_worked(): + raise forms.ValidationError( + _("Your Web browser doesn't appear to have cookies enabled. " + "Cookies are required for logging in.")) + def get_user_id(self): if self.user_cache: return self.user_cache.id diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 80e7e0a20c..7e4b1f33dc 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -24,7 +24,7 @@ from django.views.decorators.cache import never_cache def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME, authentication_form=AuthenticationForm, - extra_context=None): + current_app=None, extra_context=None): """Displays the login form and handles the login action.""" redirect_to = request.REQUEST.get(redirect_field_name, '') @@ -65,12 +65,12 @@ def login(request, template_name='registration/login.html', } context.update(extra_context or {}) return render_to_response(template_name, context, - context_instance=RequestContext(request)) + context_instance=RequestContext(request, current_app=current_app)) def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME, - extra_context=None): + current_app=None, extra_context=None): "Logs out the user and displays 'You are logged out' message." from django.contrib.auth import logout logout(request) @@ -87,16 +87,16 @@ def logout(request, next_page=None, } context.update(extra_context or {}) return render_to_response(template_name, context, - context_instance=RequestContext(request)) + context_instance=RequestContext(request, current_app=current_app)) else: # Redirect to this page until the session has been cleared. return HttpResponseRedirect(next_page or request.path) -def logout_then_login(request, login_url=None, extra_context=None): +def logout_then_login(request, login_url=None, current_app=None, extra_context=None): "Logs out the user if he is logged in. Then redirects to the log-in page." if not login_url: login_url = settings.LOGIN_URL - return logout(request, login_url, extra_context=extra_context) + return logout(request, login_url, current_app=current_app, extra_context=extra_context) def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): @@ -127,6 +127,7 @@ def password_reset(request, is_admin_site=False, token_generator=default_token_generator, post_reset_redirect=None, from_email=None, + current_app=None, extra_context=None): if post_reset_redirect is None: post_reset_redirect = reverse('django.contrib.auth.views.password_reset_done') @@ -151,15 +152,15 @@ def password_reset(request, is_admin_site=False, } context.update(extra_context or {}) return render_to_response(template_name, context, - context_instance=RequestContext(request)) + context_instance=RequestContext(request, current_app=current_app)) def password_reset_done(request, template_name='registration/password_reset_done.html', - extra_context=None): + current_app=None, extra_context=None): context = {} context.update(extra_context or {}) return render_to_response(template_name, context, - context_instance=RequestContext(request)) + context_instance=RequestContext(request, current_app=current_app)) # Doesn't need csrf_protect since no-one can guess the URL def password_reset_confirm(request, uidb36=None, token=None, @@ -167,7 +168,7 @@ def password_reset_confirm(request, uidb36=None, token=None, token_generator=default_token_generator, set_password_form=SetPasswordForm, post_reset_redirect=None, - extra_context=None): + current_app=None, extra_context=None): """ View that checks the hash in a password reset link and presents a form for entering a new password. @@ -199,17 +200,17 @@ def password_reset_confirm(request, uidb36=None, token=None, } context.update(extra_context or {}) return render_to_response(template_name, context, - context_instance=RequestContext(request)) + context_instance=RequestContext(request, current_app=current_app)) def password_reset_complete(request, template_name='registration/password_reset_complete.html', - extra_context=None): + current_app=None, extra_context=None): context = { 'login_url': settings.LOGIN_URL } context.update(extra_context or {}) return render_to_response(template_name, context, - context_instance=RequestContext(request)) + context_instance=RequestContext(request, current_app=current_app)) @csrf_protect @login_required @@ -217,7 +218,7 @@ def password_change(request, template_name='registration/password_change_form.html', post_change_redirect=None, password_change_form=PasswordChangeForm, - extra_context=None): + current_app=None, extra_context=None): if post_change_redirect is None: post_change_redirect = reverse('django.contrib.auth.views.password_change_done') if request.method == "POST": @@ -232,12 +233,12 @@ def password_change(request, } context.update(extra_context or {}) return render_to_response(template_name, context, - context_instance=RequestContext(request)) + context_instance=RequestContext(request, current_app=current_app)) def password_change_done(request, template_name='registration/password_change_done.html', - extra_context=None): + current_app=None, extra_context=None): context = {} context.update(extra_context or {}) return render_to_response(template_name, context, - context_instance=RequestContext(request)) + context_instance=RequestContext(request, current_app=current_app)) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 89478df65b..15a5ba2c55 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1459,6 +1459,13 @@ Path to a custom template that will be used by the admin site main index view. Path to a custom template that will be used by the admin site login view. +.. versionadded:: 1.3 + +.. attribute:: AdminSite.login_form + +Subclass of :class:`~django.contrib.auth.forms.AuthenticationForm` that will +be used by the admin site login view. + .. attribute:: AdminSite.logout_template .. versionadded:: 1.2 diff --git a/docs/releases/1.3-alpha-2.txt b/docs/releases/1.3-alpha-2.txt index 3403c6e8d9..0572bc14df 100644 --- a/docs/releases/1.3-alpha-2.txt +++ b/docs/releases/1.3-alpha-2.txt @@ -95,6 +95,23 @@ As a result, we took the following steps to rectify the issue: **if the value is not None**, and falls back to the previously used :setting:`MEDIA_URL` setting otherwise. +Changes to the login methods of the admin +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In previous version the admin app defined login methods in multiple locations +and ignored the almost identical implementation in the already used auth app. +A side effect of this duplication was the missing adoption of the changes made +in r12634_ to support a broader set of characters for usernames. + +This release refactores the admin's login mechanism to use a subclass of the +:class:`~django.contrib.auth.forms.AuthenticationForm` instead of a manual +form validation. The previously undocumented method +``'django.contrib.admin.sites.AdminSite.display_login_form'`` has been removed +in favor of a new :attr:`~django.contrib.admin.AdminSite.login_form` +attribute. + +.. _r12634: http://code.djangoproject.com/changeset/12634 + The Django 1.3 roadmap ====================== diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 1dc6870496..4e01fca972 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -424,3 +424,20 @@ Django 1.5, the old behavior will be replaced with the new behavior. To ensure compatibility with future versions of Django, existing templates should be modified to use the new ``future`` libraries and syntax. + +Changes to the login methods of the admin +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In previous version the admin app defined login methods in multiple locations +and ignored the almost identical implementation in the already used auth app. +A side effect of this duplication was the missing adoption of the changes made +in r12634_ to support a broader set of characters for usernames. + +This release refactores the admin's login mechanism to use a subclass of the +:class:`~django.contrib.auth.forms.AuthenticationForm` instead of a manual +form validation. The previously undocumented method +``'django.contrib.admin.sites.AdminSite.display_login_form'`` has been removed +in favor of a new :attr:`~django.contrib.admin.AdminSite.login_form` +attribute. + +.. _r12634: http://code.djangoproject.com/changeset/12634 diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/regressiontests/admin_views/customadmin.py index 8cd262b860..c31d3794cd 100644 --- a/tests/regressiontests/admin_views/customadmin.py +++ b/tests/regressiontests/admin_views/customadmin.py @@ -5,9 +5,10 @@ from django.conf.urls.defaults import patterns from django.contrib import admin from django.http import HttpResponse -import models +import models, forms class Admin2(admin.AdminSite): + login_form = forms.CustomAdminAuthenticationForm login_template = 'custom_admin/login.html' logout_template = 'custom_admin/logout.html' index_template = 'custom_admin/index.html' diff --git a/tests/regressiontests/admin_views/forms.py b/tests/regressiontests/admin_views/forms.py new file mode 100644 index 0000000000..a030c2a8d0 --- /dev/null +++ b/tests/regressiontests/admin_views/forms.py @@ -0,0 +1,10 @@ +from django import forms +from django.contrib.admin.forms import AdminAuthenticationForm + +class CustomAdminAuthenticationForm(AdminAuthenticationForm): + + def clean_username(self): + username = self.cleaned_data.get('username') + if username == 'customform': + raise forms.ValidationError('custom form error') + return username diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 79a29d8ab0..6b4f65b497 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -5,7 +5,7 @@ import datetime from django.conf import settings from django.core import mail from django.core.files import temp as tempfile -from django.contrib.auth import admin # Register auth models with the admin. +from django.contrib.auth import REDIRECT_FIELD_NAME, admin # Register auth models with the admin. from django.contrib.auth.models import User, Permission, UNUSABLE_PASSWORD from django.contrib.contenttypes.models import ContentType from django.contrib.admin.models import LogEntry, DELETION @@ -377,6 +377,19 @@ class SaveAsTests(TestCase): class CustomModelAdminTest(AdminViewBasicTest): urlbit = "admin2" + def testCustomAdminSiteLoginForm(self): + self.client.logout() + request = self.client.get('/test_admin/admin2/') + self.failUnlessEqual(request.status_code, 200) + login = self.client.post('/test_admin/admin2/', { + REDIRECT_FIELD_NAME: '/test_admin/admin2/', + LOGIN_FORM_KEY: 1, + 'username': 'customform', + 'password': 'secret', + }) + self.failUnlessEqual(login.status_code, 200) + self.assertContains(login, 'custom form error') + def testCustomAdminSiteLoginTemplate(self): self.client.logout() request = self.client.get('/test_admin/admin2/') @@ -446,36 +459,52 @@ class AdminViewPermissionsTest(TestCase): # login POST dicts self.super_login = { - LOGIN_FORM_KEY: 1, - 'username': 'super', - 'password': 'secret'} + REDIRECT_FIELD_NAME: '/test_admin/admin/', + LOGIN_FORM_KEY: 1, + 'username': 'super', + 'password': 'secret', + } self.super_email_login = { - LOGIN_FORM_KEY: 1, - 'username': 'super@example.com', - 'password': 'secret'} + REDIRECT_FIELD_NAME: '/test_admin/admin/', + LOGIN_FORM_KEY: 1, + 'username': 'super@example.com', + 'password': 'secret', + } self.super_email_bad_login = { - LOGIN_FORM_KEY: 1, - 'username': 'super@example.com', - 'password': 'notsecret'} + REDIRECT_FIELD_NAME: '/test_admin/admin/', + LOGIN_FORM_KEY: 1, + 'username': 'super@example.com', + 'password': 'notsecret', + } self.adduser_login = { - LOGIN_FORM_KEY: 1, - 'username': 'adduser', - 'password': 'secret'} + REDIRECT_FIELD_NAME: '/test_admin/admin/', + LOGIN_FORM_KEY: 1, + 'username': 'adduser', + 'password': 'secret', + } self.changeuser_login = { - LOGIN_FORM_KEY: 1, - 'username': 'changeuser', - 'password': 'secret'} + REDIRECT_FIELD_NAME: '/test_admin/admin/', + LOGIN_FORM_KEY: 1, + 'username': 'changeuser', + 'password': 'secret', + } self.deleteuser_login = { - LOGIN_FORM_KEY: 1, - 'username': 'deleteuser', - 'password': 'secret'} + REDIRECT_FIELD_NAME: '/test_admin/admin/', + LOGIN_FORM_KEY: 1, + 'username': 'deleteuser', + 'password': 'secret', + } self.joepublic_login = { - LOGIN_FORM_KEY: 1, - 'username': 'joepublic', - 'password': 'secret'} + REDIRECT_FIELD_NAME: '/test_admin/admin/', + LOGIN_FORM_KEY: 1, + 'username': 'joepublic', + 'password': 'secret', + } self.no_username_login = { - LOGIN_FORM_KEY: 1, - 'password': 'secret'} + REDIRECT_FIELD_NAME: '/test_admin/admin/', + LOGIN_FORM_KEY: 1, + 'password': 'secret', + } def testLogin(self): """ @@ -500,12 +529,12 @@ class AdminViewPermissionsTest(TestCase): self.assertContains(login, "Your e-mail address is not your username") # only correct passwords get a username hint login = self.client.post('/test_admin/admin/', self.super_email_bad_login) - self.assertContains(login, "Usernames cannot contain the '@' character") + self.assertContains(login, "Please enter a correct username and password.") new_user = User(username='jondoe', password='secret', email='super@example.com') new_user.save() # check to ensure if there are multiple e-mail addresses a user doesn't get a 500 login = self.client.post('/test_admin/admin/', self.super_email_login) - self.assertContains(login, "Usernames cannot contain the '@' character") + self.assertContains(login, "Please enter a correct username and password.") # Add User request = self.client.get('/test_admin/admin/') @@ -536,23 +565,24 @@ class AdminViewPermissionsTest(TestCase): self.failUnlessEqual(request.status_code, 200) login = self.client.post('/test_admin/admin/', self.joepublic_login) self.failUnlessEqual(login.status_code, 200) - # Login.context is a list of context dicts we just need to check the first one. - self.assert_(login.context[0].get('error_message')) + self.assertContains(login, "Please enter a correct username and password.") # Requests without username should not return 500 errors. request = self.client.get('/test_admin/admin/') self.failUnlessEqual(request.status_code, 200) login = self.client.post('/test_admin/admin/', self.no_username_login) self.failUnlessEqual(login.status_code, 200) - # Login.context is a list of context dicts we just need to check the first one. - self.assert_(login.context[0].get('error_message')) + form = login.context[0].get('form') + self.failUnlessEqual(form.errors['username'][0], 'This field is required.') def testLoginSuccessfullyRedirectsToOriginalUrl(self): request = self.client.get('/test_admin/admin/') self.failUnlessEqual(request.status_code, 200) - query_string = "the-answer=42" - login = self.client.post('/test_admin/admin/', self.super_login, QUERY_STRING = query_string ) - self.assertRedirects(login, '/test_admin/admin/?%s' % query_string) + query_string = 'the-answer=42' + redirect_url = '/test_admin/admin/?%s' % query_string + new_next = {REDIRECT_FIELD_NAME: redirect_url} + login = self.client.post('/test_admin/admin/', dict(self.super_login, **new_next), QUERY_STRING=query_string) + self.assertRedirects(login, redirect_url) def testAddView(self): """Test add view restricts access and actually adds items.""" @@ -967,33 +997,47 @@ class SecureViewTest(TestCase): def setUp(self): # login POST dicts self.super_login = { - LOGIN_FORM_KEY: 1, - 'username': 'super', - 'password': 'secret'} + LOGIN_FORM_KEY: 1, + REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', + 'username': 'super', + 'password': 'secret', + } self.super_email_login = { - LOGIN_FORM_KEY: 1, - 'username': 'super@example.com', - 'password': 'secret'} + LOGIN_FORM_KEY: 1, + REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', + 'username': 'super@example.com', + 'password': 'secret', + } self.super_email_bad_login = { - LOGIN_FORM_KEY: 1, - 'username': 'super@example.com', - 'password': 'notsecret'} + LOGIN_FORM_KEY: 1, + REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', + 'username': 'super@example.com', + 'password': 'notsecret', + } self.adduser_login = { - LOGIN_FORM_KEY: 1, - 'username': 'adduser', - 'password': 'secret'} + LOGIN_FORM_KEY: 1, + REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', + 'username': 'adduser', + 'password': 'secret', + } self.changeuser_login = { - LOGIN_FORM_KEY: 1, - 'username': 'changeuser', - 'password': 'secret'} + LOGIN_FORM_KEY: 1, + REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', + 'username': 'changeuser', + 'password': 'secret', + } self.deleteuser_login = { - LOGIN_FORM_KEY: 1, - 'username': 'deleteuser', - 'password': 'secret'} + LOGIN_FORM_KEY: 1, + REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', + 'username': 'deleteuser', + 'password': 'secret', + } self.joepublic_login = { - LOGIN_FORM_KEY: 1, - 'username': 'joepublic', - 'password': 'secret'} + LOGIN_FORM_KEY: 1, + REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/', + 'username': 'joepublic', + 'password': 'secret', + } def tearDown(self): self.client.logout() @@ -1006,9 +1050,11 @@ class SecureViewTest(TestCase): def test_secure_view_login_successfully_redirects_to_original_url(self): request = self.client.get('/test_admin/admin/secure-view/') self.failUnlessEqual(request.status_code, 200) - query_string = "the-answer=42" - login = self.client.post('/test_admin/admin/secure-view/', self.super_login, QUERY_STRING = query_string ) - self.assertRedirects(login, '/test_admin/admin/secure-view/?%s' % query_string) + query_string = 'the-answer=42' + redirect_url = '/test_admin/admin/secure-view/?%s' % query_string + new_next = {REDIRECT_FIELD_NAME: redirect_url} + login = self.client.post('/test_admin/admin/secure-view/', dict(self.super_login, **new_next), QUERY_STRING=query_string) + self.assertRedirects(login, redirect_url) def test_staff_member_required_decorator_works_as_per_admin_login(self): """ @@ -1035,12 +1081,12 @@ class SecureViewTest(TestCase): self.assertContains(login, "Your e-mail address is not your username") # only correct passwords get a username hint login = self.client.post('/test_admin/admin/secure-view/', self.super_email_bad_login) - self.assertContains(login, "Usernames cannot contain the '@' character") + self.assertContains(login, "Please enter a correct username and password.") new_user = User(username='jondoe', password='secret', email='super@example.com') new_user.save() # check to ensure if there are multiple e-mail addresses a user doesn't get a 500 login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login) - self.assertContains(login, "Usernames cannot contain the '@' character") + self.assertContains(login, "Please enter a correct username and password.") # Add User request = self.client.get('/test_admin/admin/secure-view/') @@ -1072,7 +1118,7 @@ class SecureViewTest(TestCase): login = self.client.post('/test_admin/admin/secure-view/', self.joepublic_login) self.failUnlessEqual(login.status_code, 200) # Login.context is a list of context dicts we just need to check the first one. - self.assert_(login.context[0].get('error_message')) + self.assertContains(login, "Please enter a correct username and password.") # 8509 - if a normal user is already logged in, it is possible # to change user into the superuser without error