创建一个自定义的Django用户模型(1)

最后编辑于 02月08日 开发

这是Michael Herman在2月7日发布在testdriven.io上的一篇文章,写得粗细得当,对想了解自定义Django用户模型的同学非常有帮助。

在Django中创建自定义用户模型User Model,是为了使电子邮件地址能够替代用户名来作为主要用户标识符进行身份验证。

该创建过程需要对数据库模式进行重大更改,因此,只推荐用于新项目。如果要用于旧项目,则可能需要备份数据并重新创建数据库。下面是两篇参考文章:
1、Django官方文档中的自定义用户模型指南
2、在Django中迁移到自定义用户模型

AbstractUser与AbstractBaseUser
Django的用户模型默认使用用户名作为唯一用户标识进行身份验证期,如果要使用电子邮件地址,则需要通过继承AbstractUser或AbstractBaseUser来创建自定义用户模型。

1、AbstractUser:如果对用户模型上的现有字段感到满意并且只想删除用户名字段,请使用此选项。
2、AbstractBaseUser:如果想创建全新的自定义用户模型,请使用此选项。

使用AbstractUser或AbstractBaseUser的步骤都相同:

1、创建一个自定义用户模型和管理器
2、更新settings.py
3、自定义UserCreationForm和UserChangeForm表单
4、更新管理员

Project Setup

假设你已经安装了Pipenv,然后创建一个新的Django project以及一个users app。Pipenv是Kenneth Reitz开发的Python虚拟环境和依赖管理工具。

$ mkdir django-custom-user-model && cd django-custom-user-model
$ pipenv install django==2.1.5
$ pipenv shell
(django-custom-user-model)$ django-admin.py startproject hello_django .
(django-custom-user-model)$ python manage.py startapp users

注意:不要应用迁移。在应用第一次迁移之前,必须创建自定义用户模型。

把这个users app添加到settings.py的INSTALLED_APPS中:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'users',
]

Tests
先来个测试,把这些添加到users/tests.py,并确保测试失败。

from django.test import TestCase
from django.contrib.auth import get_user_model


class UsersManagersTests(TestCase):

    def test_create_user(self):
        User = get_user_model()
        user = User.objects.create_user(email='normal@user.com', password='foo')
        self.assertEqual(user.email, 'normal@user.com')
        self.assertTrue(user.is_active)
        self.assertFalse(user.is_staff)
        self.assertFalse(user.is_superuser)
        try:
            # username is None for the AbstractUser option
            # username does not exist for the AbstractBaseUser option
            self.assertIsNone(user.username)
        except AttributeError:
            pass
        with self.assertRaises(TypeError):
            User.objects.create_user()
        with self.assertRaises(TypeError):
            User.objects.create_user(email='')
        with self.assertRaises(ValueError):
            User.objects.create_user(email='', password="foo")

    def test_create_superuser(self):
        User = get_user_model()
        admin_user = User.objects.create_superuser('super@user.com', 'foo')
        self.assertEqual(admin_user.email, 'super@user.com')
        self.assertTrue(admin_user.is_active)
        self.assertTrue(admin_user.is_staff)
        self.assertTrue(admin_user.is_superuser)
        try:
            # username is None for the AbstractUser option
            # username does not exist for the AbstractBaseUser option
            self.assertIsNone(admin_user.username)
        except AttributeError:
            pass
        with self.assertRaises(ValueError):
            User.objects.create_superuser(
                email='super@user.com', password='foo', is_superuser=False)

Model Manager
通过继承BaseUserManager,生成一个使用电子邮件地址替代用户名作为唯一标志的自定义的Manager。

在users目录下创建一个managers.py文件:

from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _


class CustomUserManager(BaseUserManager):
    """
    Custom user model manager where email is the unique identifiers
    for authentication instead of usernames.
    """
    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 Email must be set'))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError(_('Superuser must have is_staff=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)

User Model
选择该用户模型是继承自AbstractUser还是AbstractBaseUser。

如果继承自AbstractUser
更新users/models.py:
1、创建继承自AbstractUser的子类CustomUser
2、删除username字段
3、使email字段为required和unique
4、设置USERNAME_FIELD为email
5、指定CustomUser的所有的objects来自CustomUserManager

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _

from .managers import CustomUserManager


class CustomUser(AbstractUser):
    username = None
    email = models.EmailField(_('email address'), unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email

如果继承自AbstractBaseUser
更新users/models.py:
1、创建继承自AbstractBaseUser的子类CustomUser
2、增加email、is_staff、is_active和date_joined字段
3、设置USERNAME_FIELD为email
4、指定CustomUser的所有的objects来自CustomUserManager

from django.db import models
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.utils.translation import gettext_lazy as _
from django.utils import timezone

from .managers import CustomUserManager


class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), unique=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email

下文见:
创建一个自定义的Django用户模型(2)

登录注册后才能评论。