From 36c7beca84fd8b2ec9ccbba462c6e460b3d82d52 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Fri, 5 Aug 2011 23:36:00 -0400 Subject: [PATCH] Added new setting, widget, form and auth backend to allow login via user email address To enable: AUTHENTICATION_BACKENDS = ('common.auth.email_auth_backend.EmailAuthBackend',) COMMON_LOGIN_METHOD = 'email' --- apps/common/auth/__init__.py | 0 apps/common/auth/email_auth_backend.py | 23 +++++++++++++++++++ apps/common/conf/settings.py | 9 ++++++++ apps/common/forms.py | 31 +++++++++++++++++++++++++- apps/common/urls.py | 7 +++--- apps/common/views.py | 14 ++++++++++++ apps/common/widgets.py | 13 +++++++++++ 7 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 apps/common/auth/__init__.py create mode 100644 apps/common/auth/email_auth_backend.py diff --git a/apps/common/auth/__init__.py b/apps/common/auth/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/common/auth/email_auth_backend.py b/apps/common/auth/email_auth_backend.py new file mode 100644 index 0000000000..016634283e --- /dev/null +++ b/apps/common/auth/email_auth_backend.py @@ -0,0 +1,23 @@ +# From: http://www.micahcarrick.com/django-email-authentication.html +from django.contrib.auth.models import User, check_password +from django.contrib.auth.backends import ModelBackend + + +class EmailAuthBackend(ModelBackend): + """ + Email Authentication Backend + + Allows a user to sign in using an email/password pair rather than + a username/password pair. + """ + + def authenticate(self, email=None, password=None): + """ + Authenticate a user based on email address as the user name. + """ + try: + user = User.objects.get(email=email) + if user.check_password(password): + return user + except User.DoesNotExist: + return None diff --git a/apps/common/conf/settings.py b/apps/common/conf/settings.py index b1ed61dead..14640aeb2e 100644 --- a/apps/common/conf/settings.py +++ b/apps/common/conf/settings.py @@ -55,3 +55,12 @@ register_setting( global_name=u'COMMON_AUTO_ADMIN_PASSWORD', default=u'admin', ) + +register_setting( + namespace=u'common', + module=u'common.conf.settings', + name=u'LOGIN_METHOD', + global_name=u'COMMON_LOGIN_METHOD', + default=u'username', + description=_(u'Controls the mechanism used to authenticated user. Options are: username, email'), +) diff --git a/apps/common/forms.py b/apps/common/forms.py index e3d669113a..08941e3111 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -2,9 +2,12 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from django.db import models from django.contrib.auth.models import User +from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth import authenticate from common.utils import return_attrib -from common.widgets import DetailSelectMultiple, PlainWidget, TextAreaDiv +from common.widgets import DetailSelectMultiple, PlainWidget, \ + TextAreaDiv, EmailInput class DetailForm(forms.ModelForm): @@ -115,3 +118,29 @@ class UserForm(forms.ModelForm): class Meta: model = User fields = ('first_name', 'last_name') + + +class EmailAuthenticationForm(AuthenticationForm): + """ + Override the default authentication form to use email address + authentication + """ + email = forms.CharField(label=_(u'Email'), max_length=75, + widget=EmailInput() + ) + + def clean(self): + email = self.cleaned_data.get('email') + password = self.cleaned_data.get('password') + + if email and password: + self.user_cache = authenticate(email=email, password=password) + if self.user_cache is None: + raise forms.ValidationError(_('Please enter a correct email and password. Note that the password fields is case-sensitive.')) + elif not self.user_cache.is_active: + raise forms.ValidationError(_('This account is inactive.')) + self.check_for_test_cookie() + return self.cleaned_data + +# Remove the inherited username field +EmailAuthenticationForm.base_fields.keyOrder = ['email', 'password'] diff --git a/apps/common/urls.py b/apps/common/urls.py index 82aee3d29d..e21fd4b68f 100644 --- a/apps/common/urls.py +++ b/apps/common/urls.py @@ -2,23 +2,22 @@ from django.conf.urls.defaults import patterns, url from django.views.generic.simple import direct_to_template from django.conf import settings - urlpatterns = patterns('common.views', url(r'^about/$', direct_to_template, {'template': 'about.html'}, 'about'), + #url(r'^password/change/done/$', 'django.contrib.auth.views.password_change_done', {'template_name': 'password_change_done.html'}), url(r'^password/change/done/$', 'password_change_done', (), name='password_change_done'), url(r'^object/multiple/action/$', 'multi_object_action_view', (), name='multi_object_action_view'), url(r'^user/$', 'current_user_details', (), 'current_user_details'), url(r'^user/edit/$', 'current_user_edit', (), 'current_user_edit'), + + url(r'^login/$', 'login_view', (), name='login_view'), ) urlpatterns += patterns('', - url(r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name='login_view'), url(r'^logout/$', 'django.contrib.auth.views.logout', {'next_page': '/'}, name='logout_view'), url(r'^password/change/$', 'django.contrib.auth.views.password_change', {'template_name': 'password_change_form.html', 'post_change_redirect': '/password/change/done/'}, name='password_change_view'), - #url(r'^password/change/done/$', 'django.contrib.auth.views.password_change_done', {'template_name': 'password_change_done.html'}), - url(r'^password/reset/$', 'django.contrib.auth.views.password_reset', {'email_template_name': 'password_reset_email.html', 'template_name': 'password_reset_form.html', 'post_reset_redirect': '/password/reset/done'}, name='password_reset_view'), url(r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', 'django.contrib.auth.views.password_reset_confirm', {'template_name': 'password_reset_confirm.html', 'post_reset_redirect': '/password/reset/complete/'}, name='password_reset_confirm_view'), url(r'^password/reset/complete/$', 'django.contrib.auth.views.password_reset_complete', {'template_name': 'password_reset_complete.html'}, name='password_reset_complete_view'), diff --git a/apps/common/views.py b/apps/common/views.py index 7f9203fb21..c1901500ee 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -7,8 +7,11 @@ from django.contrib import messages from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.utils.http import urlencode +from django.contrib.auth.views import login from common.forms import ChoiceForm, UserForm, UserForm_view +from common.forms import EmailAuthenticationForm +from common.conf.settings import LOGIN_METHOD def password_change_done(request): @@ -167,3 +170,14 @@ def current_user_edit(request): 'title': _(u'edit current user details'), }, context_instance=RequestContext(request)) + + +def login_view(request): + #url(r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name='login_view'), + #url(r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html', 'authentication_form': EmailAuthenticationForm}, name='login_view'), + kwargs = {'template_name': 'login.html'} + + if LOGIN_METHOD == 'email': + kwargs['authentication_form'] = EmailAuthenticationForm + + return login(request, **kwargs) diff --git a/apps/common/widgets.py b/apps/common/widgets.py index 5347e11954..1f56f3e409 100644 --- a/apps/common/widgets.py +++ b/apps/common/widgets.py @@ -83,3 +83,16 @@ class TextAreaDiv(forms.widgets.Widget): conditional_escape(force_unicode(value)))) return mark_safe(result.replace('\n', '
')) + + +# From: http://www.peterbe.com/plog/emailinput-html5-django +class EmailInput(forms.widgets.Input): + input_type = 'email' + + def render(self, name, value, attrs=None): + if attrs is None: + attrs = {} + attrs.update(dict(autocorrect='off', + autocapitalize='off', + spellcheck='false')) + return super(EmailInput, self).render(name, value, attrs=attrs)