如何扩展 Django 的用户模型

Python 2019-05-14 1920 次浏览 次点赞

起步

Django 内置的用户模型提供了包括身份认证等非常棒的功能,在大多数情况下开箱即用,节省了大量的开发和测试的工作。

通常我们需要存储更多用户相关的数据,内置的模型已无法满足。我们需要对它进行扩展。

自定义模型

这是最推荐的一个方案了。正如官方Django文档强烈建议为新项目使用自定义用户模型。

If you're starting a new project, it's highly recommended to set up a custom user model, even if the default User model is sufficient for you.

(如果您要开始一个新项目,强烈建议您设置自定义用户模型,即使默认User模型已经足够。)

而对于已经使用内置模型也有办法扩展,后续再讲。

AbstractUser 还是 AbstractBaseUser ?

在Django中有两种现代方法可以创建自定义用户模型:AbstractUserAbstractBaseUser 。这里推荐 AbstractUser ,它是 AbstractBaseUser 的子类,提供了较为完整的用户模型,包含字段,作为抽象类,以便您可以继承它并添加自己的配置文件字段和方法。而 AbstractBaseUser 只包含身份验证功能,不包含其他实际字段,若使用它则需要更多的工作,说真的,除非你确实有需要,否则不要乱用它。

创建用户模型

创建个应用来处理用户模型python manage.py startapp users,在 users.models 中定义模型:

from django.db import models
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    birth_date = models.DateField(null=True, blank=True)

    def __str__(self):
        return self.username

这个模型是在保持内置 User 字段和功能下进行的扩展,能够存储更多用户相关的数据。然后在 settings.py 中指定:

AUTH_USER_MODEL = 'users.CustomUser'

扩展内置的用户模型

再次强调,还是推荐使用自定义的模型。但如果很不幸,项目用了内置模型并运行了一段时间,那该怎么扩展呢?办法也是有的。

1. 使用代理模型扩展用户模型

这是扩展现有用户模型的侵入性较小的方法。这种策略不会有任何缺点。但它在很多方面都非常有限。

from django.contrib.auth.models import User
class MyUser(User):
    class Meta:
        proxy = True

    def get_xxxx(self):
        return ''

使用 proxy = True 不会新生成数据表,却能给予模型更多操作,对原有的功能几乎是没有影响的。

同样需要在 settings.py 中设置 AUTH_USER_MODEL ,但由于认证模型的修改,在 migrate 时可能会报错:

    raise ValueError("\n".join(error.msg for error in errors))
ValueError: The field admin.LogEntry.user was declared with a lazy reference to 'accounts.user', but app 'accounts' doesn't provide model 'user'.

别担心,这是原先的模型依然依赖于内置模型而不是代理模型。清空表 django_migrations 并执行 python manage.py migrate --fake 即可。

这种方法优点十分明显,但缺点就是不能存储额外数据。(哦?也许可以在操作中将数据存于其他表?)

2. 使用一对一链接扩展用户模型

对于扩展内置用户模型而言,这个方案比较普遍,因为它能存储额外信息和添加新的操作。

from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    location = models.CharField(max_length=30, blank=True)
    birth_date = models.DateField(null=True, blank=True)

接下来就是这个方案神奇的地方了,我们将通过信号来自动创建和更新模型:

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    location = models.CharField(max_length=30, blank=True)
    birth_date = models.DateField(null=True, blank=True)

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

基本上,每当发生保存事件时,都会通过 post_save 信号将用户模型 create_user_profilesave_user_profile 方法调用。

这个方案不需要修改 AUTH_USER_MODEL 。由于这个方案会对模型添加关联关系,所以可能在查询时候会出发一个额外查询,但大多数情况下都不会触发。

由于定义的信号,所以在模型的创建和修改时,一定会触发额外的查询的。

总结

本文中介绍了两种方式来扩展用户模型。

  1. 自定义模型:保持了Django处理auth的方式,还能添加额外的属性而无需创建单独的模型。
  2. 扩展内置模型:
    • 代理模型:Django用户提供的所有内容感到满意,并且不需要存储额外的信息。
    • 一对一链接扩展:需要向用户添加一些非身份验证相关的属性。

在今后的 Django 项目中,请在创建项目后就使用自定的用户模型,或者可以试试这个脚手架 https://github.com/wsvincent/djangox ,它包含了自定义用户模型和简单的登录退出修改密码和首页的例子,包含了比较常用的第三方模块(allauth等)。


本文由 hongweipeng 创作,采用 署名-非商业性使用-相同方式共享 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!