Skip to content

Commit 4467a00

Browse files
authored
JSON capable cursor (#112)
* Adjusted query.py to use the same technique as json_cursor to correctly handle jsonb columns in Django 3.1.1+ * Eliminated the changes that were working around test result diffs in Dj3.1+
1 parent 2ed7b41 commit 4467a00

File tree

2 files changed

+20
-19
lines changed

2 files changed

+20
-19
lines changed

querybuilder/query.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from django.apps import apps
99
get_model = apps.get_model
1010
import six
11-
11+
import json
12+
import psycopg2
1213

1314
from querybuilder.fields import FieldFactory, CountField, MaxField, MinField, SumField, AvgField
1415
from querybuilder.helpers import set_value_for_keypath, copy_instance
@@ -640,7 +641,14 @@ def get_cursor(self):
640641
:rtype: :class:`CursorDebugWrapper <django:django.db.backends.util.CursorDebugWrapper>`
641642
:returns: A database cursor
642643
"""
643-
return self.connection.cursor()
644+
645+
# From Django 3.1 forward, json columns in raw select statements return a string of json instead of a
646+
# json type such as a dict or list. But we can tell psycopg2 to put the
647+
# json.loads() call back in place. Technically we would only need this addition for cursors being used
648+
# for a SELECT, but it should not cause any issues for other operations.
649+
cursor = self.connection.cursor()
650+
psycopg2.extras.register_default_jsonb(conn_or_curs=cursor.cursor, loads=json.loads)
651+
return cursor
644652

645653
def from_table(self, table=None, fields='*', schema=None, **kwargs):
646654
"""

querybuilder/tests/json_tests.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import unittest
2-
from django import VERSION
32
from django.test.utils import override_settings
43
from querybuilder.fields import JsonField
54
from querybuilder.query import Query, JsonQueryset
@@ -47,11 +46,7 @@ def test_one(self):
4746
)
4847
)
4948

50-
# Django 3.1 changes the raw queryset behavior so querybuilder isn't going to change that behavior
51-
if self.is_31_or_above():
52-
self.assertEqual(query.select(), [{'my_two_alias': '"two"'}])
53-
else:
54-
self.assertEqual(query.select(), [{'my_two_alias': 'two'}])
49+
self.assertEqual(query.select(), [{'my_two_alias': 'two'}])
5550

5651
query = Query().from_table(MetricRecord, fields=[one_field]).where(**{
5752
one_field.get_where_key(): '1'
@@ -64,11 +59,7 @@ def test_one(self):
6459
)
6560
)
6661

67-
# Django 3.1 changes the raw queryset behavior so querybuilder isn't going to change that behavior
68-
if self.is_31_or_above():
69-
self.assertEqual(query.select(), [{'my_one_alias': '1'}])
70-
else:
71-
self.assertEqual(query.select(), [{'my_one_alias': 1}])
62+
self.assertEqual(query.select(), [{'my_one_alias': 1}])
7263

7364
query = Query().from_table(MetricRecord, fields=[one_field]).where(**{
7465
one_field.get_where_key(): '2'
@@ -82,12 +73,14 @@ def test_one(self):
8273
)
8374
self.assertEqual(query.select(), [])
8475

85-
def is_31_or_above(self):
86-
if VERSION[0] == 3 and VERSION[1] >= 1:
87-
return True
88-
elif VERSION[0] > 3:
89-
return True
90-
return False
76+
# Currently unused (but maybe again sometime) function to check django version for test results
77+
# from django import VERSION
78+
# def is_31_or_above(self):
79+
# if VERSION[0] == 3 and VERSION[1] >= 1:
80+
# return True
81+
# elif VERSION[0] > 3:
82+
# return True
83+
# return False
9184

9285

9386
@override_settings(DEBUG=True)

0 commit comments

Comments
 (0)