Many to many query for objects - python

I have a many to many relationship and I'm trying to make a query to return one or all the objects but the max I can get is the id of that object which is the foreign key of my Association Class Table.
This is my association class table
class PolPropAssociation(db.Model):
__tablename__ = 'polprop'
politician = db.Column(db.Integer, db.ForeignKey('politics.idPolitician'), primary_key=True)
proposal = db.Column(db.Integer, db.ForeignKey('proposals.idProposal'), primary_key=True)
relation = db.Column(db.String(120))
parent = db.relationship("Politic", back_populates="children")
child = db.relationship("Proposal", back_populates="parents")
draft = db.Column(db.Boolean, default=True)
def __init__(self, relation):
self.relation = relation.title()
class Politic(db.Model):
__searchable__ = ['publicName', 'completeName']
__tablename__ = 'politics'
idPolitician = db.Column(db.Integer, primary_key=True)
publicName = db.Column(db.String(150))
completeName = db.Column(db.String(300))
startDate = db.Column(db.Date, default=datetime.datetime.utcnow)
endDate = db.Column(db.Date, default=datetime.datetime.utcnow)
positions=db.relationship('Position',backref='pos_politic',lazy='select')
draft = db.Column(db.Boolean, default = True)
biolink = db.Column(db.String(200))
flag = db.relationship('Flag', cascade="all, delete", backref='politics', lazy='dynamic')
children = db.relationship("PolPropAssociation", back_populates="parent", lazy='dynamic')
class Proposal(db.Model):
__tablename__ = 'proposals'
idProposal = db.Column(db.Integer, primary_key=True)
dateProposal = db.Column(db.Date, default=datetime.datetime.utcnow)
description = db.Column(db.String(500))
linkProposal = db.Column(db.String(200))
idCategory = db.Column(db.Integer, db.ForeignKey('category.idcategory'))
idProposalState = db.Column(db.Integer, db.ForeignKey('proposalstate.idproposalstate'))
draft = db.Column(db.Boolean, default = True)
flag = db.relationship('FlagProposal', backref='proposals', lazy='dynamic')
parents = db.relationship("PolPropAssociation", back_populates="child", lazy='dynamic')
And I want to return all the proposals of a politician with a given idProposal:
proposal = Proposal.query.filter_by(idProposal=idProposal).first()
politicians = proposal.parents.all()
but all it returns is an array with all the politician column of my Association Table. Is there any way I can return the Politic objects with those ids?
Best regards

I found the solution:
If there's anyone asking how to query something specific using an Object Association Table, here it goes:
pol = Politic.query.filter(Politic.children.any(proposal=idProposal)).all()

Related

SQLAlchemy One-to-(Some of Many) relationship

