You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

连接Django Admin时遭遇'last_login字段应为数字但传入datetime'错误的解决方案咨询

解决Django Admin登录时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())

验证解决方案

  1. 运行python manage.py makemigrations和python manage.py migrate(自定义字段本质还是IntegerField,不会修改数据库结构)
  2. 尝试登录Django Admin,应该不会再出现类型错误
  3. 查看数据库的users表,last_login字段依然存储的是Unix时间戳整数,完全符合遗留系统要求

这样既不用修改遗留表的字段类型,又能让Django的认证系统和Admin正常工作。

内容的提问来源于stack exchange,提问作者mginside

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 06:48:23