即使使用自定义用户模型,Django的LoginView也使用旧的用户名属性

时间:2017-05-10 08:56:06

标签: python django

我想自定义Django的内置身份验证系统,用电子邮件替换用户名(即使用电子邮件/密码组合代替用户名/密码组合进行登录),因此我制作了自定义User模型通过继承AbstractBaseUserPermissionsMixin以及自定义UserManager,然后设置settings.AUTH_USER_MODEL = 'myapp.User'以启用它。

一切正常,但问题是,django.contrib.auth.views.LoginView生成的登录表单使用name="username" type="text"并为电子邮件输入字段标记了标识id_username(尽管使用了正确的标签字符串"电子邮件地址")。登录表单无论如何都有效,但语义错误,HTML5客户端验证不会以这种方式工作。请注意,这不是注册表中的问题;它按预期将电子邮件字段命名为email而不是username

以下是代码:

models.py:

from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from django.utils.translation import ugettext_lazy as _

from .managers import UserManager


class User(AbstractBaseUser, PermissionsMixin):
    first_name = models.CharField(_('first name'), max_length=30)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    email = models.EmailField(_('email address'), unique=True)
    date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
    is_active = models.BooleanField(_('active'), default=False)
    verification_code = models.CharField(_('verification code'), max_length=32,
                                         blank=True)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name']

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def __str__(self):
        return self.email

    def get_full_name(self):
        '''
        Return first_name plus the last name, with a space in between.
        '''
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        '''
        Return short name (first name) for the user.
        '''
        return self.first_name

managers.py

from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        '''
        Create and save a User with the given email and password.
        '''
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True')

        return self._create_user(email, password, **extra_fields)

settings.py

...
# Redirect to my URL after login
LOGIN_REDIRECT_URL = '/'

# Custom login backend (using email instead of username)
AUTH_USER_MODEL = 'myapp.User'
...

urls.py

from django.conf.urls import url
from django.contrib.auth import views as auth_views

from . import views

urlpatterns = [
    url(r'^$', views.home, name='home'),
    url(r'^accounts/register/$', views.register, name='register'),
    url(r'^accounts/login/$', views.login, name='login'),
    url(r'^accounts/logout/$', auth_views.LogoutView.as_view(),
        {'next_page': '/'}, name='logout'),
    url(r'^accounts/verify/(?P<code>[a-zA-Z0-9]{32})$', views.verify,
        name='verify'),
]

views.py

...
from django.contrib.auth.views import LoginView
from django.http import HttpResponseRedirect
from django.urls import reverse

from . import util
from .forms import RegistrationForm

...
def register(request):
    if request.method == 'POST':
        form = RegistrationForm(request.POST)
        if form.is_valid():
            user = form.save(commit=False)
            user.verification_code = get_random_string(length=32)
            user.save()
            res = util.send_verification_email(request, user)
            if not res.ok:
                logging.error(res.text)
                return HttpResponse(status=500)
            return render(request, 'registration/registration_complete.html')

    else:
        form = RegistrationForm()
    token = {}
    token.update(csrf(request))
    token['form'] = form

    return render(request, 'registration/register.html', token)


def login(request):
    if request.user.is_authenticated():
        return HttpResponseRedirect(reverse('home'))
    else:
        return LoginView.as_view()(request)
...

forms.py

from django import forms
from django.contrib.auth.forms import UserCreationForm

from .models import User


class RegistrationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = User
        fields = ('first_name', 'last_name', 'email')
        widgets = {
            'first_name': forms.TextInput(attrs={'placeholder': 'John',
                                                 'autofocus': 'true'}),
            'last_name': forms.TextInput(attrs={'placeholder': 'Doe'}),
            'email': forms.TextInput(attrs={'placeholder':
                                            'johndoe@example.com'}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['email'].widget.attrs.pop('autofocus')

模板使用{{ form }}上的for循环生成表单。

我意识到我正在使用forms.py中定义的自定义注册表单,但我不知道如何使用登录表单执行相同操作,我无法在线查找任何内容或在文档中。

我错过了什么?

1 个答案:

答案 0 :(得分:4)

我根本不明白你为什么要使用LoginView。记录用户是just a few lines of code,并且在您自己的视图中非常容易。

此外,直接从另一个视图调用视图确实不是一个好习惯。

但是如果你真的想使用LoginView,你需要继承AuthenticationForm,然后将该表单类传递给LoginView调用:

return LoginView.as_view(authentication_form=MyAuthFormClass)(request)