Skip to content

Commit cad6447

Browse files
authored
improvements to to_dict to support nested objects (piccolo-orm#222)
1 parent 1daccfa commit cad6447

File tree

2 files changed

+62
-8
lines changed

2 files changed

+62
-8
lines changed

piccolo/table.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -430,16 +430,34 @@ def to_dict(self, *columns: Column) -> t.Dict[str, t.Any]:
430430
{'id': 1, 'title': 'Guido'}
431431
432432
"""
433+
# Make sure we're only looking at columns for the current table. If
434+
# someone passes in a column for a sub table (for example
435+
# `Band.manager.name`), we need to add `Band.manager` so the nested
436+
# value appears in the output.
437+
filtered_columns = []
438+
for column in columns:
439+
if column._meta.table == self.__class__:
440+
filtered_columns.append(column)
441+
else:
442+
for parent_column in column._meta.call_chain:
443+
if parent_column._meta.table == self.__class__:
444+
filtered_columns.append(parent_column)
445+
break
446+
433447
alias_names = {
434448
column._meta.name: getattr(column, "alias", None)
435-
for column in columns
449+
for column in filtered_columns
436450
}
437451

438452
output = {}
439-
for column in columns or self._meta.columns:
453+
for column in filtered_columns if columns else self._meta.columns:
454+
value = getattr(self, column._meta.name)
455+
if isinstance(value, Table):
456+
value = value.to_dict(*columns)
457+
440458
output[
441459
alias_names.get(column._meta.name) or column._meta.name
442-
] = getattr(self, column._meta.name)
460+
] = value
443461
return output
444462

445463
def __setitem__(self, key: str, value: t.Any):

tests/table/instance/test_to_dict.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from tests.base import DBTestCase
2-
from tests.example_app.tables import Manager
2+
from tests.example_app.tables import Band, Manager
33

44

55
class TestToDict(DBTestCase):
@@ -11,7 +11,26 @@ def test_to_dict(self):
1111

1212
instance = Manager.objects().first().run_sync()
1313
dictionary = instance.to_dict()
14-
self.assertEqual(dictionary, {"id": 1, "name": "Guido"})
14+
self.assertDictEqual(dictionary, {"id": 1, "name": "Guido"})
15+
16+
def test_nested(self):
17+
"""
18+
Make sure that `to_dict` works correctly, when the object contains
19+
nested objects.
20+
"""
21+
self.insert_row()
22+
23+
instance = Band.objects(Band.manager).first().run_sync()
24+
dictionary = instance.to_dict()
25+
self.assertDictEqual(
26+
dictionary,
27+
{
28+
"id": 1,
29+
"name": "Pythonistas",
30+
"manager": {"id": 1, "name": "Guido"},
31+
"popularity": 1000,
32+
},
33+
)
1534

1635
def test_filter_rows(self):
1736
"""
@@ -21,9 +40,26 @@ def test_filter_rows(self):
2140

2241
instance = Manager.objects().first().run_sync()
2342
dictionary = instance.to_dict(Manager.name)
24-
self.assertEqual(dictionary, {"name": "Guido"})
43+
self.assertDictEqual(dictionary, {"name": "Guido"})
44+
45+
def test_nested_filter(self):
46+
"""
47+
Make sure that `to_dict` works correctly with nested objects and
48+
filtering.
49+
"""
50+
self.insert_row()
51+
52+
instance = Band.objects(Band.manager).first().run_sync()
53+
dictionary = instance.to_dict(Band.name, Band.manager.id)
54+
self.assertDictEqual(
55+
dictionary,
56+
{
57+
"name": "Pythonistas",
58+
"manager": {"id": 1},
59+
},
60+
)
2561

26-
def test_to_dict_aliases(self):
62+
def test_aliases(self):
2763
"""
2864
Make sure that `to_dict` works correctly with aliases.
2965
"""
@@ -33,4 +69,4 @@ def test_to_dict_aliases(self):
3369
dictionary = instance.to_dict(
3470
Manager.id, Manager.name.as_alias("title")
3571
)
36-
self.assertEqual(dictionary, {"id": 1, "title": "Guido"})
72+
self.assertDictEqual(dictionary, {"id": 1, "title": "Guido"})

0 commit comments

Comments
 (0)