扩展和自定义 Django 的 User 模型

为什么要扩展和自定义 Django 的模型?因为其自带的字段实在是不够用。比如说,我需要用户有一个昵称,防止把用户名暴露出去,但是自带的用户模型中并没有这个字段,只有 first_namelast_name。如果勉强选择其中一个作为昵称字段,就会发现字段没有加唯一约束,多个用户可以用同一个昵称。

再比如,要为用户加上保存手机号的字段,或者要给用户加上保存积分的字段,这都是必须要修改用户模型才能实现的。那自己在应用里单独定义一个用户模型,自己做权限认证等等,行不行?完全可以,但对于新手和一般的开发人员来说,没有必要且浪费了现成的后台和认证框架。因此还是推荐按照官方文档的方法扩展和自定义 Django 的 User 模型。

代理模型、扩展模型和自定义模型

Django 提供了三种方法来弥补自带 User 模型的不足之处。

代理模型

代理模型的作用是在不修改 User 模型的前提下,为 User 模型增加额外方法和修改默认行为。如修改排序方式,增加新的模型方法等。下面我为 User 添加一个代理模型来实现排序和自定义方法:

from django.contrib.auth.models import User

class MyUser(User):
    class Meta:
        ordering = ['username']
        proxy = True

    def get_upper_fullname(self):
        return self.get_upper_fullname().upper()

以上的 MyUser 模型作为 User 的代理模型实现了按照 username 排序,并且添加了 get_upper_fullname() 方法返回大写形式的用户全名。在使用时,这种方法不需要修改数据库,只是在需要的时候导入 MyUser 模型使用即可,总体上对原有代码的改动最小,当然扩展能力也是最小。

那这里的代码应该放在哪里呢?并没有规定的地方,只要方便导入和自己修改即可。不过,通常为了服务公开用户的后台,会单独建立一个应用,放在这个应用的 models.py 中即可。

扩展模型

扩展模型也不修改原有的 User 模型,它单独建立一张新的表,通过一对一字段(OneToOneField)连接 User模型。从技术上看,与模型继承类似。我们通常使用 Profile 作为扩展模型名称,如下所示:

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

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile", )
    credit = models.IntegerField(default=0)
    phone = models.CharField(max_length=11, default='ymc.ee')

新的 Profile 模型可以随意增加字段和方法,但是使用这种方法需要注意一些问题。在使用 User 模型实例查找 Profile 信息要使用类似 user.profile.phone 的方法,这会带来额外的性能消耗。同时一对一关系与外键类似,在创建新用户时不会自动创建,要手动创建对应的 Profile 记录。

由于无法修改自带的用户模型,所以不能重载用户模型的 save() 方法。推荐方法是在使用表单时创建或者使用信号系统来创建,首先看使用表单时的代码:

from django.contrib.auth.forms import UserCreationForm

from .models import Profile

class CustomUserCreationForm(UserCreationForm):
    def save(self, commit=True):
        user = super().save(commit=False)
        if commit:
            user.save()
            profile = Profile(user=user)
            profile.save()
        return user

创建一个自定义表单,重载自带用户创建表单的 save() 方法,在创建用户的同时创建对应的 Profile。如果不使用表单,使用信号系统的话,像这样:

from .models import Profile

def create_profile(sender, **kwargs):
    ins = kwargs.get('instance')
    p = Profile.objects.get(user=ins)
    p.save()
    return True

创建一个信号处理程序,挂载到 User 模型的 post_save() 信号即可。具体如何操作,以后我再写一篇信号相关的文章。还需要注意的是,以上两种方法对于已经创建的用户不会有影响,因此需要你自己编写迁移来手动创建 Profile

自定义模型

Django 官方文档推荐在每个项目一开始就使用自定义模型,因为自定义模型便于以后自行增加功能。但是已有的项目(数据库已有模型表),不推荐更改自定义模型,除非你非常熟悉你目前的模型关系并且能够手动编写迁移来确保数据正确。以下是简单的自定义模型:

from django.contrib.auth.models import AbstractUser

class MyUser(AbstractUser):
    pass

这个自定义模型没有增加任何方法或字段,但要注意我们导入的是 AbstractUser,这是 User 模型的抽象基类,导入这个就不会额外创建用户表。然后修改配置文件中的 AUTH_USER_MODEL 选项为自定义模型:

AUTH_USER_MODEL = 'myapp.MyUser'

之后使用自定义模型即可。如果你在编写可重用的应用,需要使用 django.contrib.auth.get_user_model() 来获取用户模型。因为使用这个方法能够确保获取正确的模型,如果没有自定义模型会自动返回自带的 User 模型。

最后

三种方法各有优势,扩展模型方法将用户认证和用户信息表分离,可能在某些情况下比自定义方法更好。而且,由于官方文档将自定义模型藏的比较深,很多项目往往最初并没有选择自定义模型,后期要改也非常麻烦。如何选择还是要看具体的项目情况。

posted @ 2021-01-23 13:51  Veoco  阅读(196)  评论(0编辑  收藏  举报