#python #flask #sqlalchemy #many-to-many
#python #flask #sqlalchemy #многие-ко-многим
Вопрос:
Я пытаюсь создать самоссылающуюся модель «многие ко многим» в Flask. Я уже проверил множество примеров в Интернете, но в моем случае я хотел бы использовать таблицу, отображаемую с объектами, для обработки взаимосвязи. Также, поскольку я использую Flask с marshmallow, я хотел бы включить прокси-серверы ассоциации в свою таблицу, чтобы упростить сериализацию модели. Каждый пример, который я нашел, использует backref
но я хотел сделать это с back_populates
ради удобства чтения. В настоящее время я не уверен, возможно ли это. Пожалуйста, найдите ниже мой минимальный пример, чтобы продемонстрировать проблему.
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
class NodeRelation(Base):
__tablename__ = "node_relation"
parent_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
parent = relationship('Node',
primaryjoin="NodeRelation.parent_id == node.c.id",
back_populates='parent_childs',
#foreign_keys=parent_id
)
child_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
child = relationship('Node',
primaryjoin="NodeRelation.child_id == node.c.id",
back_populates='child_parents',
#foreign_keys=child_id
)
def __init__(self, parent=None, child=None, **kwargs):
super().__init__(**kwargs)
if child:
self.child = child
if parent:
self.parent = parent
def __repr__(self):
return "(parent_id: %s, child_id: %s)" % (self.parent_id, self.child_id)
class Node(Base):
__tablename__ = "node"
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String())
parent_childs = relationship('NodeRelation',
primaryjoin="Node.id==node_relation.c.parent_id",
back_populates='parent',
cascade='all, delete',
#foreign_keys=NodeRelation.parent_id
)
parents = association_proxy('parent_childs', 'parent',
creator=lambda parent: NodeRelation(parent=parent))
child_parents = relationship('NodeRelation',
primaryjoin="Node.id==node_relation.c.child_id",
back_populates='child',
cascade='all, delete',
#foreign_keys=NodeRelation.child_id
)
childs = association_proxy('child_parents', 'child',
creator=lambda child: NodeRelation(child=child))
def __init__(self, title, **kwargs):
super().__init__(**kwargs)
self.title = title
def __repr__(self):
return "(id: %s, title: %s, childs: %s)" % (self.id, self.title, self.childs)
Base.metadata.create_all(engine)
n1 = Node("First")
n2 = Node("Second")
"""
# This is failing with: NOT NULL constraint failed: node_relation.parent_id
n1.childs.append(n2)
session.add(n1)
session.add(n2)
session.commit()
"""
# This one is working
c = NodeRelation(n1, n2)
session.add(n1)
session.add(n2)
session.add(c)
# Node 1 and Node 2 exists
q = session.query(NodeRelation).all()
print(q)
# This is failing with infinite recursion when childs property is displayed.
q2 = session.query(Node).all()
print(q2)
Если я использую n1.childs.append()
, я получаю ошибку null contsraint. Если я напрямую создаю объект mapper с помощью n1 и n2, он работает нормально, но как только я получаю доступ к childs
свойству, я получаю бесконечную рекурсию.
Редактировать:
Я выяснил, что пользовательский creator lambda вызывает ошибку добавления. Итак, обновленный код выглядит следующим образом:
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
class NodeRelation(Base):
__tablename__ = "node_relation"
parent_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
parent = relationship('Node',
primaryjoin="NodeRelation.parent_id == node.c.id",
back_populates='parent_childs',
#foreign_keys=parent_id
)
child_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
child = relationship('Node',
primaryjoin="NodeRelation.child_id == node.c.id",
back_populates='child_parents',
#foreign_keys=child_id
)
def __init__(self, parent=None, child=None, **kwargs):
super().__init__(**kwargs)
if child:
self.child = child
if parent:
self.parent = parent
def __repr__(self):
return "(parent_id: %s, child_id: %s)" % (self.parent_id, self.child_id)
class Node(Base):
__tablename__ = "node"
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String())
parent_childs = relationship('NodeRelation',
primaryjoin="Node.id==node_relation.c.parent_id",
back_populates='parent',
cascade='all, delete',
#foreign_keys=NodeRelation.parent_id
)
parents = association_proxy('parent_childs', 'parent')
child_parents = relationship('NodeRelation',
primaryjoin="Node.id==node_relation.c.child_id",
back_populates='child',
cascade='all, delete',
#foreign_keys=NodeRelation.child_id
)
childs = association_proxy('child_parents', 'child')
def __init__(self, title, **kwargs):
super().__init__(**kwargs)
self.title = title
def __repr__(self):
return "(id: %s, title: %s, childs: %s)" % (self.id, self.title, self.childs)
Base.metadata.create_all(engine)
n1 = Node("First")
n2 = Node("Second")
# This is failing with: NOT NULL constraint failed: node_relation.parent_id
n1.childs.append(n2)
session.add(n1)
session.add(n2)
session.commit()
# Node 1 and Node 2 exists
q = session.query(NodeRelation).all()
print(q)
# This is failing with infinite recursion when childs property is displayed.
q2 = session.query(Node).all()
print(q2)
Итак, единственная проблема заключается в том, что, когда я пытаюсь получить доступ к childs
свойству, я получаю бесконечную рекурсию. Я думаю, я что-то перепутал с отношениями.