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:
auth.User.username 's field max_length is 30 characters, which might not be enough for some email addresses.
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:
- Generate unique username for auth.User.username - md5 hash of email should be fine here?
- 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:
- One is not going to be able to reuse built-in views/templates for standard operations like password reset etc
- 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]
精彩评论