写入 SQL 在 shell / django 中产生不同的结果。

huangapple go评论74阅读模式
英文:

Writing to SQL yields different results with shell / django

问题

class myJSONField(models.JSONField):
    def get_prep_value(self, v):
        if v is None:
            return value
        return json.dumps(v, ensure_ascii=False)

class Product(models.Model):
    data = myJSONField()
    number = models.PositiveIntegerField()

Product.create(number=1, data={"direction": "süd"}) 产生的结果:

{"direction": "süd"}

在使用 sqlite3 进行测试时:

conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
test = json.dumps({"direction": "Süd"}, ensure_ascii=False)
sql = """UPDATE test_product SET data = ? WHERE id=1"""
c.execute(sql, [test])
conn.commit()

产生的结果:

{"direction": "süd"}

我使用相同的 db.sqlite3 文件进行了两次测试。

  1. 我不理解为什么 Django 逻辑会忽略 ensure_ascii = False 标志。
  2. 如何在我的 JSONField 中获取正确的 JSON(没有转义的 ")。

编辑:我可以在我的 Django 管理界面中看到结果,我还在 SQLite 中测试了省略 ensure_ascii = False 设置的情况:

a = {"direction": "Süd", }
b = json.dumps(a)
sql = """UPDATE test_product SET data = ? WHERE id=1"""
c.execute(sql, [b])
conn.commit()

结果是:

{"direction": "Süd"}

(注意:这个结果没有包含 "!)

当通过 Django 写入数据库并通过 python manage.py shellsqlite3 直接读取时:

import sqlite3
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
c.execute('SELECT * FROM virtualShelf_product WHERE id=1')
res = c.fetchall()

[(1, 1, '"{"direction": "süd"}"')]

当通过 Django ORM 读取同一产品时:

p = Product.objects.get(id=1)
p.data
'{"direction": "süd"}'
英文:

I have a simple model:

class myJSONField(models.JSONField):
    def get_prep_value(self, v):
        if v is None:
            return value
        return json.dumps(v, ensure_ascii = False)

class Product(models.Model):
    data                = myJSONField()
    number              = models.PositiveIntegerField()

Product.create(number = 1, data = {"direction": "süd"}) yields:

"{\"direction\": \"s\u00fcd\"}"

Testing with sqlite3:

conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
test = json.dumps({"direction": "Süd"}, ensure_ascii = False)
sql = ("""UPDATE test_product SET data = ? WHERE id=1""")
c.execute(sql, [test])
conn.commit()

yields:

{"direction": "süd"}

I am using the same db.sqlite3 file for both tests.

  1. I do not understand why the ensure_ascii = False flag is ignored by the django logic.
  2. How can I obtain a correct JSON in my JSONField (without the escaped ")?

Edit: I can see the result in my django admin and I also tested omitting the ensure_ascii = False setting in SQLite:

a = {"direction": "Süd", }
b = json.dumps(a)#, ensure_ascii = False)
sql = ("""UPDATE test_product SET data = ? WHERE id=1""")
c.execute(sql, [b])
conn.commit()

which leads to:

{"direction": "S\u00fcd"}

(remark: no " around that one!)

When writing to the database with Django and reading via python manage.py shell and sqlite3 directly:

import sqlite3
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
c.execute('SELECT * FROM virtualShelf_product WHERE id=1')
res = c.fetchall()


[(1, 1, '"{\\"direction\\": \\"s\\u00fcd\\"}"')]

when reading the same product via Django ORM:

p = Product.objects.get(id=1)
p.data
'{"direction": "süd"}'

答案1

得分: 1

通过覆盖get_prep_value方法,您进行了两次序列化。首先是在您的myJSONField中,然后是由get_db_prep_value函数进行的第二次序列化,该函数使用get_prep_value作为辅助函数。

实际上,如果我们检查源代码(GitHub),我们可以看到以下内容:

def get_db_prep_value(self, value, connection, prepared=False):
    if not prepared:
        value = self.get_prep_value(value)
    # ...
    return connection.ops.adapt_json_value(value, self.encoder)

根据连接本身的不同,Django可以发送例如BSON数据块或JSON数据,除了PostgreSQL,它在这里对数据进行编码:

def adapt_json_value(self, value, encoder):
    return json.dumps(value, cls=encoder)

如果您覆盖get_prep_value,它会将其转换为字符串,然后再次对其进行JSON序列化。

您可以实现一个自定义后端,传递ensure_ascii=False,或者一个简单的解决方法是通过monkey patching注入这个参数。例如,在AppConfig中:

# app_name/apps.py

from django.apps import AppConfig
import json

class AppConfig(AppConfig):
    name = "app_name"

    def ready(self):
        def adapt_json_value(self, value, encoder):
            return json.dumps(value, ensure_ascii=False, cls=encoder)

        from django.db.backends.base.operations import BaseDatabaseOperations

        BaseDatabaseOperations.adapt_json_value = adapt_json_value

当然,然后我们可以省略myJSONField中的get_prep_value,这实际上使myJSONField相对于JSONField失去了作用。

英文:

By overriding get_prep_value, you are serializing twice. Indeed, once in your myJSONField, and a second time by the get_db_prep_value function which uses get_prep_value as a helper function.

Indeed, if we inspect the source code&nbsp;<sup>[GitHub]</sup>, we see:

> <pre><code>def get_db_prep_value(self, value, connection, prepared=False):
> if not prepared:
> value = self.get_prep_value(value)
> # &hellip;
> return <b>connection.ops.adapt_json_value(value, self.encoder)</b></code></pre>

depending on the connection itself, Django can then send for example a BSON blob, or a JSON. Except for PostgreSQL, it encodes the data with&nbsp;<sup>[GitHub]</sup>:

<pre><code>def adapt_json_value(self, value, encoder):
return <b>json.dumps(</b>value, cls=encoder<b>)</b></code></pre>

if you override get_prep_value, it converts it to a string, and then you JSON serialize it a second time.

One can implement a custom backend that would pass ensure_ascii=False, a poor man's solution would be to just inject this through monkey patching. For example in the AppConfig:

<pre><code># <em>app_name</em>/apps.py

from django.apps import AppConfig

class AppConfig(AppConfig):
name = &quot;<em>app_name</em>&quot;

def ready(self):
    def adapt_json_value(self, value, encoder):
        return json.dumps(value, &lt;b&gt;ensure_ascii=False&lt;/b&gt;, cls=encoder)

    from django.db.backends.base.operations import BaseDatabaseOperations

    BaseDatabaseOperations&lt;b&gt;.adapt_json_value = adapt_json_value&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;

of course we then omit the get_prep_value in the myJSONField, which also basically nullifies myJSONField over JSONField.

huangapple
  • 本文由 发表于 2023年7月23日 20:07:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76748150.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定