One-to-one self-referential relationship using an association table in SqlAlchemy - python

So I have a parent child relationship between Topic models with the relationship represented by this class:
class ParentChildRelation(db.Model):
__tablename__ = 'parent_child_relation'
id = db.Column(db.Integer, primary_key=True)
child_topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'), nullable=False)
parent_topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'), nullable=False)
And the topics defined as such:
class Topic(db.Model):
__tablename__ = 'topic'
id = db.Column(db.Integer, primary_key=True)
parent = db.relationship(
'Topic',
secondary='parent_child_relation',
primaryjoin='Topic.id == ParentChildRelation.child_topic_id',
secondaryjoin='Topic.id == ParentChildRelation.parent_topic_id'
)
I'd like to have parent be a one-to-one relationship for now (I might change it later on), but it comes back as an InstrumentedList. Is there a simple way of stating that parent should be a one-to-one relationship such that it links directly to a Topic model instead of being an InstrumentedList?

So it turns out I had previously found the correct answer, but I must have had a typo :/. Adding uselist=False did the trick.
parent = db.relationship(
'Topic',
secondary='parent_child_relation',
primaryjoin='Topic.id == ParentChildRelation.child_topic_id',
secondaryjoin='Topic.id == ParentChildRelation.parent_topic_id',
uselist=False
)

Related

SQLAlchemy - Class to increment the parent in case a child (containing 2 foreign keys) is added to the DB as Obj

I found many different ways of writing down relationships between parent and children; backref, relationship, db.column(incl foreign_keys arg.), foreign_keys argument in the DB.relationship.
Also tried many examples found on the internet. Errors range from actual coding errors to DB migration errors.
The below works, but I am quite sure I should add something like a class definition to the Model. The way it's setup now it looks to me that whenever I require the rating of the parent, I need to fetch all ratings in the DB, calculate the average, etc. This does not seem to be effecient. Is there a way to increment +x(x=rating) the parent in case the child is created with the parent - in this case - as 'user_receiver_id / user_receiver'
class Parent(UserMixin, db.Model):
__tablename__ = 'parent'
id = db.Column(db.Integer, primary_key=True)
rating = db.Column(db.Float(), index=True, unique=False)
def __rating__(self):
#if child is created in DB with parent as holder -> do some python
class Child(db.Model):
__tablename__ = 'child'
id = db.Column(db.Integer, primary_key=True)
datum = db.Column(db.DateTime, server_default=db.func.now(), index=False)
content = db.Column(db.Text)
rating = db.Column(db.Float(), index=True, unique=False)
''' receiver details '''
Parent_receiver_id = db.Column(db.Integer, db.ForeignKey(Parent.id))
''' giver details '''
Parent_giver_id = db.Column(db.Integer, db.ForeignKey(Parent.id))
Parent_receiver = db.relationship("Parent", foreign_keys=[Parent_receiver_id])
Parent_giver = db.relationship("Parent", foreign_keys=[Parent_giver_id])

Ordering relationship by property of child elements

I use flask-sqlalchemy on a Flask project to model my database.
I need to sort the elements of a many-to-many relationship based on properties of different child elements of one side.
I have "Work" (the parent element), "Tag" (the children), "Type" (a one-to-many relationship on Tag) and "Block" (a one-to-many relationship on Type). Tags and Works are joined with a mapping table "work_tag_mapping".
In essence, each tag has exactly one type, each type belongs to exactly one block, and many tags can be added on many works.
I now want the list of tags on a work be sorted by block first and type second (both have a "position" column for that purpose).
Here are my tables (simplified for the sake of the question):
class Work(db.Model):
__tablename__ = 'work'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
tags = db.relationship('Tag', order_by="Tag.type.block.position, Tag.type.position", secondary=work_tag_mapping)
class Tag(db.Model):
__tablename__ = 'tag'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
type_id = db.Column(db.Integer, db.ForeignKey('type.id'), nullable=False)
type = db.relationship('Type')
work_tag_mapping = db.Table('work_tag_mapping',
db.Column('id', db.Integer, primary_key=True),
db.Column('work_id', db.Integer, db.ForeignKey('work.id'), nullable=False),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), nullable=False)
)
class Type(db.Model):
__tablename__ = 'type'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
position = db.Column(db.Integer)
block_id = db.Column(db.Integer, db.ForeignKey('block.id'), nullable=False)
block = db.relationship('Block')
class Block(db.Model):
__tablename__ = 'block'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
position = db.Column(db.Integer)
Now, it is the "order_by" in the "tags" relationship that doesn't work as I initially hoped.
The error I get is "sqlalchemy.exc.InvalidRequestError: Property 'type' is not an instance of ColumnProperty (i.e. does not correspond directly to a Column)."
I am new to SQLalchemy, Flask and indeed Python, and none of the ressources or questions here mention a case like this.
While this appears not to be possible directly, adding a getter and performing the sorting on retrieval does the trick. Adding lazy='dynamic' ensures the collection behaves as a query, so joins can be performed.
_tags = db.relationship('Tag', lazy='dynamic')
#hybrid_property
def tags(self):
return self._tags.join(Type).join(Block).order_by(Block.position, Type.position)

