I want to delete the parent row if the associated rows in child tables have been removed.
class Child(Base):
__tablename__ = "children"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("parents.id", ondelete='CASCADE'))
class Parent(Base):
__tablename__ = "parents"
id = Column(Integer, primary_key=True)
child = relationship(Child, backref="parent", passive_deletes=True)
If I remove the child
child_obj = session.query(Child).first()
session.delete(child_obj)
session.commit()
It does delete the child obj but parent remains as it is. I want to remove the parent as well using cascading.
You can read this thread:
Linking Relationships with Backref
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
addresses = relationship("Address", backref="user")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey('user.id'))
And you can define it in your child class:
parent = relationship(Parent, backref=backref("children", cascade="all,delete"))
You could do this for simple* cases by creating a listener that intercepts deletions of Child instances and deletes the parent if there are no other children.
import sqlalchemy as sa
#sa.event.listens_for(sessionmaker, 'persistent_to_deleted')
def intercept_persistent_to_deleted(session, object_):
# We are only interested in instances of Child.
if not isinstance(object_, Child):
return
p = object_.parent
# Handle null parents.
if p is None:
return
cs = session.query(Child).filter(Child.parent == p).count()
if cs == 0:
session.delete(p)
* I would recommend thorough testing if your code is doing things like deleting children and then creating new children with the deleted children's parents in the same session. The listener works in this case:
c = session.query(Child).first()
p = c.parent
session.delete(c)
c1 = Child(parent=p)
session.add(c1)
session.commit()
but hasn't been tested on anything more complicated.
The behavior you're referring to is kinda simple. But once the application gets larger, you'd need more sophisticated methods. I, personally, implement a static method remove for every class. It takes the id and any other parameter necessary to know the delete scheme.
The answer is already given... But that's - not efficient - approach just to give you an example.
class Child(Base):
__tablename__ = "children"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("parents.id", ondelete='CASCADE'))
#staticmethod
def remove(_id_):
child = Child.query().get(_id_)
Parent.remove(child.parent_id)
session.delete(child)
class Parent(Base):
__tablename__ = "parents"
id = Column(Integer, primary_key=True)
child = relationship(Child, backref="parent", passive_deletes=True)
#staticmethod
def remove(_id_):
parent = parent.query().get(_id_)
session.delete(parent)
If you, for example, added a Column named is_old - in the Child class - that's a binary integer 0 or 1 and wanted to delete the parent of the Child that has is_old == 1, It'll be very easy task.
Related
If we have two sqlalchemy models, where a Child model has deletion flag:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship('Child', primaryjoin='and_(Parent.id==Child.parent_id, not_(Child.is)deleted))')
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForegnKey('parent.id'), nullable=False)
is_deleted = Column(Boolean, default=False)
parent = relationship(Parent)
When I remove child from parent with:
parent.children.remove(child)
SQLAlchemy sets child.parent_id to NULL. Is there any way to remove child from parent's children list, but keep parent_id? Right now I do:
child.is_deleted = True
session.flush()
session.refresh(parent)
which removes child from children list, but maybe there is a better way?
I don't think so, not as long as your relationship is dependent on Child.parent_id.
Here are my models (ignoring imports):
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship('Child', backref='parent', lazy='dynamic')
class Child(Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parents.id'))
Next I create a parent and a child, and relate them:
dbsession = session()
child = Child(name='bar')
dbsession.add(child)
parent = Parent(name='foo')
parent.children.append(child)
dbsession.add(parent)
dbsession.commit()
And all that works fine (so ignore any errors I may have made copying it to here). Now I'm trying to break the relationship, while keeping both the parent and the child in the database, and I'm coming up empty.
I appreciate any help.
I'm not sure exactly what you mean by break a relationship or why but I think this might work:
child = dbsession.query(Child).filter(Child.name=='bar').one()
child.parent_id = None
dbsession.add(child)
dbsession.commit()
This post gives more info about blanking a foreign key: Can a foreign key be NULL and/or duplicate?
Following this tutorial, for the one to many relationships, I have this simple code:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="children")
Now the problem is populating parent_id, whenever I set the children as a list, and commit the results to a database as follows:
# assume I have a session
children = Child(), Child(), Child()
p = Parent(children=children)
session.add(p)
session.commit()
If I check the database at this point, parent_id is not populated. I guess this makes sense, because I haven't explicitly defined parent_id anywhere, but is there a way to get the children to get the parents id?
Parent accept children as a list so:
children = [Child(), Child(), Child()]
...
p = Parent.query.first()
for child in p.children:
print(child.parent_id)
Can I add new object to the relationship object?
Class Parent():
id = Column(Integer, primary_key=True)
name = Column(String(10))
child = relationship('Child', backref='parent', cascade="all, delete-orphan")
Class Child():
id = Column(Integer, primary_key=True)
parent = Column(Integer, ForeignKey('parent.id'))
name = Column(String(10))
parent = Parent('parent1', [Child(name='child1'), Child(name='child2')])
session.add(parent)
session.commit()
The above code is insert one parent and its 2 child.
My Question is,
Can I add new child from exist parent like this?
parent = session.query(Parent).filter(Parent.id=1).first()
parent.children.append(Child(name='child3'))
session.merge(parent)
I tried above it shows the IntegrityError: (IntegrityError) datatype mismatch u'UPDATE child.
Had I made any mistake here?
Try this...
parent = session.query(Parent).filter(Parent.id=1).first()
child = Child(parent=parent, name='child3')
session.add(child)
My Python / SQLAlchemy application manages a set of nodes, all derived from a base class Node. I'm using SQLAlchemy's polymorphism features to manage
the nodes in a SQLite3 table. Here's the definition of the base Node class:
class Node(db.Base):
__tablename__ = 'nodes'
id = Column(Integer, primary_key=True)
node_type = Column(String(40))
title = Column(UnicodeText)
__mapper_args__ = {'polymorphic_on': node_type}
and, as an example, one of the derived classes, NoteNode:
class NoteNode(Node):
__mapper_args__ = {'polymorphic_identity': 'note'}
__tablename__ = 'nodes_note'
id = Column(None,ForeignKey('nodes.id'),primary_key=True)
content_type = Column(String)
content = Column(UnicodeText)
Now I need a new kind of node, ListNode, that is an ordered container of zero or more Nodes. When I load a ListNode, I want it to have its ID and title
(from the base Node class) along with a collection of its contained (child) nodes. A Node may appear in more than one ListNode, so it's not a proper hierarchy. I would create them along these lines:
note1 = NoteNode(title=u"Note 1", content_type="text/text", content=u"I am note #1")
session.add(note1)
note2 = NoteNode(title=u"Note 2", content_type="text/text", content=u"I am note #2")
session.add(note2)
list1 = ListNode(title=u"My List")
list1.items = [note1,note2]
session.add(list1)
The list of children should only
consist of Node objects -- that is, all I need is their base class stuff. They shouldn't be fully realized into the specialized classes
(so I don't get the whole graph at once, among other reasons).
I started along the following lines, cobbling together bits and pieces I found in various places without a complete understanding of
what was going on, so this may not make much sense:
class ListNode(Node):
__mapper_args__ = {'polymorphic_identity': 'list', 'inherit_condition':id==Node.id}
__tablename__ = 'nodes_list_contents'
id = Column(None, ForeignKey('nodes.id'), primary_key=True)
item_id = Column(None, ForeignKey('nodes.id'), primary_key=True)
items = relation(Node, primaryjoin="Node.id==ListNode.item_id")
This approach fails in several ways: it doesn't appear to allow an empty ListNode, and setting the items attribute to a list results
in SQLAlchemy complaining that 'list' object has no attribute '_sa_instance_state'. Not surprisingly, hours of random mutations on this theme haven't given any
good results,
I have limited experience in SQLAlchemy but really want to get this working soon. I'd very much appreciate any advice or direction you can
offer. Thanks in advance!
You need an additional table for many-to-many relation:
nodes_list_nodes = Table(
'nodes_list_nodes', metadata,
Column('parent_id', None, ForeignKey('nodes_list.id'), nullable=False),
Column('child_id', None, ForeignKey(Node.id), nullable=False),
PrimaryKeyConstraint('parent_id', 'child_id'),
)
class ListNode(Node):
__mapper_args__ = {'polymorphic_identity': 'list'}
__tablename__ = 'nodes_list'
id = Column(None, ForeignKey('nodes.id'), primary_key=True)
items = relation(Node, secondary=nodes_list_nodes)
Update: below is an example for ordered list using association_proxy:
from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy.ext.associationproxy import association_proxy
class ListNodeAssociation(Base):
__tablename__ = 'nodes_list_nodes'
parent_id = Column(None, ForeignKey('nodes_list.id'), primary_key=True)
child_id = Column(None, ForeignKey(Node.id), primary_key=True)
order = Column(Integer, nullable=False, default=0)
child = relation(Node)
__table_args__ = (
PrimaryKeyConstraint('parent_id', 'child_id'),
{},
)
class OrderedList(InstrumentedList):
def append(self, item):
if self:
item.order = self[-1].order+1
else:
item.order = 1
InstrumentedList.append(self, item)
class ListNode(Node):
__mapper_args__ = {'polymorphic_identity': 'list'}
__tablename__ = 'nodes_list'
id = Column(None, ForeignKey('nodes.id'), primary_key=True)
_items = relation(ListNodeAssociation,
order_by=ListNodeAssociation.order,
collection_class=OrderedList,
cascade='all, delete-orphan')
items = association_proxy(
'_items', 'child',
creator=lambda item: ListNodeAssociation(child=item))