What are the proper way to define a deletion cascade on many-to-many relationship.
Trying to achieve a setup where a deletion of a user only deletes the the user and the relationship stored in 'user_favorited'.
The different scenarios led that the user + relationship and user favorited/favorited_by where deleted.
Adding to the complexity are following parameters which can be added to the relationship and backref.
single_parent
viewonly (stackoverflow)
Goal:
deletion of a first user should not lead to deletion of second user favorited/favorited by. Only First user and relationship(s) should be deleted.
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
username = Column(String(30), nullable=False, unique=True, index=True)
favorites = relationship("User", secondary='user_favorited',
primaryjoin=UserFavorited.user_id==id,
secondaryjoin=UserFavorited.favorite_user_id==id,
backref=backref('favorites_list', lazy='dynamic', cascade='all,delete-orphan'),
cascade="all",
order_by=UserFavorited.favorited_on.desc())
favorited_by = relationship("User", secondary='user_favorited',
primaryjoin=UserFavorited.favorite_user_id==id,
secondaryjoin=UserFavorited.user_id==id,
backref=backref('favorited_list', lazy='dynamic', cascade='all,delete-orphan'),
cascade="all",
order_by=UserFavorited.favorited_on.desc())
class UserFavorited(Base):
__tablename__ = 'user_favorited'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
favorite_user_id = Column(Integer, ForeignKey('user.id'), primary_key=True, index=True)
favorited_on = Column(DateTime(timezone=True), nullable=False)
Related
documentations says:
Mapping a one-to-many or many-to-many relationship results in a collection of values accessible through an attribute on the parent instance. By default, this collection is a list
So I have user with roles and I can access it using user.roles and user.roles.append() method works fine, but I can't remove all mapped entries.
I tried:
user.roles = []
also I tried clear() method and remove() in loop but I always get
sqlalchemy.orm.exc.FlushError: Can't flush None value found in collection Users.roles
this is example of my tables relationships
class Users(sa.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
login = Column(String(50), unique=False)
roles = relationship('Roles', secondary='user_roles_map',
cascade='all, delete')
class Roles(sa.Model):
__tablename__ = 'roles'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(40), unique=True)
class UserRolesMap(sa.Model):
__tablename__ = 'user_roles_map'
id_seq = Sequence(__tablename__ + "_id_seq")
id = Column(Integer(), id_seq, server_default=id_seq.next_value(),
unique=True, nullable=False)
user_id = Column(
Integer, ForeignKey('users.id'),
primary_key=True)
role_id = Column(
Integer, ForeignKey('roles.id'),
primary_key=True)
is it possible to remove all entries by accessing the roles attribute from the parent entry?
call flush() was required after user.roles = []
##CONFIGURE TABLES
class Userdb(db.Model, UserMixin):
__tablename__ = "users"
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(100))
# This will act like a List of BlogPost objects attached to each User.
# The "author" refers to the author property in the BlogPost class.
post = relationship("BlogPost", back_populates="author", lazy='dynamic')
class BlogPost(db.Model):
__tablename__ = "blog_posts"
id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey("users.id"),nullable=False)
# Create reference to the User object, the "posts" refers to the posts protperty in the User class.
author = relationship("Userdb", back_populates="post", lazy='dynamic')
# Create Foreign Key, "users.id" the users refers to the tablename of User.
title = db.Column(db.String(250), unique=True, nullable=False)
subtitle = db.Column(db.String(250), nullable=False)
date = db.Column(db.String(250), nullable=False)
body = db.Column(db.String(10000), nullable=False)
img_url = db.Column(db.String(250), nullable=False)
sqlalchemy.exc.NoForeignKeysError
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Userdb.post - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
I have a User model that has many to many relation with Room model so one user can be subscribed to many rooms and many users can be subscribed to one room. This relation works fine.
# for many to many relationship
user_room = Table('user_room', Base.metadata,
Column('user_id', Integer, ForeignKey('user.id')),
Column('room_id', Integer, ForeignKey('room.id'))
)
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
full_name = Column(String(50), nullable=False)
username = Column(String(50), nullable=False)
email = Column(String(255), nullable=False)
password = Column(String(300), nullable=False)
room = relationship("Room", secondary=user_room, backref='participants', lazy='dynamic')
class Room(Base):
__tablename__ = "room"
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
description = Column(String(200))
created_date = Column(TIMESTAMP, default=datetime.utcnow())
But now I want to add another model which is Profile that should have one to one relation with User model I tried this:
class Profile(Base):
__tablename__ = "profile"
id = Column(Integer, primary_key=True)
avatar = Column(String(500), nullable=True)
bio = Column(String(500), default="I like collobrating on Collab.")
user = relationship("User", uselist=False, backref="profile")
and updated User model like this:
class User(Base):
__tablename__ = "user"
# rest of the columns
profile = Column(Integer, ForeignKey("profile.id"))
but that gives the following error:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't
proceed with initialization of other mappers. Triggering mapper:
'mapped class Profile->profile'. Original exception was: Error creating backref 'profile'
on relationship 'Profile.user': property of that name exists on mapper 'mapped class User->user'
Try this on user and by using backref instead of backpopulates you can remove the profile relationship.
from sqlalchemy.orm import relationship, backref
class User(Base):
__tablename__ = "user"
profile_id = Column(Integer, ForeignKey("profile.id"))
profile = relationship("Profile", backref=backref('profile', use_list=False))
class Team(db.Model):
__tablename__ = "Teams"
id = db.Column(db.Integer, primary_key=True)
flashscore_id = db.Column(db.String(255), nullable=False, unique=True)
name = db.Column(db.String(255), nullable=False)
leagues = db.relationship("League",
secondary=league_teams_table)
standings = db.relationship('Standing', backref='teams', cascade="all,delete")
fixtures = db.relationship('Fixture', backref='teams', cascade="all,delete")
related_fixtures_table = db.Table('RelatedFixtures',
db.Column('fixture_id', db.Integer, db.ForeignKey('Fixtures.id')),
db.Column('related_fixture_id', db.Integer, db.ForeignKey('Fixtures.id')))
class Fixture(db.Model):
__tablename__ = "Fixtures"
id = db.Column(db.Integer, primary_key=True)
home_id = db.Column(db.Integer, db.ForeignKey('Teams.id'),
nullable=False)
away_id = db.Column(db.Integer, db.ForeignKey('Teams.id'),
nullable=False)
home_ref = db.relationship("Teams", backref="fixture", uselist=False, foreign_keys=[home_id])
away_ref = db.relationship("Teams", backref="fixture", uselist=False, foreign_keys=[away_id])
flashscore_id = db.Column(db.String(255), nullable=False, unique=True)
total_goals = db.Column(db.Integer, nullable=False)
total_fh_goals = db.Column(db.Integer, nullable=False, default=0)
total_sh_goals = db.Column(db.Integer, nullable=False, default=0)
total_home_goals = db.Column(db.Integer, nullable=False)
total_away_goals = db.Column(db.Integer, nullable=False)
total_home_fh_goals = db.Column(db.Integer, nullable=False, default=0)
total_home_sh_goals = db.Column(db.Integer, nullable=False, default=0)
total_away_fh_goals = db.Column(db.Integer, nullable=False, default=0)
total_away_sh_goals = db.Column(db.Integer, nullable=False, default=0)
related_fixtures = db.relationship("Fixture",
secondary=related_fixtures_table)
I wrote the above code trying to define multiple relations between Fixture & Team. When I run the application and execute an action I get the following error:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Team.fixtures - 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.
I tried solving this by adding the following two lines:
home_ref = db.relationship("Teams", backref="fixture", uselist=False, foreign_keys=[home_id])
away_ref = db.relationship("Teams", backref="fixture", uselist=False, foreign_keys=[away_id])
But I still get the same error. Can anyone help me out?
You need to configure the fixtures relationship on the Team class, because that's what the error is telling you to do: Could not determine join condition ... on relationship Team.fixtures.
However, you can't fix this situation with foreign_keys; that expects a single (possibly compound) foreign key, and you have two, distinct foreign key relationships. You'd have to create two separate relationships, one for fixtures where this team is the home team, and another one for the away relationship.
If you want Team.fixtures to return a list of all Fixture rows that reference the team via one or both foreign keys, you need to create a custom primaryjoin condition, one that matches your Team.id against the home_id or the away_id column (and you probably want to add a DISTINCT condition to your query to avoid getting duplicate results for fixtures that connect to your team via both foreign keys).
You need to get rid of fixtures = db.relationship('Fixture', backref='teams', cascade="all,delete") in Team.
What you will have at the end is:
class Team(db.Model):
__tablename__ = "Teams"
id = db.Column(db.Integer, primary_key=True)
# ...
class Fixture(db.Model):
__tablename__ = "Fixtures"
id = db.Column(db.Integer, primary_key=True)
home_id = db.Column(db.Integer, db.ForeignKey('Teams.id'), nullable=False)
away_id = db.Column(db.Integer, db.ForeignKey('Teams.id'), nullable=False)
home_ref = db.relationship("Teams", backref="fixture", uselist=False, foreign_keys=[home_id])
away_ref = db.relationship("Teams", backref="fixture", uselist=False, foreign_keys=[away_id])
# ...
You have your foreign keys defined in home_id and away_id, then you 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. Refer to this section in the SQLAlchemy documentation.
I am trying to get a many to many relationship working. I have three tables
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
class Groups(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
class Members(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
I would like to have the option group.members, which should give me all User objects which are member of that group. I implemented it the following way
members = db.relationship('User', secondary="join(Members, User, Members.user_id == User.id)", primaryjoin="and_(Groups.id == Members.group_id)")
this seems to work, but when I delete a group it gives me (sometimes) the error
AttributeError: 'Join' object has no attribute 'delete'
so I guess this is not the right way to implement such a relation.
Any ideas how to do this correctly?
thanks
carl
Perhaps a simpler way to implement this is as follows (adapted from the documentation on Flask-SQLAlchemy
members = db.Table('members',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('group_id', db.Integer, db.ForeignKey('groups.id'))
)
class Groups(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
members = db.relationship('User', secondary=members, backref=db.backref('group', lazy='dynamic'))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
Instead of using a Model for the joining table (members), let's just use a simple table.
With this configuation, you can easily add/remove members and groups:
u = User(username='matt')
g = Groups(name='test')
db.session.add(u)
db.session.add(g)
db.session.commit()
g.members.append(u)
db.session.commit()
db.session.delete(g)
db.session.commit()