Sqlalchemy multiple many to many - python

I'm pretty new to Sqlalchemy and in general I'm not a SQL guru. I'm currently trying to build a model to manage different workspaces (you can see them as organizations), with the following requirements:
A user can create multiple workspaces
When a user creates a workspace, it has automatically assigned the "owner" Role
One user can have just one role in the workspace
Every workspace can contain multiple users with different roles
In every workspace I can create Teams
Different workspaces can have the same team name (but different team_id)
Teams can live without users assigned to them, but they stick to workspaces
Users can be assigned to a team by the owner of the workspace
This is the code I wrote until now:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, autoincrement=True)
email = Column(String, unique=True, index=True, nullable=False)
first_name = Column(String)
last_name = Column(String)
password = Column(String, nullable=False)
is_active = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
class Workspace(Base):
__tablename__ = "workspace"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
description = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
users = relationship(User, lambda: user_workspace, backref='workspaces')
class Role(Base):
__tablename__ = "role"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
capabilities = Column(Integer)
class Team(Base):
__tablename__ = "team"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
workspace = relationship(Workspace, lambda: user_workspace, backref='teams')
user_workspace = Table(
'user_workspace', Base.metadata,
Column('user_id', Integer, ForeignKey(User.id), primary_key=True),
Column('workspace_id', Integer, ForeignKey(Workspace.id), primary_key=True),
)
Now if I create a new workspace I'm able to automatically populate the user_workspace table with the workspace_id and the user_id. But currently I'm a bit blocked on how to extend the user_workspace table so it can contain references also to role_id and team_id.
Anyone can suggest me the best approach? I searched a lot, but I didn't really find something similar to this (maybe it's just me being newbie with that stuff). Thanks you in advance!

Related

SqlAlchemy - two foreign keys and bidirectional relationship

I have a small base for a website for a real estate agency, below are two tables:
class Person(Base):
__tablename__ = "people"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
surname = Column(String, nullable=False)
city = Column(String, nullable=True)
# TODO - add lists
class Property(Base):
__tablename__ = "properties"
id = Column(Integer, primary_key=True, index=True)
city = Column(String, nullable=False)
address = Column(String, nullable=False)
owner_id = Column(Integer, ForeignKey("people.id"), nullable=False)
manager_id = Column(Integer, ForeignKey("people.id"), nullable=False)
# TODO - rework
owner = relationship("Person", foreign_keys=[owner_id], backref=backref("owners"))
manager = relationship("Person", foreign_keys=[manager_id], backref=backref("managers"))
I would like my 'Person' object to have two lists of properties - "owned_properites" and "properties_to_manage" (without losing reference to the owner/manager in the 'Property' class). But i don't know how to define a relationship to make auto mapping work properly.
If the class 'Property' only had one foreign key to the 'Person', for example - only "owner_id" key and "owner" object then it would be simple:
#in Property
owner_id = Column(Integer, ForeignKey("people.id"), nullable=False)
owner = relationship("Person", back_populates="property")
#in Person
owned_properties = relationship("Property", back_populates="owner")
But how to do the same with two keys, as shown at the beginning?

Sqlalchemy delete orphaned relation with one-to-many

I have two tables, User and Profiles. A user can have many profiles. If the user is deleted all the profiles are deleted. However I want to also make it so if a user has no profiles the user is also delete (assume I always insert a user with at least one profile).
I looked into delete-orphan but I'm not totally sure where I should put this or if it does what I need?
Any help is appreciated.
https://docs.sqlalchemy.org/en/13/orm/cascades.html#delete-orphan
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
active = db.Column(db.Boolean(), default=True, nullable=False)
created_date = db.Column(db.DateTime, default=func.now(), nullable=False)
profile_ = db.relationship("Profile", back_populates="user_", cascade="all,delete")
class Profile(db.Model):
__tablename__ = 'profile'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
user_ = db.relationship("User", back_populates="profile_")

SQLAlchemy multiple joins to single table

