Flask SQLAlchemy many-to-many relationship issue - many-to-many

I'm having an issue with my many-to-many relationship in Flask SQLAlchemy. I have a general understanding of how this works, but I can't seem to be able to wrap my head around my example.
I'm making a tennis score keeping app and I currently have two models, Player and Score and and intermediary table Match (players can challenge each other and keep track of the matches they played). The issue is that my intermediary table has three columns that point to the same column in the Player class (player id) and I cant figure out how to make the relationship column. My code so far is as follows:
match = db.Table("match",
db.Model.metadata,
db.Column("id", db.Integer, primary_key=True),
db.Column("player1", db.Integer, db.ForeignKey("players.id")),
db.Column("player2", db.Integer, db.ForeignKey("players.id")),
db.Column("score", db.Integer, db.ForeignKey("scores.id")),
db.Column("winner", db.Integer, db.ForeignKey("players.id"))
)
class Player(db.Model):
__tablename__ = "players"
id = db.Column(db.Integer, primary_key=True)
fullname = db.Column(db.String)
# scores = db.relationship("Score", secondary=match)
class Score(db.Model):
__tablename__ = "scores"
id = db.Column(db.Integer, primary_key=True)
score = db.Column(db.String)
date = db.Column(db.String)
# players = db.relationship("Player", secondary=match)
I know I'm supposed to show the relationship in both the Player and Score class to the match table (the commented out part), but if I try to create it, it gives me the following error:
"AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Player.scores - there are multiple foreign key paths linking the tables via secondary table 'match'. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference from the secondary table to each of the parent and child tables."
If I create it without the columns 'scores' and 'players', it creates it just fine, but then I can't query the intermediary table or add rows to it. (Or if that's possible, I don't really know how)
Can someone please explain what I'm doing wrong? Overall I understand how a many-to-many relationship is supposed to work, but I just can't wrap my head around what I'm supposed to do here. screams in confusion

So I found the answer to my question and I'm just gonna leave it here, in case it helps someone else struggling with the same issue...
So I was trying to use a secondary table, but that's only meant to include foreign keys, however my relationship table has other data (id, score, winner...) which turns it into an Association Object based many-to-many relationship. And that one requires mapping the relationship from Parent to the Association object instead of directly onto the child.
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))
parent = db.relationship("Child", back_populates="children")
child = db.relationship("Parent", back_populates="parents")
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 = db.relationship("Association", back_populates="child")

Related

SQL Alchemy Could not determine join condition between parent/child tables but foreign keys are specified

I got table that reference twice to another table, but got AmbiguousForeignKeysError. I specified foreign keys, but have no luck.
My simplified models are:
class User(Base):
id = Column(Integer, primary_key=True)
manager_of = relationship("Project", back_populates="manager")
teamlead_of = relationship("Project", back_populates="teamlead")
class Project(Base):
id = Column(Integer, primary_key=True)
manager_id = Column(Integer, ForeignKey("user.id"))
manager = relationship(
"User", back_populates="manager_of", foreign_keys=[manager_id], uselist=False
)
teamlead_id = Column(Integer, ForeignKey("user.id"))
teamlead = relationship(
"User", back_populates="teamlead_of", foreign_keys=[teamlead_id], uselist=False
)
Error that I got:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.manager_of - 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 reference to the parent table.
What am i doing wrong?

SQLAlchemy delete many to many with Association Object