How to remove a child from a parent in SQLAlchemy relationship

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?

Access SQLAlchemy model linked with foreign key

I have a model with a foreign key to another model. I query the first model, and want to access the model related to it. In Django I can access the model from the foreign key. How do I do this in SQLAlchemy?
class Model(Base):
field_id = Column(Integer, ForeignKey('Model2.id'))
class Model2(Base):
id = Column(Integer)
needed_field = Column(Integer)
models = Model.query.all()
return render to template('templ.html', models=models)
Django works like this:
models = Model.objects.all()
model.field_id.needed_field # loop in template
In Django, defining the foreign key defines the column as well as the relationship to the other model. In SQLAlchemy, you need to define both manually. See the docs about defining relationships.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.ForeignKey(Group.id))
group = db.relationship(Group, backref='users')
db.create_all()
db.session.add(User(group=Group()))
db.session.commit()
u = User.query.get(1)
print(u.group)
User.group_id is the foreign key pairing a User with a Group. User.group is the relationship (and Group.users is the relationship in the other direction). Usually you set and read the relationship, not the foreign key.
Below example can help you
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 You can access parent child from parent_obj.children
For more details http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-one

Self referential relationship including a relationship attribute

Situation
I have the Self-Referential Many-to-Many Relationship (almost identical to the sqlalchemy manual entry of the same heading). This relationship is governed by the table entity_weights. This code works!
Question
How do I include an attribute in class Entity representing the table column entity_weights.weight. Lets say the attribute would be called Entity.child_weights. It is important that the rank of Entity.child_entities and Entity.child_weights are identical.
entity_weights = Table('entity_weights', Base.metadata,
Column('id',Integer, primary_key=True),
Column('parent_entity_id',Integer, ForeignKey('entity.id')),
Column('entity_id',Integer, ForeignKey('entity.id')),
Column('weight',Float))
class Entity(Base):
__tablename__ = 'entity'
id = Column(Integer, primary_key=True)
name = Column(String)
domicile_id = Column(Integer, ForeignKey('domicile.id'))
entity_type = Column('type',Enum('asset','institution','model'))
source_table_id = Column(Integer)
child_entities = relationship('Entity',
secondary=entity_weights,
primaryjoin=id==entity_weights.c.parent_entity_id,
secondaryjoin=id==entity_weights.c.entity_id,
backref='parent_entity_id'
)
The cleanest solution I've found for this scenario is to break up the child_entities relationship by adding entity_weights as a one-to-many relationship on Entity and use an association proxy to proxy the weight value as well as the remote side of the many-to-many relationship:
class EntityWeight(Base):
__tablename__ = "entity_weights"
id = Column(Integer, primary_key=True)
parent_entity_id = Column(Integer, ForeignKey('entity.id'))
entity_id = Column(Integer, ForeignKey('entity.id'))
weight = Column(Float)
entity = relationship("Entity", primaryjoin=lambda: EntityWeight.entity_id == Entity.id)
class Entity(Base):
...
_child_weights = relationship(EntityWeight, primaryjoin=id == EntityWeight.parent_entity_id)
child_weights = association_proxy("_child_weights", "weight")
child_entities = association_proxy("_child_weights", "entity")

Categories