问题
实际开发中可能内置User模型的字段不能满足需要。
解决
1.首先查看内置User模型的源码:
MyDjangovenvScriptspyton.exeLibsite-packagesdjangocontribauthmodels.py,理清相关各类继承关系,如下:
1 2 3 | class PermissionsMixin(models.Model): class AbstractUser(AbstractBaseUser, PermissionsMixin): class User(AbstractUser): |
其中AbstractBaseUser在文件MyDjangovenvScriptspyton.exeLibsite-packagesdjangocontribauthbase_user.py中:
1 | class AbstractBaseUser(models.Model): |
从官方文档可以知道,User具有如下的内置方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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 "." . (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 "." . 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结构,也不会额外建表。
如下:
1 2 3 4 5 6 7 8 9 10 | 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添加:
1 | AUTH_USER_MODEL = '应用名.扩展的类名' |
最后要进行数据迁移:
1 2 | 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中进行相关定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持IT俱乐部。