Fetching a specific column from SQLAlchemy relationship - python

I need to allow a user block other users in my app. My problem occurs when I want to check if a user has been blocked by the current (logged-in user). How do I check if a particular user is in the blocked list of the current user? My models are below:
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
urid = Column(String(50), unique=True)
full_name = Column(String(100))
...
blockedlist = relationship('Blacklist', primaryjoin='Blacklist.user_id==User.id', back_populates='owner')
class Blacklist(db.Model):
__tablename__ = 'blacklist'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
blocked_id = Column(Integer, ForeignKey('users.id'))
date_blocked = Column(DateTime, default=func.now())
owner = relationship('User', primaryjoin='Blacklist.user_id==User.id', back_populates='blockedlist')
blocked = relationship('User', primaryjoin='Blacklist.blocked_id==User.id')
def __init__(self, user_id, blocked_id):
self.user_id = user_id
self.blocked_id = blocked_id
Basically, I want to check that a user's id is in the current user's list of blocked id

You can use .any on a relationship, like so:
alice = User(urid='alice', full_name='Alice')
bob = User(urid='bob', full_name='Bob')
session.add(Blacklist(owner=alice, blocked=bob))
session.commit()
bob_blocked_alice = (
session.query(User.blockedlist.any(blocked_id=alice.id))
.filter(User.id == bob.id)
.scalar()
)
print('Did Bob block Alice:', bob_blocked_alice)
alice_blocked_bob = (
session.query(User.blockedlist.any(blocked_id=bob.id))
.filter(User.id == alice.id)
.scalar()
)
print('Did Alice block Bob:', alice_blocked_bob)
As an aside, you can simplify your relationships using the foreign_keys parameter:
blockedlist = relationship('Blacklist', foreign_keys='Blacklist.user_id', back_populates='owner')
owner = relationship('User', foreign_keys=user_id, back_populates='blockedlist')
blocked = relationship('User', foreign_keys=blocked_id)

Related

Adding multiple foreign keys from same model (FastAPI and SqlAlechemy)

What I am trying to do is have 2 foreign keys from User table inside Ban table here is how I did it:
class Ban(Base):
__tablename__ = "ban"
ban_id = Column(Integer, primary_key=True, index=True)
poll_owner_id = Column(Integer)
banned_by = Column(String , ForeignKey('user.username', ondelete='CASCADE', ), unique=True)
user_id = Column(Integer, ForeignKey('user.user_id', ondelete='CASCADE', ))
updated_at = Column(DateTime)
create_at = Column(DateTime)
ban_to_user = relationship("User", back_populates='user_to_ban', cascade='all, delete')
and User table:
class User(Base):
__tablename__ = "user"
user_id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True)
email = Column(String)
create_at = Column(DateTime)
updated_at = Column(DateTime)
user_to_ban = relationship("Ban", back_populates='ban_to_user', cascade='all, delete')
When I try to run a query to fetch all users like this:
#router.get('/all')
async def get_all_users(db:Session = Depends(get_db)):
return db.query(models.User).all()
I get this error:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'mapped class User->user'. Origina
l exception was: Could not determine join condition between parent/child tables on relationship User.user_to_ban - 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 did the relationship between them as you can see but it states that there is problem between them. If needed I can show you how I did migration for my db using alembic if that is possible cause or is there a cleaner and better way to do this. Thanks in advance
You can have several foreign keys to a single table, like in your case for banned user and banned_by user.
You just need to disambiguate, which ForeignKey for which relationship (docs):
class Ban(Base):
__tablename__ = "ban"
id = Column(Integer, primary_key=True)
banned_user_id = Column(Integer, ForeignKey("user.id")) # for banned_user relationship
banned_by_user_id = Column(Integer, ForeignKey("user.id")) # for banned_by relationship
banned_user = relationship("User", foreign_keys=[banned_user_id], back_populates="bans")
banned_by = relationship("User", foreign_keys=[banned_by_user_id])
Full demo:
from sqlalchemy import (
Column,
ForeignKey,
Integer,
String,
create_engine,
select,
)
from sqlalchemy.orm import Session, declarative_base, relationship
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
username = Column(String, unique=True)
bans = relationship(
"Ban",
back_populates="banned_user",
foreign_keys="Ban.banned_user_id",
)
class Ban(Base):
__tablename__ = "ban"
id = Column(Integer, primary_key=True)
banned_user_id = Column(Integer, ForeignKey("user.id"))
banned_by_user_id = Column(Integer, ForeignKey("user.id"))
banned_user = relationship(
"User", foreign_keys=[banned_user_id], back_populates="bans"
)
banned_by = relationship("User", foreign_keys=[banned_by_user_id])
engine = create_engine("sqlite://", echo=True, future=True)
Base.metadata.create_all(engine)
spongebob = User(username="spongebob")
patrick = User(username="patrickstarr")
spongebob_bans_patrick = Ban(banned_by=spongebob, banned_user=patrick)
with Session(engine) as session:
session.add_all(
[
spongebob,
patrick,
spongebob_bans_patrick,
]
)
session.commit()
with Session(engine) as session:
result = session.scalars(select(Ban)).first()
print(
"User:",
result.banned_user.username,
"was banned by User:",
result.banned_by.username,
)
# User: patrickstarr was banned by User: spongebob

