开发者

Using email instead of login name in django

Firstly, this is not the question how to authenticate on email/password pair, but rather how to produce logical, and if you like, beautiful data structure.

I want to use emails as user names in a given django project. However, I am unable to re-use fields provided by auth.User model for at least two reasons:

  1. auth.User.username 's field max_length is 30 characters, which might not be enough for some email addresses.

  2. auth.User.email is not unique - which is obviously not satisfactory for a prerequisite saying that user names have to be unique.

So an obvious way here is to store username in a custom profile, which is linked to auth.User. In this case we have to deal with following problems:

  1. Generate unique username for auth.User.username - md5 hash of email should be fine here?
  2. Leave out completely auth.User.email empty - since开发者_开发百科 it's only 75 characters long, while according to RFC 5321 (What is the maximum length of a valid email address?) email can be as long as 256 characters.

The following problems stem from the proposed solution:

  1. One is not going to be able to reuse built-in views/templates for standard operations like password reset etc
  2. In case of email change auth.User.username will have to be updated

To add oil into the fire, django developers are not likely to fix this limitation in any foreseeable future - see http://code.djangoproject.com/ticket/11365

So the question is: is there any other way to do it and do you see any other drawbacks in the solution proposed above?

Thanks!


I had a client with a commercial site that had been up since 1995 (yeah, we're talking early adopters here). Anyway, they already had an established user base and the names were totally non-compliant with Django's idea of a username.

I looked at a few ways to handle it and they all felt like hacks (this was Summer of 2007), so I said screw it and hacked contrib.auth.models.User directly. I only had to change about 10 lines of code, increase the field size, and tweak the validator. We've done two upgrades since then -- 0.97-pre => 1.0, and 1.0 => 1.1.1 -- and it's only taken about 15 minutes each time to "port the hack".

It isn't pretty, and I may burn in Hell for doing it like this, but it took less time to do it this way than anything else I could figure out and the forward ports have been a total non-issue.


You might want to take a look at how Satchmo handle this problem :

http://bitbucket.org/chris1610/satchmo/src/tip/satchmo/apps/satchmo_store/accounts/email-auth.py

and

http://bitbucket.org/chris1610/satchmo/src/533a63f955f8/satchmo/apps/satchmo_utils/unique_id.py


I wrote up an explanation of my solution to this same problem: Django Authentication using an Email Address. It basically consists of:

  • Create a custom authorization backend for email authentication.
  • Subclass the user creation form to add email address as a required field.
  • Hide the username field from the user in the creation and login forms.
  • Randomly generate a username in the view that processes the creation form.
  • Manually ad a unique index to the email column (Yuck!)

My solution has still has 2 problems. First, manually creating a database index is not good. Second, the email is still limited to the 75 characters (I didn't have any issues porting a system with about 8,000 users). But, it plays pretty nicely with the rest of Django and 3rd party apps.


I too must confess that I'll burn in hell. I've recently deployed a small app where I slugified the user's e-mail, sliced it to 30 characters and set that as the username. I thought hell, what are the odds? and went through with it. Took a few lines of code and voila.

I think the 75 character cap has been set as such because people usually don't have personal e-mails that long. That's just a matter of space conservation, because all those unused bytes will get reserved anyways (i.e. NULL and shorter/smaller values aren't free).


I just use this djangosnippet and users can use either their username or their email.
But it won't solve the 75 characters limit, just a handy snippet.


I wrote a solution based on Dominique answer that includes security improvements and some extra features like case sensitive authentication. If you prefer, you can install it directly from pypi:

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


for both authentication [username, email] without change models just add Backend:

method both authentication [email, username]

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