So I have myself a many to many relationship which requires extra data in the association table. As a result, I have used an Association Object.
I can delete the Association Object itself from the DB just fine, but there doesn't seem to be any cascade taking place. I have looked at the documentation and followed it as best I could (I'm somewhat of a beginner here), but the situation as explained there does not seem to apply to my situation entirely (and I can't quite figure out the way to get it to work right).
So I have three classes. Student, CMClass and StudentClass (this being the Association Object):
class Student(Base):
__tablename__ = 'Students'
id = Column('ID', Integer, primary_key=True, autoincrement=True)
name = Column('NAME', String(50), unique=True)
CMClasses = relationship("StudentClass", back_populates="Student", secondary="StudentClasses", cascade="all, delete")
class CMClass(Base):
__tablename__ = "Classes"
id = Column('ID', Integer, primary_key=True, autoincrement=True)
name = Column('NAME', String(50), unique=True)
classDate = Column('CLASS_DATE', DateTime, nullable=True)
Students = relationship("StudentClass", back_populates="CMClass", secondary="StudentClasses")
class StudentClass(Base):
__tablename__ = 'StudentClasses'
student_id = Column(Integer, ForeignKey('Students.ID', ondelete="CASCADE"), primary_key=True)
CMClass_id = Column(Integer, ForeignKey('Classes.ID', ondelete="CASCADE"), primary_key=True)
attended = Column(Boolean)
Student = relationship("Student", back_populates="CMClasses")
CMClass = relationship("CMClass", back_populates="Students")
It all seems to work rather well honestly, except for the cascade delete. I've played around with the 'relationship' part of the two sub classes (Student and CMClass). I think that's where the issue lies, but honestly I'm just not sure how to set it up. The examples just use an association table, which seems to work a little differently from an association object.

What is the correct way to create object with relationship in SQLAlchemy

I've two models, Parent and Child over a SQLite db.
class Parent(Base):
__tablename__ = 'PARENTS'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False, sqlite_on_conflict_unique='IGNORE')
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = 'CHILDREN'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
parent_id = Column(Integer, ForeignKey('PARENTS.id'), nullable=False)
parent = relationship("Parent", back_populates="children")
I need to create children like the following:
session.add(Child(name="...",parent=Parent(name="...")),
that is I want to create a child having its name and the name of the parent. I would like to know which is the best way to do this. Can I instruct SA to go and select Parents for me retrieving the parent.id from parent.name? Can I leverage some sort of caching mechanism for not having the parent table queried for each adding child (parent changes seldom). I don't need to create a new parent when I create a new child, but I want it to be linked to an already existing parent. Moreover I need to have long lists of children to be created fastly (e.g. for seeding the db).
Thanks.

SQLALCHEMY query on a many to many relationship with map table

I have a database model (see below). I'm working on a project that should filter query results based on user assigned categories - there can be several categories associated to a user. In other words, I want to show the User with Articles that matches his/her assigned categories. Articles can also be associated with several categories. How can I achieve this using sqlalchemy query?
categories_users = db.Table(
'categories_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('category_id', db.Integer(), db.ForeignKey('category.id'))
)
categories_articles = db.Table(
'categories_articles',
db.Column('article_id', db.Integer(), db.ForeignKey('article.id')),
db.Column('category_id', db.Integer(), db.ForeignKey('category.id'))
)
class Category(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
articles = db.relationship('Article', secondary=categories_articles,
backref=db.backref('categories', lazy='dynamic'))
def __str__(self):
return self.name
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
def __str__(self):
return self.name
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False)
categories = db.relationship('Category', secondary=categories_users,
backref=db.backref('users', lazy='dynamic'))
def __str__(self):
return self.email
In a normal m2m you have an association table with join path between the two entities that is clearly defined by foreign keys. As such, you can just tell sqlalchemy which table you want to use to associate two other tables, and it does the rest (As you've done here: secondary=categories_articles). Joining User's to Articles is slightly more complicated.
You want a many to many relationship between User and Article, so you can get the articles that users are interested in based on common categories. Or simply:
User <===> some join path involving categories <===> Article.
Our user table has a foreign key to the categories_users table and our categories_articles table has a foreign key to our articles table. Most importantly, both the categories_users table and the categories_articles table have the category_id field and so we can join those two tables together.
So, you can add an articles relationship to your User model with the following code:
articles = db.relationship(
'Article',
secondary=\
'join(categories_users, categories_articles, '
'categories_users.c.category_id==categories_articles.c.category_id)'
)
Accessing the User.articles attribute then emits this SQL:
SELECT article.id AS article_id, article.name AS article_name
FROM article, categories_users
INNER JOIN categories_articles
ON categories_users.category_id = categories_articles.category_id
WHERE %(param_1)s = categories_users.user_id
AND article.id = categories_articles.article_id
where it's clear to see what is happening. categories_users gets joined with categories_articles effectively making a new table where user rows are combined with article rows on their common categories. SQLAlchemy can then treat this as a "normal" many to many relationship with a single association table and leverage the existing foreign keys to piece together the rest of the relationship and construct the necessary query.

SQLAlchemy one-to-many relationship clarification

After reading the SQLAlchemy documentation, it's still unclear to me how one-to-many relationships are actually supposed to be specified. I'll break down the documentation and explain why I'm confused (http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#one-to-many):
A one to many relationship places a foreign key on the child table
referencing the parent.
It looks like I am to place some Column attribute on the model that will be on the "many" side of the relationship.
relationship() is then specified on the parent, as referencing a collection of items represented by the child:
This means that there is some attribute on the parent that specifies the model participating in the "many" side of the relationship.
This would make total sense to me if it weren't for that fact that there could be a situation where I want to define two one-to-many relationships with the same participants on both sides of the relationship.
How does SQLAlchemy know that the ForeignKey column on the "many" side of the relationship corresponds to the relationship attribute placed on the "one" side?
A one-to-many relationship is set up like this:
class Group(Base):
id = Column(Integer, primary_key=True)
users = relationship(lambda: User)
class User(Base):
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Group.id))
SQLAlchemy infers that you meant to use parent_id as the join condition for users based on the fact that it's the only foreign key linking the two tables.
In the case where you have circular relationships:
class Group(Base):
id = Column(Integer, primary_key=True)
owner_id = Column(Integer, ForeignKey("users.id"))
users = relationship(lambda: User)
class User(Base):
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Group.id))
owned_groups = relationship(Group)
If you try this, it won't work because SQLAlchemy complains that it cannot infer what foreign key to use for each relationship. Instead, you have to tell it explicitly what to use:
class Group(Base):
id = Column(Integer, primary_key=True)
owner_id = Column(Integer, ForeignKey("users.id"))
users = relationship(lambda: User, foreign_keys=lambda: User.parent_id)
class User(Base):
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Group.id))
owned_groups = relationship(Group, foreign_keys=Group.owner_id)
A more complete example with backrefs:
class Group(Base):
id = Column(Integer, primary_key=True)
owner_id = Column(Integer, ForeignKey("users.id"))
users = relationship(lambda: User, foreign_keys=lambda: User.parent_id, back_populates="parent")
owner = relationship(lambda: User, foreign_keys=owner_id, back_populates="owned_groups")
class User(Base):
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Group.id))
owned_groups = relationship(Group, foreign_keys=Group.owner_id, back_populates="owner")
parent = relationship(Group, foreign_keys=parent_id, back_populates="users")