Skip to content
This repository has been archived by the owner on Jun 23, 2021. It is now read-only.

Commit

Permalink
Cast value to text on PostgreSQL if custom decoder is used (#14)
Browse files Browse the repository at this point in the history
Fix #5.

Instead of registering a no-op `loads()` to avoid psycopg2's automatic decoding, we cast the value into `text`. As [the docs](http://initd.org/psycopg/docs/extras.html#json-adaptation) say, it is an efficient operation that doesn’t involve a copy.
  • Loading branch information
laymonage authored and adamchainz committed Aug 18, 2019
1 parent 1fc3681 commit d781afd
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 18 deletions.
29 changes: 11 additions & 18 deletions jsonfield/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,15 @@ def db_type(self, connection):
def from_db_value(self, value, expression, connection):
if value is None:
return None
return json.loads(value, **self.decoder_kwargs)
elif connection.vendor == 'postgresql' and self.decoder_kwargs.get('cls') is None:
return value
return json.loads(value, **self.decoder_kwargs)
else:
def from_db_value(self, value, expression, connection, context):
if value is None:
return None
elif connection.vendor == 'postgresql' and self.decoder_kwargs.get('cls') is None:
return value
return json.loads(value, **self.decoder_kwargs)

def get_db_prep_value(self, value, connection=None, prepared=None):
Expand All @@ -102,6 +106,12 @@ def get_prep_value(self, value):
return None
return json.dumps(value, **self.encoder_kwargs)

def select_format(self, compiler, sql, params):
if compiler.connection.vendor == 'postgresql' and self.decoder_kwargs.get('cls') is not None:
# Avoid psycopg2's automatic decoding to allow custom decoder
return '%s::text' % sql, params
return super().select_format(compiler, sql, params)

def value_to_string(self, obj):
return self.value_from_object(obj)

Expand Down Expand Up @@ -185,20 +195,3 @@ def validate(self, value, model_instance):
v(item)
else:
v(value)


def configure_database_connection(connection, **kwargs):
if connection.vendor != 'postgresql':
return

# Ensure that psycopg does not do JSON decoding under the hood
# We want to be able to do our own decoding with our own options
import psycopg2.extras
if hasattr(psycopg2.extras, 'register_default_jsonb'):
psycopg2.extras.register_default_jsonb(
connection.connection,
globally=False,
loads=lambda x: x)


connection_created.connect(configure_database_connection)
9 changes: 9 additions & 0 deletions jsonfield/tests/jsonfield_test_app/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.postgres.fields import JSONField as PostgresJSONField
from django.db import models
from jsonfield.fields import JSONField

Expand Down Expand Up @@ -29,3 +30,11 @@ class CallableDefaultModel(models.Model):

class Meta:
app_label = 'jsonfield'


class PostgresParallelModel(models.Model):
library_json = JSONField()
postgres_json = PostgresJSONField()

class Meta:
app_label = 'jsonfield'
10 changes: 10 additions & 0 deletions jsonfield/tests/test_fields.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from unittest import skipUnless

from django.db import connection
from django.core import serializers
from django.test import TestCase as DjangoTestCase
from django.utils.encoding import force_text
Expand Down Expand Up @@ -162,6 +165,13 @@ def test_serializing(self):
JSONFieldTestModel.objects.all())
self.assertIn('"json": "[\\"foo\\"]"', serialized)

@skipUnless(connection.vendor == 'postgresql', 'PostgreSQL-specific test')
def test_work_parallel_with_postgres_json_field(self):
data = {'foo': 'bar'}
obj = PostgresParallelModel.objects.create(library_json=data, postgres_json=data)
obj = PostgresParallelModel.objects.get(id=obj.id)
self.assertEqual(obj.library_json, obj.postgres_json)


class SavingModelsTest(DjangoTestCase):
def test_saving_null(self):
Expand Down

0 comments on commit d781afd

Please sign in to comment.