在Django中接受电子邮件地址作为用户名


Answers:


35

对于其他想要这样做的人,我建议您看一下django-email-as-username,它是一个非常全面的解决方案,其中包括修补admin和createsuperusermanagement命令以及其他细节。

编辑:从Django 1.5开始,您应该考虑使用自定义用户模型而不是django-email-as-username


3
如果您认为自定义用户模型是最佳选择,则可能需要在caktus组博客上查看本教程该教程类似于django docs中给出的示例,但要注意生产环境所需的一些细节(例如,权限)。
gaboroncancio 2015年

4
对于Django 1.5及更高版本,django-custom-user应用程序包含一个实现此功能的罐装自定义用户模型。
2015年

29

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

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中见过的最聪明的答案。
Emmet B

在admin创建用户上,此操作:exclude =('email',)向我抛出一个错误,即UserForm中缺少key ['email'],这很明显。
CristiC777

22

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

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()得到了意外的关键字参数'authentication_form'–
Raja Simon

这对我来说很干净,实际上我使用了自定义用户配置文件(因为在我的体系结构中,不能保证用户会附加电子邮件地址)。似乎比自定义用户模型或使用户名等于电子邮件好得多(因为这样可以在公开用户名的同时将朋友的电子邮件保密)。
owenfi


7

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

这是Django 1.8的示例。Django 1.7需要做更多的工作,主要是更改默认格式(只需查看UserChangeFormUserCreationFormin django.contrib.auth.forms-这就是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'.
bgarcial

我已将username字段User及其null=True属性添加到我的模型中。在此粘贴容器条目中,我确实想显示实现。pastebin.com/W1PgLrD9
bgarcial

2

其他选择对我来说看起来太复杂,因此我编写了一个代码段,该代码段允许使用用户名和/或电子邮件进行身份验证,还可以启用或禁用区分大小写的功能。我将其作为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


1
     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



0

最简单的方法是根据登录视图中的电子邮件查找用户名。这样一来,您可以不理会其他所有事情:

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函数。这实际上取决于您如何定义用户名。如果您在用户名中允许使用“ @”字符,则该功能将无效。


1
该代码有问题,因为用户名也可以带有“ @”符号,因此如果存在“ @”,则无需发送电子邮件。
MrKsn 2014年

确实取决于您,我不允许用户名使用@符号。如果这样做,则可以添加另一个过滤器查询,以按用户名搜索User对象。PS。用户名也可以是电子邮件,因此您必须谨慎设计用户管理。
radtek 2014年

还可以从django.core.validators导入validate_email进行检出。您可以使用validate_email('your@email.com')进行除ValidationError块之外的尝试。根据您的应用程序,它可能仍然是错误的。
radtek 2014年

当然,您是对的,这取决于如何设置登录逻辑。我提到这是因为django的默认名称是用户名'@'。用户名“ Gladi @ tor”的用户由于登录代码认为这是电子邮件而无法登录时,有人可以复制您的代码并遇到问题。
MrKsn 2014年

好决定。我希望人们了解他们正在复制的内容。我将添加电子邮件验证并注释掉当前验证,然后将其保留。我猜除了我的用户名没有@之外,我不使用验证的唯一其他原因是,它使代码对Django的依赖性降低。事情确实会随着版本的变化而变化。干杯!
radtek

0

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

例如:

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

出于两个原因而投票。这个答案是做同一件事的更好方法。为什么只在UserCreationForm类中直接定义要使用的小部件,为什么要子类化呢?而且<input …,当然{{form.username}}最好不要建议写出更好的内容。
乔纳斯·格兰杰

0

不知道人们是否在尝试做到这一点,但是我发现了一种很好的(干净的)方法,只要求发送电子邮件,然后在保存之前在视图中将用户名设置为电子邮件。

我的用户窗体仅需要电子邮件和密码:

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

1
因为用户名是30个字符而电子邮件是75个字符,所以不能将电子邮件存储在用户名中导致问题?
user2233706's
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.