I currently have a SQLAlchemy model User which has two types of measurements associated with it, weight and height. These are added as foreign keys.
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
weight = db.relationship("Weight", cascade="all,delete")
height = db.relationship("Height", cascade="all,delete")
class Weight(db.Model):
__tablename__ = "weight"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
weight = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
class Height(db.Model):
__tablename__ = "height"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
height = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
I want to be able to add more types of measurements, without having to rebuild the database structure every time. Therefore I thought about using a generic Measurement model which has a column for the type of measurement (not an Enum so I can add new types easily):
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
measurements = db.relationship("Measurement", cascade="all,delete")
#property
def weight(self):
pass # <-- No idea what to do here
#property
def height(self):
pass # <-- No idea what to do here
class Measurement(db.Model):
__tablename__ = "measurement"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
value = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
measurement_type_id = db.Column(db.Integer, db.ForeignKey('measurement_type.id'))
measurement_type = db.relationship("MeasurementType")
class MeasurementType(db.Model):
__tablename__ = "measurement_type"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
unit = db.Column(db.String(100), nullable=True)
With this new model, how do I get only the e.g. weights? With the current models, this is as simple as this, where current_user is the currently logged in user:
current_user.weight
But I guess it would have to be something like this:
current_user.measurements.filter(Measurement.measurement_type == "Weight")
(Which doesn't work, as current_user.measurements returns a list.)
Similarly, how do I then add a new value to the measurements? Currently I do this:
current_user.weight.append(Weight(date=date, weight=weight))
db.session.commit()
Do I basically need to replicate the low-level implementation of relationship in order to filter by two things, the user ID and the measurement type?
Or can I somehow achieve this using an association proxy? Or (correctly) using the primaryjoin argument of relationship?
The easiest way I found to achieve this is to use dynamic lazy loading on the relationship, as also recommended on this answer to a similar question. This way the return type is no longer a simple list, but an SQL object. This also uses a simple string as the measurement type, instead of a lookup table.
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
measurements = db.relationship("Measurement", cascade="all,delete", lazy="dynamic")
class Measurement(db.Model):
__tablename__ = "measurement"
id = db.Column(db.Integer, primary_key=True)
m_type = db.Column(db.String(20))
date = db.Column(db.DateTime)
value = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
Afterwards you can use normal filter methods on the measurements:
list(current_user.measurements.filter_by(m_type="weight"))
And adding a new row is also very simple:
current_user.measurements.append(Measurement(m_type="weight", date=date, value=weight))

Flask SQLAlchemy Association Table: error creating backref

I have a Artist model, and a Venue model, and they have a many-to-many relationship facilitated by the Show model (the shows indicate the artist, venue and date of the concert).
class Venue(db.Model):
__tablename__ = 'Venue'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, nullable=False)
shows = db.relationship('Show', backref='venue', lazy=True)
class Artist(db.Model):
__tablename__ = 'Artist'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, nullable=False)
shows = db.relationship('Show', backref='artist', lazy=True)
class Show(db.Model):
__tablename__ = 'Show'
id = db.Column(db.Integer, primary_key=True)
venue_id = db.Column(db.Integer, db.ForeignKey('Venue.id'), nullable=False)
artist_id = db.Column(db.Integer, db.ForeignKey('Artist.id'), nullable=False)
start_time = db.Column(db.DateTime, nullable=False)
venue = db.relationship('Venue', back_populates='venue')
artist = db.relationship('Artist', back_populates='artist')
I'd like to have a query to get the venue name, artist name, and date of all upcoming shows so had in mind the following...
upcoming_shows = Show.query.filter_by(show.start_time >= datetime.now()).all()
...so that I can then access for example, upcoming_shows[0].Venue.name, upcoming_shows[0].Artist.name and upcoming_shows[0].start_time.
That query however returns the following:
sqlalchemy.exc.ArgumentError: Error creating backref 'venue' on
relationship 'Venue.shows': property of that name exists on mapper
'mapped class Show->Show'
Feel like I'm close but missing something fundamental!
Solved. I made the changes below to the code and it works now. It was rather straight-forward in the end, I simply had to ensure that the relationship names were unique :-\
class Venue(db.Model):
__tablename__ = 'Venue'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, nullable=False)
venue_shows = db.relationship('Show', back_populates='venue', lazy=True)
class Artist(db.Model):
__tablename__ = 'Artist'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, nullable=False)
artist_shows = db.relationship('Show', back_populates='artist', lazy=True)
class Show(db.Model):
__tablename__ = 'Show'
id = db.Column(db.Integer, primary_key=True)
venue_id = db.Column(db.Integer, db.ForeignKey('Venue.id'), nullable=False)
artist_id = db.Column(db.Integer, db.ForeignKey('Artist.id'), nullable=False)
start_time = db.Column(db.DateTime, nullable=False)
venue = db.relationship('Venue', back_populates='venue_shows')
artist = db.relationship('Artist', back_populates='venue_artist')
Here is a general example for what you are looking for
class Association(Base):
__tablename__ = 'association'
left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
child = relationship("Child", back_populates="parents")
parent = relationship("Parent", back_populates="children")
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Association", back_populates="child")
For more details check the documentation

SqlAlchemy Error on Creating multiple foreign key to one table