how to create a like and dislike function on python flask

So i been trying to make a like function for my q&a website. however, i'm stuck on database relations part of the models.py. I'm getting an error that says
"sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'mapped class User->user'. Original exception was: Could not determine join condition between parent/child tables on relationship User.posts - 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."
This is my code for the user and post class
class Post(db.Model):
id = db.Column("id", db.Integer, primary_key=True)
title = db.Column("title", db.String(200))
text = db.Column("text", db.String(100))
date = db.Column("date", db.String(50))
#Create Foreign Key
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
comments = db.relationship("Comment", backref="post", cascade="all, delete-orphan", lazy=True)
recipient_id = db.Column(db.Integer, db.ForeignKey('user.id'))
likes = db.relationship('PostLike', backref='post', lazy='dynamic')
and my user class
class User(db.Model):
id = db.Column("id", db.Integer, primary_key=True)
first_name = db.Column("first_name", db.String(100))
last_name = db.Column("last_name", db.String(100))
email = db.Column("email", db.String(100))
password = db.Column(db.String(255), nullable=False)
registered_on = db.Column(db.DateTime, nullable=False)
posts = db.relationship("Post", backref="user", lazy=True)
comments = db.relationship("Comment", backref="user", lazy=True)
liked = db.relationship(
'PostLike',
foreign_keys='PostLike.user_id',
backref='user', lazy='dynamic'
)
def like_post(self, post):
if not self.has_liked_post(post):
like = PostLike(user_id=self.id, post_id=post.id)
db.session.add(like)
def unlike_post(self, post):
if self.has_liked_post(post):
PostLike.query.filter_by(
user_id=self.id,
post_id=post.id).delete()
def has_liked_post(self, post):
return PostLike.query.filter(
PostLike.user_id == self.id,
PostLike.post_id == post.id).count() > 0
my postlike class in the models.py
class PostLike(db.Model):
__tablename__ = 'post_like'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
my flask file for like_action function
#app.route('/like/<int:post_id>/<action>')
def like_action(post_id, action):
post = Post.query.filter_by(id=post_id).first_or_404()
if action == 'like':
session['user_id'].like_post(post)
db.session.commit()
if action == 'unlike':
session['user_id'].unlike_post(post)
db.session.commit()
return redirect(request.referrer)
You have two foreign keys pointing to User on Post:
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
recipient_id = db.Column(db.Integer, db.ForeignKey('user.id'))
so, your User doesn't know where to point
posts = db.relationship("Post", backref="user", lazy=True)
Have something like recipient = db.relationship (..) and author = db.relationship (..) in User model, and make posts = db.relationship("Post", back_populates="author", lazy=True).

SQLAlchemy table defining relationship using two foreign keys

