【问题标题】:Accepting email address as username in Django在 Django 中接受电子邮件地址作为用户名
【发布时间】:2010-10-21 04:09:16
【问题描述】:

在 Django 中是否有一种不用滚动我自己的身份验证系统的好方法来做到这一点?我希望用户名是用户的电子邮件地址,而不是他们创建用户名。

请指教

【问题讨论】:

标签: python django authentication


【解决方案1】:

对于其他想要这样做的人,我建议查看django-email-as-username,这是一个非常全面的解决方案,其中包括修补管理员和createsuperuser 管理命令,以及其他一些零碎的东西。

编辑:从 Django 1.5 开始,您应该考虑使用 custom user model 而不是 django-email-as-username

【讨论】:

  • 如果您认为自定义用户模型是最佳选择,您可能需要查看 caktus 组博客上的 this tutorial,它类似于 django 文档中给出的 this example,但需要注意一些细节生产环境所需的(例如权限)。
  • 对于 Django 1.5 及更高版本,django-custom-user 应用程序包含实现此功能的预制自定义用户模型。
【解决方案2】:

这就是我们所做的。这不是一个“完整”的解决方案,但它可以满足您的大部分需求。

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)

【讨论】:

  • 为我工作。虽然我可以看到这让未来的维护者感到困惑。
  • 这可能是我在 SO 中见过的最聪明的答案。
  • on admin Create User this: exclude = ('email',) 向我抛出一个错误,即用户窗体中缺少 key['email'],这很明显。
【解决方案3】:

这是一种同时接受用户名和电子邮件的方法:

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

不知道是否有一些设置可以设置默认的身份验证表单,但您也可以覆盖 urls.py 中的 url

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

提高 ValidationError 将防止在提交无效电子邮件时出现 500 错误。使用超级用户对“invalid_login”的定义会使错误消息不明确(相对于特定的“未找到该电子邮件的用户”),这将是防止泄漏电子邮件地址是否已在您的服务上注册帐户所必需的。如果该信息在您的架构中不安全,则提供更多信息的错误消息可能更友好。

【讨论】:

  • 我没有使用 auth.views.login。使用自定义这是我的网址 url(r'accounts/login', 'login_view',) 。如果我给 EmailAuthenticationForm,那么错误是 login_view() got an unexpected keyword argument 'authentication_form'
  • 这对我来说很干净,我实际上使用了我的自定义用户配置文件(因为在我的架构中,用户不能保证附加电子邮件地址)。似乎比自定义用户模型或使用户名等于电子邮件要好得多(因为这允许在公开用户名的同时保持朋友的电子邮件私密)。
【解决方案4】:

Django 现在提供了一个带有管理员和表单的扩展身份验证系统的完整示例:https://docs.djangoproject.com/en/stable/topics/auth/customizing/#a-full-example

你基本上可以复制/粘贴它并适应(我不需要date_of_birth)。

它实际上是从 Django 1.5 开始可用的,并且到现在 (django 1.7) 仍然可用。

【讨论】:

【解决方案5】:

如果您要扩展用户模型,无论如何您都必须实现自定义用户模型。

这是 Django 1.8 的示例。 Django 1.7 需要更多的工作,主要是更改默认表单(只需查看 django.contrib.auth.forms 中的 UserChangeFormUserCreationForm - 这就是您在 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'

【讨论】:

  • 我已经测试了您的方法并且有效,但在我的情况下,我已将 username 字段添加到 SiteUser 模型,因为当我执行 python manage.py makemigrations ... 命令时,我得到以下输出: ERRORS: <class 'accounts.admin.UserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not an attribute of 'accounts.User'.
  • 我已将 username 字段添加到我的 User 模型及其 null=True 属性。在这个粘贴箱条目中,我确实想展示实现。 pastebin.com/W1PgLrD9
【解决方案6】:

其他替代方案对我来说太复杂了,所以我编写了一个 sn-p,它允许使用用户名、电子邮件或两者进行身份验证,并且还启用或禁用区分大小写。我以django-dual-authentication 的身份将其上传到pip。

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

【讨论】:

    【解决方案7】:

    最新版本的 django-registration 允许一些不错的自定义,并且可以完成这项工作 - 此处的文档 https://bitbucket.org/ubernostrum/django-registration/src/fad7080fe769/docs/backend-api.rst

    【讨论】:

      【解决方案8】:
           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()
      

      完美运行...适用于 django 1.11.4

      【讨论】:

        【解决方案9】:

        【讨论】:

          【解决方案10】:

          最简单的方法是在登录视图中根据电子邮件查找用户名。这样你就可以不用管其他所有事情了:

          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))
          

          在您的模板中相应地设置下一个变量,即

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

          并为您的用户名/密码输入正确的名称,即用户名、密码。

          更新

          或者,if _is_valid_email(email): 调用可以替换为用户名中的 if '@'。这样您就可以删除 _is_valid_email 函数。这实际上取决于您如何定义用户名。如果您在用户名中允许使用“@”字符,它将不起作用。

          【讨论】:

          • 这段代码有问题,因为用户名也可以有'@'符号,所以如果有'@',就不需要电子邮件。
          • 真的取决于你,我不允许用户名有@符号。如果您这样做,您可以添加另一个过滤器查询以通过用户名搜索用户对象。 PS。用户名也可以是电子邮件,因此您必须谨慎设计用户管理。
          • 另外签出,从 django.core.validators import validate_email 。您可以尝试使用 validate_email('your@email.com') 来尝试除 ValidationError 块。根据您的应用,它可能仍然存在错误。
          • 当然,你是对的,这取决于登录逻辑是如何设置的。我提到过,因为用户名中“@”的可能性是 django 的默认设置。当用户名为“Gladi@tor”的用户无法登录时,有人可以复制您的代码并遇到问题,因为登录代码认为这是一封电子邮件。
          • 好电话。我希望人们明白他们在复制什么。我将添加电子邮件验证并注释掉当前验证,将其作为替代。我想除了我的用户名没有@之外,我没有使用验证的唯一其他原因是它使代码对 django 的依赖性降低。事情确实倾向于从一个版本到另一个版本。干杯!
          【解决方案11】:

          我认为最快的方法是创建一个从UserCreateForm 继承的表单,然后用forms.EmailField 覆盖username 字段。然后对于每个新注册用户,他们需要使用他们的电子邮件地址登录。

          例如:

          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>
          ...
          

          【讨论】:

          • 由于几个原因而被否决。 This answer 是做同样事情的更好方法。当您只能在UserCreationForm 类中直接定义要使用的小部件时,为什么要进行子类化?请不要建议写出&lt;input …,当然{{form.username}} 更好。
          【解决方案12】:

          不确定人们是否正在尝试完成此操作,但我发现了一种很好(且干净)的方式,只要求提供电子邮件,然后在保存之前将用户名设置为视图中的电子邮件。

          My UserForm 只需要邮箱和密码:

          class UserForm(forms.ModelForm):
              password = forms.CharField(widget=forms.PasswordInput())
          
              class Meta:
                  model = User
                  fields = ('email', 'password')
          

          然后在我看来,我添加了以下逻辑:

          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()
          

          【讨论】:

          • 无法将电子邮件存储在用户名中导致问题,因为用户名是 30 个字符,而电子邮件是 75 个字符?
          猜你喜欢
          • 2012-12-09
          • 1970-01-01
          • 2015-04-27
          • 1970-01-01
          • 2011-05-17
          • 1970-01-01
          • 1970-01-01
          • 2014-10-08
          • 2011-04-02
          相关资源
          最近更新 更多