Skip to content

Commit dd8caac

Browse files
committed
Fix populating relation on self-referential FK in model_to_dict.
Fixes #3006
1 parent dee3e85 commit dd8caac

File tree

2 files changed

+48
-2
lines changed

2 files changed

+48
-2
lines changed

playhouse/shortcuts.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,14 @@ def model_to_dict(model, recurse=True, backrefs=False, only=None,
7979
field_data = model.__data__.get(field.name)
8080
if isinstance(field, ForeignKeyField) and recurse:
8181
if field_data is not None:
82-
seen.add(field)
8382
rel_obj = getattr(model, field.name)
8483
field_data = model_to_dict(
8584
rel_obj,
8685
recurse=recurse,
8786
backrefs=backrefs,
8887
only=only,
8988
exclude=exclude,
90-
seen=seen,
89+
seen=seen | set((field,)),
9190
max_depth=max_depth - 1)
9291
else:
9392
field_data = None

tests/shortcuts.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ class Item(TestModel):
8383
basket = ForeignKeyField(Basket)
8484

8585

86+
class NodeTag(TestModel):
87+
tag = TextField()
88+
89+
class Node(TestModel):
90+
name = TextField()
91+
tag = ForeignKeyField(NodeTag)
92+
parent = ForeignKeyField('self', null=True, backref='children')
93+
94+
8695
class TestModelToDict(ModelTestCase):
8796
database = get_in_memory_db()
8897
requires = [User, Tweet, Tag, TweetTag]
@@ -91,6 +100,44 @@ def setUp(self):
91100
super(TestModelToDict, self).setUp()
92101
self.user = User.create(username='peewee')
93102

103+
@requires_models(Node, NodeTag)
104+
def test_self_referential(self):
105+
a, b = [NodeTag.create(tag=tag) for tag in 'ab']
106+
root = Node.create(name='root', tag=a)
107+
n1 = Node.create(name='n1', parent=root, tag=a)
108+
n2 = Node.create(name='n2', parent=root, tag=b)
109+
Parent = Node.alias('parent')
110+
ParentTag = NodeTag.alias('parent_tag')
111+
112+
def assertSerialization(n, expected):
113+
obj = (Node
114+
.select(Node, NodeTag, Parent, ParentTag)
115+
.join_from(Node, NodeTag, JOIN.LEFT_OUTER)
116+
.join_from(Node, Parent, JOIN.LEFT_OUTER)
117+
.join_from(Parent, ParentTag, JOIN.LEFT_OUTER)
118+
.where(Node.name == n)
119+
.first())
120+
121+
self.assertEqual(model_to_dict(obj, recurse=True), expected)
122+
123+
assertSerialization('n1', {
124+
'id': n1.id,
125+
'name': 'n1',
126+
'parent': {'id': root.id, 'name': 'root',
127+
'tag': {'id': a.id, 'tag': 'a'}},
128+
'tag': {'id': a.id, 'tag': 'a'}})
129+
assertSerialization('n2', {
130+
'id': n2.id,
131+
'name': 'n2',
132+
'parent': {'id': root.id, 'name': 'root',
133+
'tag': {'id': a.id, 'tag': 'a'}},
134+
'tag': {'id': b.id, 'tag': 'b'}})
135+
assertSerialization('root', {
136+
'id': root.id,
137+
'name': 'root',
138+
'parent': None,
139+
'tag': {'id': a.id, 'tag': 'a'}})
140+
94141
def test_simple(self):
95142
with self.assertQueryCount(0):
96143
self.assertEqual(model_to_dict(self.user), {

0 commit comments

Comments
 (0)