功能期望
- 基于Django提供的AbstractUser类重写User模型,在其中根据业务需求增加信息,并将新的用户模型设为系统默认用户模型。
- 使用新的自定义User模型创建超级用户和普通用户。
其中,自定义用户模型的部分参考了如下链接中的代码:
部分代码如下所示:
from django.contrib.auth.models import BaseUserManager,AbstractUser
from shortuuidfield import ShortUUIDField # 使用shortuuid作为User表的主键,使数据更加的安全
class UserManager(BaseUserManager): #自定义Manager管理器
def _create_user(self,username,password,email,**kwargs):
if not username:
raise ValueError("请传入用户名!")
if not password:
raise ValueError("请传入密码!")
if not email:
raise ValueError("请传入邮箱地址!")
user = self.model(username=username,email=email,**kwargs)
user.set_password(password)
user.save()
return user
def create_user(self,username,password,email,**kwargs): # 创建普通用户
kwargs['is_superuser'] = False
return self._create_user(username,password,email,**kwargs)
def create_superuser(self,username,password,email,**kwargs): # 创建超级用户
kwargs['is_superuser'] = True
kwargs['is_staff'] = True
return self._create_user(username,password,email,**kwargs)
class User(AbstractUser): # 自定义User
GENDER_TYPE = (
("1","男"),
("2","女")
)
uid = ShortUUIDField(primary_key=True)
username = models.CharField(max_length=15,verbose_name="用户名",unique=True)
nickname = models.CharField(max_length=13,verbose_name="昵称",null=True,blank=True)
age = models.IntegerField(verbose_name="年龄",null=True,blank=True)
gender = models.CharField(max_length=2,choices=GENDER_TYPE,verbose_name="性别",null=True,blank=True)
phone = models.CharField(max_length=11,null=True,blank=True,verbose_name="手机号码")
email = models.EmailField(verbose_name="邮箱")
picture = models.ImageField(upload_to="Store/user_picture",verbose_name="用户头像",null=True,blank=True)
home_address = models.CharField(max_length=100,null=True,blank=True,verbose_name="地址")
card_id = models.CharField(max_length=30,verbose_name="身份证",null=True,blank=True)
is_active = models.BooleanField(default=True,verbose_name="激活状态")
is_staff = models.BooleanField(default=True,verbose_name="是否是员工")
date_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'username' # 使用authenticate验证时使用的验证字段,可以换成其他字段,但验证字段必须是唯一的,即设置了unique=True
REQUIRED_FIELDS = ['email'] # 创建用户时必须填写的字段,除了该列表里的字段还包括password字段以及USERNAME_FIELD中的字段
EMAIL_FIELD = 'email' # 发送邮件时使用的字段
objects = UserManager()
def get_full_name(self):
return self.username
def get_short_name(self):
return self.username
class Meta:
verbose_name = "用户"
verbose_name_plural = verbose_name
问题描述
- 我在上一篇帖子中已经描述了遇到的一个坑,但是按照上一篇文章完成了数据库的migrate后,我发现在前端调用自己编写的函数去访问User模型内的所有对象时,出现了 no such table: accounts_user 的报错。
- 根据上述问题,我尝试了一些解决方案,其中包括重新makemigrations 和 migrate,但是都没有用!!!makemigrations提示我没有需要修改的地方,migrate同样提示我没有需要修改的地方!! 我尝试直接使用 pyton manage.py createsuperuser 指令创建管理员,但是在我输入用户名后,Django直接返回报错:django.db.utils.OperationalError: no such table: accounts_user
完整报错如下所示:
Traceback (most recent call last):
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\backends\sqlite3\base.py", line 383, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: no such table: accounts_user
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "manage.py", line 21, in <module>
main()
File "manage.py", line 17, in main
execute_from_command_line(sys.argv)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\core\management\__init__.py", line 381, in execute_from_command_line
utility.execute()
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\core\management\__init__.py", line 375, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\core\management\base.py", line 323, in run_from_argv
self.execute(*args, **cmd_options)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\contrib\auth\management\commands\createsuperuser.py", line 61, in execute
return super().execute(*args, **options)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\core\management\base.py", line 364, in execute
output = self.handle(*args, **options)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\contrib\auth\management\commands\createsuperuser.py", line 95, in handle
error_msg = self._validate_username(username, verbose_field_name, database)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\contrib\auth\management\commands\createsuperuser.py", line 201, in _validate_username
self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\contrib\auth\base_user.py", line 44, in get_by_natural_key
return self.get(**{self.model.USERNAME_FIELD: username})
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\models\query.py", line 402, in get
num = len(clone)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\models\query.py", line 256, in __len__
self._fetch_all()
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\models\query.py", line 1242, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\models\query.py", line 55, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\models\sql\compiler.py", line 1100, in execute_sql
cursor.execute(sql, params)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\backends\utils.py", line 99, in execute
return super().execute(sql, params)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\backends\utils.py", line 67, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\backends\utils.py", line 76, in _execute_with_wrappers
return executor(sql, params, many, context)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\utils.py", line 89, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "D:\ProgramData\Anaconda3\envs\Django2\lib\site-packages\django\db\backends\sqlite3\base.py", line 383, in execute
return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table: accounts_user
- 我还尝试stackoverflow上的一些方法(当然最多的还是makemigrations 和 migrate,但是我真的想吐槽,如果这么简单的方法能解决也不会有那么多人去问了啊。。。):比如让我删除项目内的所有migrations文件夹以及__pycache__文件夹,并且删除db.sqlite3数据库,之后重新运行makemigrations 和 migrate, 然后我就遇到了新的报错:django.db.migrations.exceptions.InconsistentMigrationHistory。
事情到这里陷入了僵局,还有人说要把settings.py里的INSTALLED_APPS内的’django.contrib.admin’语句注释掉的。
INSTALLED_APPS = [
...
# 'django.contrib.admin',
...
]
试了一下直接报错: "No install app with label admin"
评论区也有很多伙计说这样不行。
so, 难道就这样放弃了嘛?
不存在的,新建一个全新的project,让我们来测试一下!
问题分析
- 首先,在上面遇到的种种错误中,基本都是和数据库相关,自定义的UserModel是没有什么问题的,所以我新建了一个project,运行了migrate指令初始化数据库,又在其中新建了一个app,将上述自定义UserModel拷贝到accounts app下的model.py文件内,并在settings.py中进行了app的注册和AUTH_USER_MODEL的声明。完成上述任务后,我又一次运行了migrate指令,结果遇到了报错:
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency accounts.0001_initial on database 'default'.
- 仿佛有了一点头绪,这个报错提到了很关键的一点,“Migration admin.0001_initial is applied before its dependency accounts.0001_initial”,那么问题可能出现在原始的migrations文件和我们的新定义的用户模型之间存在冲突。
那么现在应该怎么办?
一个字: “删!”
解决方案
- 删除项目内和app内的migrations文件夹以及__pycache__文件夹、以及数据库db.sqlite3, 数据库是一定要删的,因为数据库里存储了默认的user模型,我尝试不删除数据库,结果还是抛出错误:InconsistentMigrationHistory;
- 运行 python manage.py makemigrations accounts, 这里的accounts是创建的用来实现用户模型的app。一定要精确到这个app,否则直接运行makemigrations的话,Django可能会提示 no change,对,就是这么坑。
运行过后,提示如下:
Migrations for 'accounts':
accounts\migrations\0001_initial.py
- Create model User
- 运行 python manage.py migrate accounts, 提示如下:可以看到django重新对所有model进行了配置。
Operations to perform:
Apply all migrations: accounts
Running migrations:
Applying contenttypes.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0001_initial... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying accounts.0001_initial... OK
- 让我们使用 python manage.py createsuperuser 来测试一下!
成功!!!
原因分析
- 数据库中的django migrations table是导致不一致的原因,仅仅从本地路径删除所有的migrations是行不通的。
- 应该从数据库中截断(truncate)migrations,并重新makemigrations和migrate;
按照Django官方的说法:用户最好在migrate项目的数据库之前应用自己的自定义模型(哪怕仅仅是声明),否则可能会遇到不可预知的错误。
参考资料
[1] stackoverflow_django.db.migrations.exceptions.InconsistentMigrationHistory
[2] stackoverflow_django.db.utils.OperationalError: no such table: auth_user
[3] 优快云_自定义Django用户模型
[4] stackoverflow_User Registration with error: no such table: auth_user
[5] Django官方文档:Customizing authentication in Django