I am making a wishlist app and I want to have db schema like bellow, but I can't figure out how to make the joins in sqlalchemy (this is the first time I am using sqlalchemy).
DB schema
(user : wish = 1 : N)
When I select a user, I want to get a list of wishes and each wish may contain a different user (an arranger of the wish)
So I could do something like this
first_user = User.query.get(1)
user_wishes = first_user.wishes.all()
for wish in user_wishes:
if wish.arranger is not None:
print(wish.id, wish.owner.id, wish.arranger.id)
else:
print(wish.id, wish.owner.id)
I have looked up some tutorials, but I only found simple relations.
I need a relation from User to Wish and in the Wish, back to both the UserWishOwner (the user from which I got here) a UserWishArranger (if there is any).
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
wishes = db.Column(db.relationship('Wish', backref='owner', lazy='dynamic'))
class Wish(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
arranger_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
arranger = relationship("User", foreign_keys=[arranger_id])
I have come up with some code, but am a bit confused, because owner_id and arranger_id are the same...
What do I need to do, to make this work?
Just like this
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
wishes = db.relationship('Wish', backref='owner', lazy='dynamic', foreign_keys="[Wish.owner_id]")
class Wish(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
arranger_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
arranger = db.relationship("User", foreign_keys=[arranger_id])

How to set one to many and one to one relationship at same time in Flask-SQLAlchemy?

I'm trying to create one-to-one and one-to-many relationship at the same time in Flask-SQLAlchemy. I want to achieve this:
"A group has many members and one administrator."
Here is what I did:
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
members = db.relationship('User', backref='group')
admin = db.relationship('User', backref='admin_group', uselist=False)
def __repr__(self):
return '<Group %r>' % (self.name)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
admin_group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
However I got an error:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join
condition between parent/child tables on relationship Group.members -
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.
Does anyone know how to do that properly?
The solution is to specify the foreign_keys argument on all relationships:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'))
admin_group_id = Column(Integer, ForeignKey('groups.id'))
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
members = relationship('User', backref='group', foreign_keys=[User.group_id])
admin = relationship('User', backref='admin_group', uselist=False, foreign_keys=[User.admin_group_id])
Perhaps consider the admin relation in the other direction to implement "a group has many members and one admin":
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'))
group = relationship('Group', foreign_keys=[group_id], back_populates='members')
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
members = relationship('User', foreign_keys=[User.group_id], back_populates='group')
admin_user_id = Column(Integer, ForeignKey('users.id'))
admin = relationship('User', foreign_keys=[admin_user_id], post_update=True)
See note on post_update in the documentation. It is necessary when two models are mutually dependent, referencing each other.
The problem you're getting comes from the fact that you've defined two links between your classes - a User has a group_id (which is a Foreign Key), and a Group has an admin (which is also defined by a Foreign Key). If you remove the Foreign Key from the admin field the connection is no longer ambiguous and the relationship works. This is my solution to your problem (making the link one-to-one):
from app import db,app
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
admin_id = db.Column(db.Integer) #, db.ForeignKey('user.id'))
members = db.relationship('User', backref='group')
def admin(self):
return User.query.filter_by(id=self.admin_id).first()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
The one drawback to this is that the group object doesn't have a neat admin member object you can just use - you have to call the function group.admin() to retrieve the administrator. However, the group can have many members, but only one of them can be the administrator. Obviously there is no DB-level checking to ensure that the administrator is actually a member of the group, but you could add that check into a setter function - perhaps something like:
# setter method
def admin(self, user):
if user.group_id == self.id:
self.admin_id = user.id
# getter method
def admin(self):
return User.query.filter_by(id=self.admin_id).first()
Ok, I found a workaround for this problem finally. The many-to-many relationship can coexist with one-to-many relationship between the same two tables at the same time.
Here is the code:
groups_admins = db.Table('groups_admins',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('group_id', db.Integer, db.ForeignKey('group.id'))
)
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
members = db.relationship('User', backref='group')
admins = db.relationship('User',
secondary=groups_admins,
backref=db.backref('mod_groups', lazy='dynamic'),
lazy='dynamic')
def __repr__(self):
return '<Group %r>' % (self.name)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
I still want someone to tell me how to set one-to-many and one-to-one relationship at the same time, so I leave my answer here and won't accept it forever.
This link solved it for me
most important thing is to specify foreign_keys value in the relation as well as the primary join

SQLAlchemy Polymorphic Loading

I've this model in SQLAlchemy:
class User(Base):
__tablename = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
type = Column(Text, nullable=False)
user_name = Column(Text, unique=True, nullable=False)
__mapper_args__ = {'polymorphic_on': type}
class Client(User):
__tablename__ = 'clients'
__mapper_args__ = {'polymorphic_identity': 'client'}
id = Column(Integer, ForeignKey('users.id'), primary_key=True)
client_notes = Column(Text)
This is a joined table inheritance. The problem is when I'm querying User:
self.session.query(User).all()
all I get is records from clients, while what I want is all record on User without Client. How do I solve this problem?
Edit: I'm using SQLAlchemy 0.7.4 and Pyramid 1.3a3
session.query(User) does not perform any filtering based on the value of type column.
However, the SQL SELECT statement it generates selects only data from the users table (unless with_polymorphic(...) is used).
But you can add the filter explicitely to achive the desired result:
session.query(User).filter(User.type=='user').all()

Categories

Resources