连接Django Admin时遭遇'last_login字段应为数字但传入datetime'错误的解决方案咨询
last_login字段类型不匹配的问题 我明白你的痛点——遗留系统的用户表用整数存Unix时间戳作为last_login,但Django默认期望这个字段是datetime类型,导致登录Admin时抛出Field 'last_login' expected a number but got datetime错误。不用改数据库字段类型,我们可以通过自定义字段类型和修正信号绑定来完美解决这个问题。
核心原因
Django的认证系统(包括Admin)内部逻辑默认认为last_login是datetime字段,当你在模型中定义为IntegerField后,Django尝试将其当作datetime处理时就会触发类型不匹配报错。我们需要让Django在ORM层自动完成datetime和Unix时间戳整数的转换,同时保持数据库存储的字段类型不变。
方案一:自定义Unix时间戳字段(推荐)
创建一个继承自IntegerField的自定义字段,让它自动处理datetime和整数之间的双向转换,这样Django内部拿到的始终是datetime对象,数据库存储的依然是Unix时间戳整数,完美适配你的遗留表结构。
1. 定义自定义字段
在你的app中创建字段类(可以放在models.py或者单独的fields.py文件中):
import datetime from django.db import models class UnixTimestampIntegerField(models.IntegerField): def to_python(self, value): """将数据库中的整数转换为Python的datetime对象""" if value is None: return None if isinstance(value, datetime.datetime): return value try: # 把Unix时间戳转换为本地datetime return datetime.datetime.fromtimestamp(int(value)) except (ValueError, TypeError): # 转换失败时返回原始值,避免程序崩溃 return value def get_prep_value(self, value): """将Python的datetime对象转换为数据库存储的整数""" if value is None: return None if isinstance(value, datetime.datetime): # 把datetime转换为Unix时间戳整数 return int(value.timestamp()) # 如果传入的已经是整数,直接返回 return super().get_prep_value(value)
2. 更新自定义用户模型
把原来的last_login = models.IntegerField(...)替换为我们的自定义字段:
class Account(AbstractBaseUser): # ... 其他所有字段保持不变 ... # 替换原来的last_login字段定义 last_login = UnixTimestampIntegerField(null=True, blank=True) # ... 其他字段和方法保持不变 ...
方案二:重写模型的last_login属性(备选)
如果你不想自定义字段,也可以通过重写模型的属性存取方法来实现转换逻辑:
import datetime from django.db import models from django.contrib.auth.models import AbstractBaseUser, BaseUserManager class Account(AbstractBaseUser): # ... 其他字段保持不变 ... last_login = models.IntegerField(null=True, blank=True) # ... 其他字段 ... def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 用私有变量存储数据库中的原始整数 self._raw_last_login = self.last_login @property def last_login(self): """读取时返回datetime对象""" if self._raw_last_login is None: return None return datetime.datetime.fromtimestamp(self._raw_last_login) @last_login.setter def last_login(self, value): """赋值时自动转换为Unix时间戳整数""" if isinstance(value, datetime.datetime): self._raw_last_login = int(value.timestamp()) elif isinstance(value, int): self._raw_last_login = value else: self._raw_last_login = None # 同步到模型的字段值,确保ORM能正确保存 self.__dict__['last_login'] = self._raw_last_login
修正登录信号的绑定错误
我注意到你代码中绑定登录信号的方式有误:
user_logged_in.send(update_last_login)
send()是主动发送信号的方法,我们需要用connect()把接收器绑定到信号上,修改为:
user_logged_in.connect(update_last_login)
这样用户登录时才会触发update_last_login函数更新last_login值。
另外,你的get_current_unix_timestamp()函数可以优化为更跨平台的写法(strftime('%s')在Windows系统可能不支持):
import datetime def get_current_unix_timestamp(): return int(datetime.datetime.now().timestamp())
验证解决方案
- 运行
python manage.py makemigrations和python manage.py migrate(自定义字段本质还是IntegerField,不会修改数据库结构) - 尝试登录Django Admin,应该不会再出现类型错误
- 查看数据库的
users表,last_login字段依然存储的是Unix时间戳整数,完全符合遗留系统要求
这样既不用修改遗留表的字段类型,又能让Django的认证系统和Admin正常工作。
内容的提问来源于stack exchange,提问作者mginside