I am new using sqlAlchemy and having problem creating new tables, specially when it comes around 2 foreign keys pointing to 1 table:
class Offers(db.Model):
__tablename__ = 'offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
contact_ign = db.Column(db.String(100))
conversion_rate = db.Column(db.Float)
stock = db.Column(db.Integer)
create_date = db.Column(db.DateTime(timezone=True), default=func.now())
currency_pair = db.relationship('CurrencyPairs', backref='pair', lazy='dynamic')
class CurrencyPairs(db.Model):
__tablename__ = 'currency_pairs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
league = db.Column(db.String(100))
pair_id = db.Column(db.Integer, db.ForeignKey('offers.id'))
want = db.relationship('Currency', backref='want', lazy='dynamic')
have = db.relationship('Currency', backref='have', lazy='dynamic')
class Currency(db.Model):
__tablename__ = 'currency'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
poe_trade = db.Column(db.Integer, nullable=False)
poe_official = db.Column(db.String(10), nullable=False)
tier = db.Column(db.Integer, nullable=False)
want_id = db.Column(db.Integer, db.ForeignKey('currency_pairs.id'))
have_id = db.Column(db.Integer, db.ForeignKey('currency_pairs.id'))
The error I am getting is:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper|CurrencyPairs|currency_pairs'. Original exception was: Could not determine join condition b
etween parent/child tables on relationship CurrencyPairs.want - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key refe
rence to the parent table
I have try different things but I get same result.
What am I doing wrong?
Thanks In advance.
I know this is an old question, but I had the same problem. I hope to help others with the answer.
This issue is addressed in the sqlalchemy documentation.
https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
class Offers(db.Model):
__tablename__ = 'offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
contact_ign = db.Column(db.String(100))
conversion_rate = db.Column(db.Float)
stock = db.Column(db.Integer)
create_date = db.Column(db.DateTime(timezone=True), default=func.now())
currency_pair = db.relationship('CurrencyPairs', backref='pair', lazy='dynamic')
class CurrencyPairs(db.Model):
__tablename__ = 'currency_pairs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
league = db.Column(db.String(100))
pair_id = db.Column(db.Integer, db.ForeignKey('offers.id'))
want_currency = relationship("Currency", foreign_keys='[Currency.want_id]', back_populates="want_currency_pairs")
have_currency = relationship("Currency", foreign_keys='[Currency.have_id]', back_populates="have_currency_pairs")
class Currency(db.Model):
__tablename__ = 'currency'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
poe_trade = db.Column(db.Integer, nullable=False)
poe_official = db.Column(db.String(10), nullable=False)
tier = db.Column(db.Integer, nullable=False)
want_currency_pairs = relationship(CurrencyPairs, foreign_keys="[Currency.want_id]", back_populates="want_currency")
have_currency_pairs = relationship(CurrencyPairs, foreign_keys="[Currency.have_id]", back_populates="have_currency")
The way you wrote the code, sqlalchemy can't really understand which relationship to choose, because you have 2 of the same relationship. So you have to describe to sqlalchemy that there are 2 relationships to the same table.

SQLALchemy session.merge is not working

