How to delete mapped children from associations table in sqlalchemy using relationships - python

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 = []

Related

Could not determine join condition between parent/child tables, setting foreign_keys

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.

SQLAlchemy cascade delete on multiple many-to-many relationships

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)

Flask-SQLAlchemy Many-to-Many with composite key

I am trying to build a Many-to-Many relationship using Flask-SQLAlchemy using two primary keys from one model and one from another. My models are the following:
Service:
class Service(db.Model):
"""
Service model object
"""
name = db.Column(db.String(120), primary_key=True, nullable=False)
description = db.Column(db.Text)
ContactClosure:
class ContactClosure(db.Model):
module = db.Column(db.Integer, primary_key=True, nullable=False)
relay = db.Column(db.Integer, primary_key=True, nullable=False)
status = db.Column(db.Boolean)
description = db.Column(db.Text)
#Relationships
hostname = db.Column(db.String(120), db.ForeignKey('ip2cc.hostname'), primary_key=True, nullable=False)
device_name = db.Column(db.String(120), db.ForeignKey('device.name'), primary_key=True, nullable=False)
services = db.relationship('Service', secondary=cc_services, backref=db.backref('contact_closures', lazy='dynamic'))
This is the related table:
cc_services = db.Table('cc_services',
db.Column('service_name', db.String(120), db.ForeignKey('service.name')),
db.Column('hostname', db.String(120), db.ForeignKey('contact_closure.hostname')),
db.Column('device_name', db.String(120), db.ForeignKey('contact_closure.device_name')),
)
And this is the error I am getting:
"AmbiguousForeignKeysError: Could not determine join condition between
parent/child tables on relationship ContactClosure.services - there
are multiple foreign key paths linking the tables via secondary table
'cc_services'. 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 anybody can tell what is the problem here I will be highly thankful, I've been stuck on this for a while...
Ok, finally I found the solution to this problem:
cc_services = db.Table('cc_services',
db.Column('service_name', db.String(120),
db.ForeignKey('service.name')),
db.Column('cc_hostname', db.String(120)),
db.Column('cc_module', db.Integer),
db.Column('cc_relay', db.Integer),
ForeignKeyConstraint(
('cc_hostname', 'cc_module', 'cc_relay'),
('contact_closure.hostname', 'contact_closure.module', 'contact_closure.relay')
)
)
If a model has multiple keys, they must be declared on the helper table on a ForeignKeyConstraint statement.
You need to define 'foreign_keys' argument as the error text says, like this:
services = db.relationship('Service', secondary=cc_services, backref=db.backref('contact_closures', lazy='dynamic'), foreign_keys=[column_name])
If you're using an association table or fully declared table metadata, you can use the primary_key=True in both columns, as suggested here.
Association table example:
employee_role = db.Table(
"employee_role",
db.Column("role_id", db.Integer, db.ForeignKey("role.id"), primary_key=True),
db.Column("employee_id", db.Integer, db.ForeignKey("agent.id"), primary_key=True),
)
Metadata example:
# this is using SQLAlchemy
class EmployeeRole(Base):
__tablename__ = "employee_role"
role_id = Column(Integer, primary_key=True)
employee_id = Column(Integer, primary_key=True)
# this is using Flask-SQLAlchemy with factory pattern, db gives you access to all SQLAlchemy stuff
class EmployeeRole(db.Model):
__tablename__ = "employee_role"
role_id = db.Column(db.Integer, primary_key=True)
employee_id = db.Column(db.Integer, primary_key=True)
Alembic migration for it:
op.create_table(
'employee_role',
sa.Column('role_id', sa.Integer(), nullable=False),
sa.Column('employee_id', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('role_id', 'employee_id')
)
SQL:
CREATE TABLE agent_role (
role_id INTEGER NOT NULL,
employee_id INTEGER NOT NULL,
PRIMARY KEY (role_id, employee_id)
);
In terms of relationship, declare it on one side (this should give you role.employees or employee.roles which should return a list):
# this is using Flask-SQLAlchemy with factory pattern, db gives you access to all SQLAlchemy stuff
class Employee(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
roles = db.relationship("Role", secondary=employee_role, backref="employee")
Your Role class can be:
# this is using Flask-SQLAlchemy with factory pattern, db gives you access to all SQLAlchemy stuff
class Role(db.Model):
__tablename__ = "role"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(25), nullable=False, unique=True)

SQLAlchemy - Array of multiple types without inheritance

I have an existing set of sqlalchemy models like so
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String)
first_name = Column(String)
last_name = Column(String)
email = Column(String)
user_group = Table('user_group', Base.metadata,
Column('user_id', Integer, ForeignKey('users.id')),
Column('group_id', Integer, ForeignKey('groups.id'))
)
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(String)
members = relationship("User", secondary=user_group)
and I would like to create a third model which can have a 1 to many relationship with either a User or a Group type. So I want to end up with something that can be used like this
class Contact(Base):
__tablename__ = 'contacts'
id = Column(Integer, primary_key=True)
type_id = Column(Integer) # the id of the group or user row which it refers to.
type = Column(String) # 'group' or 'user'
class Thing(Base):
__tablename__ = 'things'
relationship('Contact') # Array of Contacts
where
thing = Thing()
thing.contacts[0].type # returns 'group' or 'user'
thing.contacts[0].contact # returns the actual User or Group object
I know this can be done with inheritance but is there any alternative? I'd rather not have to subclass my basic User and Group models.
Thanks in advance!
Read Generic Associations section of the documentation.
Your description matches the case of the Generic ForeignKey

SQLAlchemy Relationships between 3 tables

I am using Python 2.7.2 and SQLAlchemy 0.9.3.
A Department has many groups. A Group has many members. A Member can only be in one group.
Currently to get a list of all the members of a Department. I first get all the groups and then get the members of those groups.
Is there a way to go directly from a department to members without going via groups table ?
class Department(Base):
__tablename__ = 'departments'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True)
location = Column(String(32), unique=True)
groups = relationship("Group", backref="department")
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True)
department_id = Column(Integer, ForeignKey('departments.id'))
members = relationship("Member", backref="group")
class Member(Base):
__tablename__ = 'members'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'))
This kind of query requires a join statement:
q = session.query(Member).\
join(Member.group).\
join(Group.department).\
filter(Department.name=='depart_name')
result = q.all()
This will return a list of Member Objects where the member is part of the department depart_name

Categories