从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
相关产品推荐
相关产品推荐

