django之对django内置的User模型进行自定义扩展方式

2023-05-12 14:05:33 模型 自定义 扩展

问题

实际开发中可能内置User模型的字段不能满足需要。

解决

1.首先查看内置User模型的源码:

MyDjango\venv\Scripts\pyton.exe\Lib\site-packages\djanGo\contrib\auth\models.py,理清相关各类继承关系,如下:

class PermissionsMixin(models.Model):
class AbstractUser(AbstractBaseUser, PermissionsMixin):
class User(AbstractUser):

其中AbstractBaseUser在文件MyDjango\venv\Scripts\pyton.exe\Lib\site-packages\django\contrib\auth\base_user.py中:

class AbstractBaseUser(models.Model):

官方文档可以知道,User具有如下的内置方法:

class models.User
get_username()¶
Returns the username for the user. Since the User model can be swapped out, you should use this method instead of referencing the username attribute directly.
get_full_name()¶
Returns the first_name plus the last_name, with a space in between.
get_short_name()¶
Returns the first_name.
set_password(raw_passWord)¶
Sets the user's password to the given raw string, taking care of the password hashing. Doesn't save the User object.
When the raw_password is None, the password will be set to an unusable password, as if set_unusable_password() were used.
check_password(raw_password)¶
Returns True if the given raw string is the correct password for the user. (This takes care of the password hashing in making the comparison.)
set_unusable_password()¶
Marks the user as having no password set. This isn't the same as having a blank string for a password. check_password() for this user will never return True. Doesn't save the User object.
You may need this if authentication for your application takes place against an existing external source such as an LDAP directory.
has_usable_password()¶
Returns False if set_unusable_password() has been called for this user.
Changed in Django 2.1:
In older versions, this also returns False if the password is None or an empty string, or if the password uses a hasher that's not in the PASSWORD_HASHERS setting. That behavior is considered a bug as it prevents users with such passwords from requesting a password reset.
get_group_permissions(obj=None)¶
Returns a set of permission strings that the user has, through their groups.
If obj is passed in, only returns the group permissions for this specific object.
get_all_permissions(obj=None)¶
Returns a set of permission strings that the user has, both through group and user permissions.
If obj is passed in, only returns the permissions for this specific object.
has_perm(perm, obj=None)¶
Returns True if the user has the specified permission, where perm is in the fORMat "<app label>.<permission codename>". (see documentation on permissions). If the user is inactive, this method will always return False.
If obj is passed in, this method won't check for a permission for the model, but for this specific object.
has_perms(perm_list, obj=None)¶
Returns True if the user has each of the specified permissions, where each perm is in the format "<app label>.<permission codename>". If the user is inactive, this method will always return False.
If obj is passed in, this method won't check for permissions for the model, but for the specific object.
has_module_perms(package_name)¶
Returns True if the user has any permissions in the given package (the Django app label). If the user is inactive, this method will always return False.
email_user(subject, message, from_email=None, **kwargs)¶
Sends an email to the user. If from_email is None, Django uses the DEFAULT_FROM_EMAIL. Any **kwargs are passed to the underlying send_mail() call.

从源码可以知道,它们来自PermissionsMixin类、AbstractBaseUser类和AbstractUser类。

2.因此要实现内置User模型的扩展

可以从这些继承关系入手:

  • 1 继承AbstractUser

查看源码可以知道,User直接继承自AbstractUser,如果AbstractUser拥有的方法已经够用,且仅仅是添加一些额外字段的话,这是最方便的方法。

  • 2 继承AbstractBaseUser

查看源码可以知道,AbstractUser继承自AbstractBaseUser与PermissionsMixin。这方法比方法1自定义程度更高,对已经使用User建表的情况不友好,因为会破坏已有的表结构,且还要自己写相关的权限验证,相当麻烦。

  • 3 重写User源码

可以是可以,但直接改源码会在版本升级时失效。

  • 4 使用Profile模式

不改变已有的表结构,且如果AbstractUser拥有的方法已经够用,仅需在已有表基础上添加额外字段,就可以将这些额外字段所在的表通过外键与 User 关联起来。

  • 5 设置Proxy模型

自定义一个继承自User的类,将元数据Meta中的proxy置为True,以代表这个是User的代理模型。适用于不改变已有的表结构,但对User拥有的方法不够满足而需要自定义方法的情况。

大多情况下会选择方法1,既不改变原有User结构,也不会额外建表。

如下:

from django.db import models
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
    qq = models.CharField('QQ号', max_length=30)
    wechat = models.CharField('微信号', max_length=40)
    mobile = models.CharField('电话号', max_length=20)
    class Meta:
        verbose_name_plural = '自定义用户表'
    def __str__(self):
        return self.username

记得在settings.py添加:

AUTH_USER_MODEL = '应用名.扩展的类名'

最后要进行数据迁移:

python manage.py makemigrations
Python manage.py migrate

可能会报错:

django.db.migrations.exceptions.InconsistentMigrationHistory:
Migration admin.0001_initial is applied bef ore its dependency
user.0001_initial on database ‘default’.

删除数据库中除auth_user外的其他表,再重新进行数据迁移即可。

结果如下:

进入admin后台系统,但却没显示MyUser信息表。

根据MyUser的产生原理可以知道,MyUser是在User的model.py中定义的,admin后台无法直接显示MyUser信息表,需要在项目的admin.py与__init__.py中进行相关定义:

admin.py:
from django.contrib import admin
from .models import MyUser
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _
# 先注册
@admin.reGISter(MyUser)
class MyUserAdmin(UserAdmin):
    list_display = ['username', 'email', 'mobile', 'qq', 'wechat']
    # 将源码的UserAdmin.fieldsets转换成列表格式
    fieldsets = list(UserAdmin.fieldsets)
    fieldsets[1] = (_('Personal info'),
                    {'fields': ('first_name', 'last_name',
                     'email', 'mobile', 'qq', 'wechat')})
__init__.py:
from django.apps import AppConfig
import os
default_app_config = 'user.IndexConfig'
# 获取当前app的命名
def get_current_app_name(_file):
    return os.path.split(os.path.dirname(_file))[-1]
# 重写类IndexConfig
class IndexConfig(AppConfig):
    name = get_current_app_name(__file__)
    verbose_name = '自定义用户信息数据表'

MyUserAdmin继承自UserAdmin,再重写后台数据展示字段,就能使自定义用户模型展示在admin后台。

然后运行开发服务器可能会报错:

LookupError: No installed app with label ‘admin’.

我的django版本为2.2,查了一下,据说是2.2的bug,更换为2.2.14后问题解决。

如下:

访问127.0.0.1:8000

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

相关文章