Accepting email address as username in Django

PythonDjangoAuthentication

Python Problem Overview


Is there a good way to do this in Django without rolling my own authentication system? I want the username to be the user's email address instead of them creating a username.

Please advise

Python Solutions


Solution 1 - Python

For anyone else wanting to do this, I'd recommend taking a look at django-email-as-username which is a pretty comprehensive solution, that includes patching up the admin and the createsuperuser management commands, amongst other bits and pieces.

Edit: As of Django 1.5 onwards you should consider using a custom user model instead of django-email-as-username.

Solution 2 - Python

Here's what we do. It isn't a "complete" solution, but it does much of what you're looking for.

from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        exclude = ('email',)
    username = forms.EmailField(max_length=64,
                                help_text="The person's email address.")
    def clean_email(self):
        email = self.cleaned_data['username']
        return email

class UserAdmin(UserAdmin):
    form = UserForm
    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff',)
    search_fields = ('email',)

admin.site.unregister(User)
admin.site.register(User, UserAdmin)

Solution 3 - Python

Here is one way to do it so that both username and email are accepted:

from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.forms import ValidationError

class EmailAuthenticationForm(AuthenticationForm):
    def clean_username(self):
        username = self.data['username']
        if '@' in username:
            try:
                username = User.objects.get(email=username).username
            except ObjectDoesNotExist:
                raise ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username':self.username_field.verbose_name},
                )
        return username

Don't know if there is some setting to set the default Authentication form but you can also override the url in urls.py

url(r'^accounts/login/$', 'django.contrib.auth.views.login', { 'authentication_form': EmailAuthenticationForm }, name='login'),

Raising the ValidationError will prevent 500 errors when an invalid email is submitted. Using the super's definition for "invalid_login" keeps the error message ambiguous (vs a specific "no user by that email found") which would be required to prevent leaking whether an email address is signed up for an account on your service. If that information is not secure in your architecture it might be friendlier to have a more informative error message.

Solution 4 - Python

Django now provides a full example of an extended authentication system with admin and form: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#a-full-example

You can basically copy/paste it and adapt (I didn't need the date_of_birth in my case).

It is actually available since Django 1.5 and is still available as of now (django 1.7).

Solution 5 - Python

If you're going to extend user model, you will have to implement custom user model anyway.

Here is an example for Django 1.8. Django 1.7 would require a little bit more work, mostly changing default forms (just take a look at UserChangeForm & UserCreationForm in django.contrib.auth.forms - that's what you need in 1.7).

user_manager.py:

from django.contrib.auth.models import BaseUserManager
from django.utils import timezone

class SiteUserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        today = timezone.now()

        if not email:
            raise ValueError('The given email address must be set')

        email = SiteUserManager.normalize_email(email)
        user  = self.model(email=email,
                          is_staff=False, is_active=True, **extra_fields)
 
        user.set_password(password)
        user.save(using=self._db)
        return user
 
    def create_superuser(self, email, password, **extra_fields):
        u = self.create_user(email, password, **extra_fields)
        u.is_staff = True
        u.is_active = True
        u.is_superuser = True
        u.save(using=self._db)
        return u

models.py:

from mainsite.user_manager import SiteUserManager

from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin

class SiteUser(AbstractBaseUser, PermissionsMixin):
    email    = models.EmailField(unique=True, blank=False)

    is_active   = models.BooleanField(default=True)
    is_admin    = models.BooleanField(default=False)
    is_staff    = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'

    objects = SiteUserManager()

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

forms.py:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from mainsite.models import SiteUser

class MyUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = SiteUser
        fields = ("email",)


class MyUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = SiteUser


class MyUserAdmin(UserAdmin):
    form = MyUserChangeForm
    add_form = MyUserCreationForm

    fieldsets = (
        (None,              {'fields': ('email', 'password',)}),
        ('Permissions',     {'fields': ('is_active', 'is_staff', 'is_superuser',)}),  
        ('Groups',          {'fields': ('groups', 'user_permissions',)}),
    )

    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2')}
        ),
    )

    list_display = ('email', )       
    list_filter = ('is_active', )    
    search_fields = ('email',)       
    ordering = ('email',)


admin.site.register(SiteUser, MyUserAdmin)

settings.py:

AUTH_USER_MODEL = 'mainsite.SiteUser'

Solution 6 - Python

Other alternatives look too complex for me, so I wrote a snippet that allows to authenticate using username, email, or both, and also enable or disable case sensitive. I uploaded it to pip as django-dual-authentication.

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.conf import settings

###################################
"""  DEFAULT SETTINGS + ALIAS   """
###################################


try:
    am = settings.AUTHENTICATION_METHOD
except:
    am = 'both'
try:
    cs = settings.AUTHENTICATION_CASE_SENSITIVE
except:
    cs = 'both'

#####################
"""   EXCEPTIONS  """
#####################


VALID_AM = ['username', 'email', 'both']
VALID_CS = ['username', 'email', 'both', 'none']

if (am not in VALID_AM):
    raise Exception("Invalid value for AUTHENTICATION_METHOD in project "
                    "settings. Use 'username','email', or 'both'.")

if (cs not in VALID_CS):
    raise Exception("Invalid value for AUTHENTICATION_CASE_SENSITIVE in project "
                    "settings. Use 'username','email', 'both' or 'none'.")

############################
"""  OVERRIDDEN METHODS  """
############################


