在django中使用电子邮件而不是登录名

时间:2010-02-17 22:41:15

标签: database django architecture data-structures

首先,这不是如何对电子邮件/密码对进行身份验证的问题,而是如何生成逻辑,如果您愿意,还可以生成漂亮的数据结构。

我想在给定的django项目中使用电子邮件作为用户名。但是,我无法重新使用auth.User模型提供的字段,原因至少有两个:

  1. auth.User.username的字段max_length是30个字符,对于某些电子邮件地址可能不够。

  2. auth.User.email不是唯一的 - 显然不能满足前提条件,即用户名必须是唯一的。

  3. 这里一个显而易见的方法是将用户名存储在自定义配置文件中,该配置文件链接到auth.User。在这种情况下,我们必须处理以下问题:

    1. 为auth.User.username生成唯一的用户名 - 电子邮件的md5哈希应该没问题吗?
    2. 完全不使用auth.User.email - 因为它只有75个字符长,而根据RFC 5321(What is the maximum length of a valid email address?),电子邮件可以长达256个字符。
    3. 以下问题源于提议的解决方案:

      1. 一个人无法为密码重置等标准操作重用内置视图/模板
      2. 如果电子邮件更改,则必须更新auth.User.username
      3. 为了在火灾中加油,django开发商不可能在任何可预见的未来修复此限制 - 请参阅http://code.djangoproject.com/ticket/11365

        所以问题是:有没有其他方法可以做到这一点,你看到上面提出的解决方案有任何其他缺点吗?

        谢谢!

7 个答案:

答案 0 :(得分:5)

我的客户有一个自1995年以来一直在增加的商业网站(是的,我们在这里谈论早期采用者)。无论如何,他们已经建立了一个已建立的用户群,并且名称​​完全不符合Django对用户名的想法。

我看了几种方法来处理它,它们都感觉像黑客(这是2007年的夏天),所以我说搞砸它并直接攻击contrib.auth.models.User。我只需更改大约10行代码,增加字段大小,并调整验证器。从那以后我们做了两次升级 - 0.97-pre => 1.0,和1.0 => 1.1.1 - 并且每次只需要大约15分钟来“移植黑客”。

它不漂亮,而且我可能会像这样在地狱中燃烧,但这样做的时间比我能想到的任何其他东西花费的时间更短,前向端口完全没有问题。

答案 1 :(得分:2)

答案 2 :(得分:1)

我写了一个关于我对这个问题的解决方案的解释:Django Authentication using an Email Address。它基本上包括:

  • 为电子邮件身份验证创建自定义授权后端。
  • 将用户创建表单子类化,以将电子邮件地址添加为必填字段。
  • 在创建和登录表单中隐藏用户的用户名字段。
  • 在处理创建表单的视图中随机生成用户名。
  • 手动为电子邮件列添加唯一索引(Yuck!)

我的解决方案仍有2个问题。首先,手动创建数据库索引并不好。其次,电子邮件仍然限制在75个字符(我没有任何问题移植系统有大约8,000个用户)。但是,它与Django和第三方应用程序的其余部分相当不错。

答案 3 :(得分:0)

我也必须承认我会在地狱中燃烧。我最近部署了一个小应用程序,我将用户的电子邮件强制插入,将其切成30个字符并将其设置为用户名。我以为地狱,赔率是多少?并经历过它。拿了几行代码,瞧不起。

我认为75字符的上限是这样设置的,因为人们通常没有长时间的个人电子邮件。这只是空间保护的问题,因为所有那些未使用的字节都将被保留(即NULL和更短/更小的值不是免费的)。

答案 4 :(得分:0)

我只使用this djangosnippet,用户可以使用他们的用户名或电子邮件 但它不能解决75个字符的限制,只是一个方便的片段。

答案 5 :(得分:0)

我写了一个基于Dominique答案的解决方案,其中包括安全性改进和一些额外的功能,如区分大小写的身份验证。如果您愿意,可以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

答案 6 :(得分:0)

对于没有更改模型的两种身份验证[用户名,电子邮件],只需添加后端:

method both authentication [email, username]