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

从Django 3.2升级到5.2后关联对象查询出现不存在错误

Django 5.2中managed=False模型关联访问ObjectDoesNotExist问题解决

问题背景

在基于PostgreSQL 17的非Django项目中,仅使用Django简化单元测试,定义了managed=False的模型。Django 3.2版本中,通过原生SQL插入数据后可正常访问模型关联对象;但升级到5.2后,执行print(a[0].bar)时触发ObjectDoesNotExist错误,a[0].bar_id == b[0].id断言却能成功。尝试提交Django连接、重新打开连接等操作均无效,未使用Django测试框架,采用unittest.TestCase配合自定义初始化函数。

模型代码

class Bar(models.Model):
    internal_type = models.TextField()

    class Meta:
        managed = False
        db_table = 'bar'


class Foo(models.Model):
    bar = models.ForeignKey('Bar', models.CASCADE, db_column='bar', related_name='foo')

    class Meta:
        managed = False
        db_table = 'foo'

测试代码

def test_foo_bar(self):
    from django_app import models

    self.cursor.execute("INSERT INTO bar(internal_type) VALUES ('test') RETURNING id;")
    self.cursor.execute("INSERT INTO foo(bar) VALUES (%s)", (self.cursor.fetchone()[0],))
    self.cursor.connection.commit()

    from django.db import connection
    connection.force_debug_cursor = True
    a = models.Foo.objects.all()
    b = models.Bar.objects.all()

    assert a[0].bar_id == b[0].id  # 此断言成功
    print(a[0].bar)  # 触发ObjectDoesNotExist错误

表结构

create table if not exists bar (
    id bigserial primary KEY,
    internal_type text
);

create table if not exists foo (
    id bigserial primary KEY,
    bar bigint references bar(id)
);

查询日志

(0.000) SELECT "foo"."id", "foo"."bar" FROM "foo" LIMIT 1; args=(); alias=default
(0.000) SELECT "bar"."id", "bar"."internal_type" FROM "bar" LIMIT 1; args=(); alias=default
(0.000) SELECT "foo"."id", "foo"."bar" FROM "foo" LIMIT 1; args=(); alias=default

解决方法

  • 重置Django数据库连接
    原生SQL插入数据后,Django ORM的连接缓存可能未同步数据。直接关闭并重建连接,让ORM重新读取最新数据:

    from django.db import connection
    # 原生插入并提交后执行
    connection.close()
    # 重新查询模型数据
    a = models.Foo.objects.all()
    b = models.Bar.objects.all()
    
  • 显式指定关联字段的to_field
    Django 5.x对managed=False模型的字段映射校验更严格,显式指定关联的主键字段可避免歧义:

    # 修改Foo模型的外键字段
    bar = models.ForeignKey('Bar', models.CASCADE, db_column='bar', related_name='foo', to_field='id')
    
  • 统一使用Django的连接执行原生SQL
    确保原生操作和ORM使用同一个数据库连接,避免数据感知不一致:

    from django.db import connection
    # 使用Django连接的游标执行原生插入
    with connection.cursor() as cursor:
        cursor.execute("INSERT INTO bar(internal_type) VALUES ('test') RETURNING id;")
        bar_id = cursor.fetchone()[0]
        cursor.execute("INSERT INTO foo(bar) VALUES (%s)", (bar_id,))
    connection.commit()
    
  • 重新获取模型实例,绕过缓存
    已加载的模型实例可能缓存了无效的关联状态,重新查询实例即可:

    # 不要复用之前的a[0],重新查询最新数据
    foo_instance = models.Foo.objects.get(id=a[0].id)
    print(foo_instance.bar)
    

关于测试模式的适用性

这种测试模式依然适用,只是Django 5.x对managed=False模型的处理逻辑更严谨,需要确保原生SQL操作与ORM使用同一连接,并且及时刷新ORM的状态缓存,避免因数据不同步导致的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 01:27:18