django 升级后,原始sql查询将json字段作为字符串返回到postgres上

fslejnso  于 2022-11-18  发布在  Go
关注(0)|答案(4)|浏览(140)

我正在将Django应用程序从2.2.7升级到3.1.3。该应用程序使用Postgres 12和psycopg2 2.8.6。
我按照说明将所有的django.contrib.postgres.fields.JSONField引用更改为django.db.models.JSONField,并进行和运行迁移。这不会对我的模式产生任何更改(这很好)。
然而,当我执行原始查询时,这些jsonb列的数据在某个时候作为文本返回,或者转换为文本。

import os, django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "big_old_project.settings")
django.setup()

with connection.cursor() as c:
    c.execute("select name, data from tbl where name=%s", ("rex",))
    print(c.description)
    for row in c.fetchall():
        for col in row:
            print(f"{type(col)} => {col!r}")

(Column(name='name', type_code=1043), Column(name='data', type_code=3802))
<class 'str'> => 'rex'
<class 'str'> => '{"toy": "bone"}'

[edit]使用原始连接可以得到预期的结果:

conn = psycopg2.connect("dbname=db user=x password=z")
with conn.cursor() as c:
    ...
<class 'str'> => 'rex'
<class 'dict'> => {'toy': 'bone'}

尝试“注册”适配器的老把戏不起作用,而且无论如何也不应该需要。

import psycopg2.extras
psycopg2.extras.register_json(oid=3802, array_oid=3807, globally=True)

这个应用程序有很多历史,所以也许有什么东西在踩psycopg2的脚趾?我到目前为止找不到任何东西,并评论了所有似乎无关紧要的东西。
查看发行说明也没有帮助,我确实使用了其他postgres字段,所以我不能从我的模型中删除所有对contrib.postgres.fields的引用。
任何关于为什么会发生这种情况的想法都将受到极大的赞赏。

ijxebb2r

ijxebb2r1#

为了补充@ AndrewBacker的回答,这显然是故意的。
修正了在PostgreSQL上使用自定义decoder#31956)按JSONField排序和分组时QuerySet.order_by()崩溃。因此,使用原始SQL获取JSONField现在返回字符串而不是预加载的数据。在这种情况下,您需要显式调用json.loads()
在一个bugfix发行版中发现API不兼容的改变是令人惊讶的。现在我将添加json.loads()调用,因为正如已经提到的,不能保证::json解决方案不会中断!

dgiusagp

dgiusagp2#

好的,这似乎是他们在django 3.1.1中引入的一个修改,为了修复其他bug。它从底层连接中取消了jsonb转换器的注册,这在IMO中是很糟糕的。
Django问题:第一个“修复”破坏了这个基本功能沿着拒绝了原始sql的用例,第二个是声明破坏无效。

  1. QuerySet.order_by() chained with values() crashes on JSONField
  2. TypeError loading data in JSONField if DB has native JSON support
    要解决这个问题,你可以制作你自己的原始游标,django不会搞砸,或者在原始sql中转换你的字段。至少,直到他们也破坏它!
SELECT 
    data::json,  -- db type is jsonb
    other_fields
FROM
    table
hts6caw3

hts6caw33#

谢谢你的这篇有帮助的文章!我的解决方案看起来像这样:

def call_database(query, vars=None):
    conn = connections['default'] #database name
    conn.ensure_connection()
    with conn.connection.cursor() as cursor:
        psycopg2.extras.register_default_jsonb(conn_or_curs=cursor)
        cursor.execute(query, vars)
        row = cursor.fetchall()
    return row
70gysomp

70gysomp4#

@Sascha Rau,你的帖子真的很有帮助。为了可能保存别人一点时间,我注意到这对你提出的解决方案至关重要:

conn = connections[settings.UX_DATABASE_NAME]
conn.ensure_connection()

另外,对于Django项目数据库的这个设置配置:

DATABASES = {
    "default": {...}
}

可以使用conn = connections['default']引用与默认项目数据库的连接

相关问题