Access SQLAlchemy model linked with foreign key - python

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

Related

Sqlalchemy many-to-many association proxy: silently reject duplicates

I have a many to many association using association proxies as follows:
import sqlalchemy.orm as orm
import sqlalchemy as sa
import sqlalchemy.ext.associationproxy as ap
class Asset(BaseModel):
__tablename__ = 'assets'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.VARCHAR(255))
asset_tags = orm.relationship(
"AssetTag", back_populates='asset', cascade='all, delete-orphan')
tags = ap.association_proxy(
"asset_tags", "tag",
creator=lambda tag: AssetTag(tag=tag))
class AssetTag(BaseModel):
__tablename__ = 'asset_tags'
asset_id = sa.Column(sa.Integaer, sa.ForeignKey("assets.id"), primary_key=True)
tag_id = sa.Column(sa.Integer, alsach.ForeignKey("tags.id"), primary_key=True)
asset = orm.relationship("Asset", back_populates='asset_tags')
tag = orm.relationship("Tag", back_populates='asset_tags')
class Tag(BaseModel):
__tablename__ = 'tags'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.VARCHAR(255))
asset_tags = orm.relationship("AssetTag",
back_populates='tag',
cascade='all, delete-orphan')
assets = ap.association_proxy("asset_tags", "asset",
creator=lambda asset: AssetTag(asset=asset))
Note that the asset_tas table has a uniqueness constraint on (asset_id, tag_id).
If I do
with Session() as s:
a = s.get(Asset, 1)
tag = a.tags[0]
a.tags.append(tag)
s.commit()
SQLAlchemy creates a new AssetTag between Asset<1> and Tag<31> (example number), and tries to commit that, violating the uniqueness constraint.
pymysql.err.IntegrityError: (1062, "Duplicate entry '1-31' for key 'PRIMARY'")
Is there any way to make asset.tags have the behavior of a set, where adding an existing item is skipped?
asset.tags.append(tag)
asset.tags.append(tag) # fails silently
You can pass set as the relationship's collection_class argument. This will have the collection behave as a set rather than as a list.
asset_tags = orm.relationship(
"AssetTag",
back_populates='asset',
cascade='all, delete-orphan',
collection_class=set,
)
Note that you must use set methods when operating on the collection, for example
asset.tags.add(some_tag)
not
asset.tags.append(some_tag)

Flask SQLAlchemy Foreign Key Relationships

I'm having a lot of trouble getting my head around foreign keys and relationships in SQLAlchemy. I have two tables in my database. The first one is Request and the second one is Agent. Each Request contains one Agent and each Agent has one Request.
class Request(db.Model):
__tablename__ = 'request'
reference = db.Column(db.String(10), primary_key=True)
applicationdate = db.Column(db.DateTime)
agent = db.ForeignKey('request.agent'),
class Agent(db.Model):
__tablename__ = 'agent'
id = db.relationship('Agent', backref='request', \
lazy='select')
name = db.Column(db.String(80))
company = db.Column(db.String(80))
address = db.Column(db.String(180))
When I am running db.create_all() I get the following error
Could not initialize target column for ForeignKey 'request.agent' on table 'applicant': table 'request' has no column named 'agent'
Have a look at the SqlAlchemy documentation on OneToOne relationships. First you need to supply a Primary Key for each model. Then you need to define one Foreign Key which refers to the Primary Key of the other model. Now you can define a relationship with a backref that allows direct access to the related model.
class Request(db.Model):
__tablename__ = 'request'
id = db.Column(db.Integer, primary_key=True)
applicationdate = db.Column(db.DateTime)
class Agent(db.Model):
__tablename__ = 'agent'
id = db.Column(db.Integer, primary_key=True)
request_id = db.Column(db.Integer, db.ForeignKey('request.id'))
request = db.relationship("Request", backref=backref("request", uselist=False))
name = db.Column(db.String(80))
company = db.Column(db.String(80))
address = db.Column(db.String(180))
Now you can access your models like this:
request = Request.query.first()
print(request.agent.name)
agent = Agent.query.first()
print(agent.request.applicationdate)

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

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
)

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")

How can I make property comparison able to be compiled to SQL expression in SQLAlchemy?

There are two tables that one column of table A is pointing another table B's primary key.
But they are placed in different database, so I cannot configure them with foreign key.
Configuring via relationship() is unavailable, so I implemented property attribute manually.
class User(Base):
__tablename__ = 'users'
id = Column(BigInteger, id_seq, primary=True)
name = Column(Unicode(256))
class Article(Base):
__tablename__ = 'articles'
__bind_key__ = 'another_engine'
# I am using custom session configures bind
# each mappers to multiple database engines via this attribute.
id = Column(BigInteger, id_seq, primary=True)
author_id = Column(BigInteger, nullable=False, index=True)
body = Column(UnicodeText, nullable=False)
#property
def author(self):
_session = object_session(self)
return _session.query(User).get(self.author_id)
#author.setter
def author(self, user):
if not isinstance(user, User):
raise TypeError('user must be a instance of User')
self.author_id = user.id
This code works well for simple operations. But it causes dirty queries making SQLAlchemy's features meaningless.
Code would be simple if it was configured via relationship() (e.g. query.filter(author=me)) got messed up(e.g. query.filter(author_id=me.id)).
Relationship(e.g. join) related features are never able to be used in query building.
Can I use property attribute, at least, in building query criterion(filter()/filter_by())?
you can still use relationship here. If you stick to "lazy loading", it will query for the related item in database B after loading the lead item in database A. You can place a ForeignKey() directive in the Column, even if there isn't a real one in the database. Or you can use primaryjoin directly:
class User(Base):
__tablename__ = 'users'
id = Column(BigInteger, id_seq, primary=True)
name = Column(Unicode(256))
class Article(Base):
__tablename__ = 'articles'
__bind_key__ = 'another_engine'
id = Column(BigInteger, id_seq, primary=True)
author_id = Column(BigInteger, nullable=False, index=True)
body = Column(UnicodeText, nullable=False)
author = relationship("User",
primaryjoin="foreign(Article.author_id) == User.id")

Categories