I have two tables, Users and ChatSessions. ChatSessions has two fields, user_id and friend_id, both foreign keys to the Users table.
user_id always contains the user that initiated the chat session, friend_id is the other user. As a certain user can have chat sessions initiated by him, or his friends, he can have his id either as user_id or as friend_id, in various sessions.
Is it possible to define a relationship in the Users table, where i have access to all the chat_sessions of that user, no matter whether his id is in user_id or friend_id?
Something like this:
chat_sessions = db.relationship('chat_sessions',
primaryjoin="or_(User.id==ChatSession.user_id, User.id==ChatSession.friend_id)",
backref="user")
I receive the following error when I try to commit an entry to the Users table:
ERROR main.py:76 [10.0.2.2] Unhandled Exception [93e3f515-7dd6-4e8d-b096-8239313433f2]: relationship 'chat_sessions' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)
The models:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(60), index=True, unique=True)
password = db.Column(db.String(255))
name = db.Column(db.String(100))
active = db.Column(db.Boolean(), nullable=False)
chat_sessions = db.relationship('chat_sessions',
primaryjoin="or_(User.id==ChatSession.user_id, User.id==ChatSession.friend_id)")
class ChatSession(db.Model):
__tablename__ = 'chat_sessions'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
friend_id = db.Column(db.Integer, db.ForeignKey('users.id'))
status = db.Column(db.String(50))
user = db.relationship('User', foreign_keys=[user_id])
friend = db.relationship('User', foreign_keys=[friend_id])
It's difficult to be certain without seeing the tables' code, but it might be sufficient to remove the backref argument.
Here's a pure SQLAlchemy implementation that seems to do what you want:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import orm
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
all_chats = orm.relationship('Chat',
primaryjoin="or_(User.id==Chat.user_id, User.id==Chat.friend_id)")
def __repr__(self):
return f'User(name={self.name})'
class Chat(Base):
__tablename__ = 'chats'
id = sa.Column(sa.Integer, primary_key=True)
user_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'))
friend_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'))
user = orm.relationship('User', foreign_keys=[user_id])
friend = orm.relationship('User', foreign_keys=[friend_id])
def __repr__(self):
return f'Chat(user={self.user.name}, friend={self.friend.name})'
engine = sa.create_engine('sqlite:///')
Base.metadata.create_all(bind=engine)
Session = orm.sessionmaker(bind=engine)
usernames = ['Alice', 'Bob', 'Carol']
session = Session()
users = [User(name=name) for name in usernames]
session.add_all(users)
session.flush()
a, b, c = users
session.add(Chat(user_id=a.id, friend_id=b.id))
session.add(Chat(user_id=a.id, friend_id=c.id))
session.add(Chat(user_id=c.id, friend_id=a.id))
session.commit()
session.close()
session = Session()
users = session.query(User)
for user in users:
for chat in user.all_chats:
print(user, chat)
print()
session.close()
This is the output:
User(name=Alice) Chat(user=Alice, friend=Bob)
User(name=Alice) Chat(user=Alice, friend=Carol)
User(name=Alice) Chat(user=Carol, friend=Alice)
User(name=Bob) Chat(user=Alice, friend=Bob)
User(name=Carol) Chat(user=Alice, friend=Carol)
User(name=Carol) Chat(user=Carol, friend=Alice)

Query in one-to-many relationship

I have two tables which their relation is one-to-many
class User (db.Model):
__tablename__ = 'user_model'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable = False)
messages = db.relationship ('Message' , cascade = 'all,delete', backref = 'user_model' , lazy = True)
class Message (db.Model):
__tablename__ = 'message_model'
id = db.Column(db.Integer, primary_key=True)
context = db.Column(db.Text, nullable = False)
user_id = db.Column(db.Integer, db.ForeignKey('user_model.id'))
Assume for a specific user (user with id = 1) I want to retrieve all messages which are related to this user and the message length is less than 50.
How can I do this?
Something like this:
user = User.query.get (1)
messages_less_than_50 = user.messages.query.filter (len (Message.context) < 50)
Am I able to query on user.messages ?

flask-sqlalchemy, one-many relationship, foreign key changes into NULL

I have user table and album table etc.. and user-album have one-many relationship.
But when a user associates with one or more albums, the foreign key excluding the latest one from the album table changes null. This is the case that user_uid=1 have 3 albums and user_uid=2 have 1 album.(BUT foreign key having user_uid=1 is only just one. And this problem also occurs everywhere having one-many relationship. Here is my code..
class User(Base):
__tablename__ = 'user'
uid = Column(Integer, primary_key=True)
username = Column(String(10), unique=True, nullable=False)
email = Column(String(35), unique=True, nullable=False)
salted_password = Column(String(100), unique=True, nullable=False)
profile_pic = Column(String(100))
authorization = Column(Boolean)
expiry = Column(DATETIME)
fcm_token = Column(String(45))
created_at = Column(DATETIME)
albums = relationship('Album')
notifications = relationship('Notification')
like_photo = relationship('Photo', secondary=like_photo)
follow_album = relationship('Album', secondary=follow_album)
followed = relationship('User',
secondary=followers,
primaryjoin=(followers.c.follower_id == uid),
secondaryjoin=(followers.c.followed_id == uid),
backref=backref('followers', lazy='dynamic'),
lazy='dynamic')
comment_photo = relationship('Photo', secondary=comment)
class Album(Base):
__tablename__ = 'album'
aid = Column(Integer, primary_key=True)
title = Column(String(45), nullable=False)
created_at = Column(DATETIME)
user_uid = Column(Integer, ForeignKey('user.uid'))
photos = relationship('Photo')
album_tags = relationship('Album_tag')
And I updated album table like below..
u = User.query.filter(User.uid == session['uid']).first()
u.albums = [Album(title=request.json['title'], created_at=datetime.utcnow())]
db_session.add(u)
db_session.commit()
I don't know why..
I believe you need to do it the other way around, since in your way, you are overriding user's albums list:
coffee_album = Album(title=request.json['title'], \
created_at=datetime.utcnow())
u = User.query.filter(User.uid == session['uid']).first()
coffe_album.user_uid = u.uid
db_session.add(coffee_album)
db_session.commit()

Categories

Resources