class DualAuthentication(ModelBackend):
    """
    This is a ModelBacked that allows authentication
    with either a username or an email address.
    """

    def authenticate(self, username=None, password=None):
        UserModel = get_user_model()
        try:
            if ((am == 'email') or (am == 'both')):
                if ((cs == 'email') or cs == 'both'):
                    kwargs = {'email': username}
                else:
                    kwargs = {'email__iexact': username}

                user = UserModel.objects.get(**kwargs)
            else:
                raise
        except:
            if ((am == 'username') or (am == 'both')):
                if ((cs == 'username') or cs == 'both'):
                    kwargs = {'username': username}
                else:
                kwargs = {'username__iexact': username}

                user = UserModel.objects.get(**kwargs)
        finally:
            try:
                if user.check_password(password):
                    return user
            except:
                # Run the default password hasher once to reduce the timing
                # difference between an existing and a non-existing user.
                UserModel().set_password(password)
                return None

    def get_user(self, username):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=username)
        except UserModel.DoesNotExist:
            return None
        

Solution 7 - Python

Latest version of django-registration allows some nice customisation and might do the job - docs here https://bitbucket.org/ubernostrum/django-registration/src/fad7080fe769/docs/backend-api.rst

Solution 8 - Python

     if user_form.is_valid():
        # Save the user's form data to a user object without committing.
        user = user_form.save(commit=False)
        user.set_password(user.password)
        #Set username of user as the email
        user.username = user.email
        #commit
        user.save()

working perfectly... for django 1.11.4

Solution 9 - Python

you can also find an interesting discussion on this topic at the below link :

http://groups.google.com/group/django-users/browse_thread/thread/c943ede66e6807c/2fbf2afeade397eb#2fbf2afeade397eb

Solution 10 - Python

The easiest way is to lookup the username based on the email in the login view. That way you can leave everything else alone:

from django.contrib.auth import authenticate, login as auth_login

def _is_valid_email(email):
    from django.core.validators import validate_email
    from django.core.exceptions import ValidationError
    try:
        validate_email(email)
        return True
    except ValidationError:
        return False

def login(request):

    next = request.GET.get('next', '/')

    if request.method == 'POST':
        username = request.POST['username'].lower()  # case insensitivity
        password = request.POST['password']

    if _is_valid_email(username):
        try:
            username = User.objects.filter(email=username).values_list('username', flat=True)
        except User.DoesNotExist:
            username = None
    kwargs = {'username': username, 'password': password}
    user = authenticate(**kwargs)

        if user is not None:
            if user.is_active:
                auth_login(request, user)
                return redirect(next or '/')
            else:
                messages.info(request, "<stvrong>Error</strong> User account has not been activated..")
        else:
            messages.info(request, "<strong>Error</strong> Username or password was incorrect.")

    return render_to_response('accounts/login.html', {}, context_instance=RequestContext(request))

In your template set the next variable accordingly, i.e.

<form method="post" class="form-login" action="{% url 'login' %}?next={{ request.GET.next }}" accept-charset="UTF-8">

And give your username / password inputs the right names, i.e. username, password.

UPDATE:

Alternatively, the if _is_valid_email(email): call can be replaced with if '@' in username. That way you can drop the _is_valid_email function. This really depends on how you define your username. It will not work if you allow the '@' character in your usernames.

Solution 11 - Python

I think the most quickly way is to create a form inherit from UserCreateForm, and then override the username field with forms.EmailField. Then for every new registration user, they need to signon with their email address.

For example:

urls.py

...
urlpatterns += url(r'^signon/$', SignonView.as_view(), name="signon")

views.py

from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django import forms

class UserSignonForm(UserCreationForm):
    username = forms.EmailField()


class SignonView(CreateView):
    template_name = "registration/signon.html"
    model = User
    form_class = UserSignonForm

signon.html

...
<form action="#" method="post">
    ...
    <input type="email" name="username" />
    ...
</form>
...

Solution 12 - Python

Not sure if people are trying to accomplish this, but I found nice (and clean) way to only ask for the email and then set the username as the email in the view before saving.

My UserForm only requires the email and password:

class UserForm(forms.ModelForm):
    password = forms.CharField(widget=forms.PasswordInput())

    class Meta:
        model = User
        fields = ('email', 'password')

Then in my view I add the following logic:

if user_form.is_valid():
            # Save the user's form data to a user object without committing.
            user = user_form.save(commit=False)

            user.set_password(user.password)
            #Set username of user as the email
            user.username = user.email
            #commit
            user.save()

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondamonView Question on Stackoverflow
Solution 1 - PythonTom ChristieView Answer on Stackoverflow
Solution 2 - PythonS.LottView Answer on Stackoverflow
Solution 3 - PythonJuho RutilaView Answer on Stackoverflow
Solution 4 - PythonvinyllView Answer on Stackoverflow
Solution 5 - PythonMax MalyshView Answer on Stackoverflow
Solution 6 - PythonAdrian LopezView Answer on Stackoverflow
Solution 7 - PythonPhoebeBView Answer on Stackoverflow
Solution 8 - PythonsyedView Answer on Stackoverflow
Solution 9 - PythonRama VadakattuView Answer on Stackoverflow
Solution 10 - PythonradtekView Answer on Stackoverflow
Solution 11 - PythonEnixView Answer on Stackoverflow
Solution 12 - PythonNick S.View Answer on Stackoverflow