AFAIK merge performs and insert or update so what I'm doing is pretty simple.
There is a relation of 1 to N between Hubs and Tags.
So when I try to make a merge on tag and hub, hub goes well and load from DB the existent hub and make an update, but fails when db.session.merge(nuevo_tag) is executed throwing an exception because behind the scenes tries to make an insert, even if the tag previously exist.
What I did wrong?
nuevo_hub = Hub(guid_hub,name,location,comments,id_project,creado_en,actualizado_en)
merged_hub = db.session.merge(nuevo_hub)
#db.session.commit() # If I use this line tags perform an insert.
nuevo_tag = Tag(guid_tag,project,merged_hub,TYPE_HUB,creado_en,actualizado_en)
merged_tag = db.session.merge(nuevo_tag)
db.session.commit()
If I remove db.session.commit() then other error is shown:
sqlalchemy.orm.exc.FlushError: New instance with
identity key (,
(b'\x11\x0e\x84\x00\xe2\x9b\x11\xd4\xa7\x16DfUD\x00\r',)) conflicts
with persistent instance
class Item(db.Model):
__tablename__ = "items"
# id_item = db.Column(db.Integer, autoincrement=True, primary_key=True)
guid_item = db.Column(db.BINARY(16), primary_key=True)
id_project = db.Column(db.Integer,db.ForeignKey("projects.id_project"))
type = db.Column(db.Integer)
name = db.Column(db.String(50), nullable=False, index= True)
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
__mapper_args__ = {
'polymorphic_identity': 'items',
'polymorphic_on':type,
'with_polymorphic':'*'
}
__table_args__ = (
db.UniqueConstraint('name', 'id_project', name='_unique_name_project'),
)
def __init__(self,creado_en=None):
self.created_at = creado_en
self.updated_at = creado_en
class Hub(Item):
__tablename__ = "hubs"
__mapper_args__ = {
'polymorphic_identity': TYPE_HUB,
'with_polymorphic':'*'
}
guid_hub = db.Column(db.BINARY(16), db.ForeignKey(Item.guid_item), primary_key=True)
location = db.Column(db.String(50))
comments = db.Column(db.String(128))
def __init__(self, guid_hub=None, nombre=None, location=None,comments=None, id_project=None, creado_en=None, actualizado_en=None):
self.type = TYPE_HUB
self.guid_item = guid_hub
self.guid_hub = guid_hub
self.name = nombre
self.id_project = id_project
self.location = location
self.comments = comments
self.created_at = creado_en
self.updated_at = actualizado_en
class Tag(db.Model):
__tablename__ = "tags"
guid_tag = db.Column(db.BINARY(16), primary_key=True)
id_project = db.Column(db.Integer,db.ForeignKey("projects.id_project"))
guid_item = db.Column(db.BINARY(16),db.ForeignKey("items.guid_item"))
project = db.relationship(Proyecto, backref=db.backref('list_tags', lazy='dynamic'))
item = db.relationship(Item, backref=db.backref('list_tags', lazy='joined'))
type = db.Column(db.Integer) #(0,hub);(1,cable);(2,pipe);(3,electrical_pipes)
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
def __init__(self,guid_tag,project,item,type,created_at,updated_at):
# self.guid_item = guid_tag
self.guid_tag = guid_tag
self.project = project
self.item = item
self.type = type
self.created_at = created_at
self.updated_at = updated_at
I found the answer in docs.
Consider Hub is a subclass of Item.
I have to change backref lazy from joined to dynamic.
class Tag(db.Model):
__tablename__ = "tags"
guid_tag = db.Column(db.BINARY(16), primary_key=True)
id_project = db.Column(db.Integer,db.ForeignKey("projects.id_project"))
guid_item = db.Column(db.BINARY(16),db.ForeignKey("items.guid_item"))
project = db.relationship(Proyecto, backref=db.backref('list_tags', lazy='dynamic'))
item = db.relationship(Item, backref=db.backref('list_tags', lazy='joined'))
type = db.Column(db.Integer) #(0,hub);(1,cable);(2,pipe);(3,electrical_pipes)
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
In other words I must avoid have two copys of a persistence object in the same session.

SqlAlchemy Flask - association table with more than two columns

I am creating a shopping cart, for this I am using the following models in Flask:
line_item = db.Table('line_item',
db.Column('cart_id', db.Integer, db.ForeignKey('cart.id')),
db.Column('product_id', db.Integer, db.ForeignKey('product.id')),
db.Column('price', db.Float)
)
class Cart(db.Model):
id = db.Column(db.Integer, primary_key=True)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Unicode(64), index=True, unique=True)
description = db.Column(db.Unicode(500), index=True, unique=True)
image_url = db.Column(db.Unicode(128))
price = db.Column(db.Float)
line_item = db.relationship('Cart', secondary=line_item, backref=db.backref('products', lazy='dynamic'))
I want to use an extra column in the association table "line_item" to include price in order to save the price of a product at the moment the user adds it to the cart.
I know that without the price column I would do the following to add this data.
p = Product()
c = Cart()
p.line_item.append(c)
db.session.add(p)
db.session.commit()
How am I supposed to insert the price in the association table?
Use a db.Model for your LineItem - untested code as follows:
class LineItem(db.Model):
__tablename__ = 'line_items'
cart_id = db.Column(db.Integer, db.ForeignKey('carts.id'), primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('products.id'), primary_key=True)
price = db.Column(db.Float)
cart = db.relationship("Cart", back_populates="line_items")
product = db.relationship("Product", back_populates="carts")
class Cart(db.Model):
__tablename__ = 'carts'
id = db.Column(db.Integer, primary_key=True)
line_items = db.relationship(LineItem, back_populates="cart")
class Product(db.Model):
__tablename__ = 'products'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Unicode(64), index=True, unique=True)
description = db.Column(db.Unicode(500), index=True, unique=True)
image_url = db.Column(db.Unicode(128))
price = db.Column(db.Float)
carts = db.relationship(LineItem, back_populates="product")
p = Product()
c = Cart()
line_item = LineItem()
line_item.price = p.price
line_item.product = p
c.line_items.append(line_item)

Categories