Flask-SQLAlchemy query in a many to many ITSELF relationship - python

I have a many to many relationship to itself, that means, my model is User and one field is friends, which would be Users. I have done it the relationship but my problem comes when I try to do the query for the friends of an User. My model and the relation looks like this:
friendship = db.Table('friends',
Column('friend_id', db.Integer, db.ForeignKey('monkeys.id')),
Column('myfriend_id', db.Integer, db.ForeignKey('monkeys.id'))
)
class Monkey (db.Model):
_tablename__ = "monkeys"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique = True)
email = Column(String(120), unique = True)
password = Column(String(50))
date = Column (DateTime(), default=datetime.now())
friends = relationship('Monkey',
secondary = friendship,
primaryjoin = (friendship.c.friend_id == id),
secondaryjoin = (friendship.c.myfriend_id == id),
backref = backref('friendship', lazy = 'dynamic'),
lazy = 'dynamic')
And from de view, if I want to do the query, I have tried with something
friends_list = Monkey.query.join(Monkey.friends).filter(Monkey.id == user.id).all()
Bu it does not work... any help please? thanks!

You don't need to create join by yourself.
change lazy='joined' - items will be loaded “eagerly” in the same query as that of the parent.
When You get object Monkey you already have access to friends
monkey = session.query(Monkey).get(user.id)
friends_list = monkey.friends

Related

Flask-SQLAlchemy update one-to-many record

I have one-to-many relationship models, and here is the snippet of my models:
class ScheduleDayAndTime(db.Model):
__tablename__ = 'schedule_day_and_time'
id = db.Column(db.Integer, primary_key=True)
day = db.Column(db.Enum(DayNameList, name='day'))
start_at = db.Column(db.Time())
end_at = db.Column(db.Time())
requisition_schedule_id = db.Column(db.Integer, db.ForeignKey('requisition_schedule.id'), nullable=True)
class RequisitionSchedule(db.Model):
__tablename__ = 'requisition_schedule'
id = db.Column(db.Integer, primary_key=True)
schedule_day_and_time = db.relationship('ScheduleDayAndTime', backref='requisition_schedule', lazy=True)
# ...
# ...
How to update the data on the many table..?
For now try it like this:
requisition_schedule = RequisitionSchedule.query.filter_by(id=requisition_schedule_id).first()
requisition_schedule.schedule_day_and_time.clear()
db.session.commit()
schedule_day_and_time_1 = ScheduleDayAndTime(
day=form.schedule_day.data,
start_at=form.start_at.data,
end_at=form.end_at.data,
)
schedule_day_and_time_2 = ScheduleDayAndTime(
day=form.schedule_day_2.data,
start_at=form.start_at_2.data,
end_at=form.end_at_2.data,
)
requisition_schedule.schedule_day_and_time.append(schedule_day_and_time_1)
requisition_schedule.schedule_day_and_time.append(schedule_day_and_time_2)
db.session.commit()
I clear the data first, and then append the new data.
But I think that is not the best practice since I still have the old record on my table, it just delete the related ForeignKey id, but still have other records on related column.
So, how to do it in the correct way..?
I figure out this by doing this following:
First, I delete the current record:
ScheduleDayAndTime.query.filter(ScheduleDayAndTime.requisition_schedule_id==requisition_schedule_id).delete()
db.session.commit()
Then I append it like my above question:
schedule_day_and_time_1 = ScheduleDayAndTime(
day=form.schedule_day.data,
start_at=form.start_at.data,
end_at=form.end_at.data,
)
schedule_day_and_time_2 = ScheduleDayAndTime(
day=form.schedule_day_2.data,
start_at=form.start_at_2.data,
end_at=form.end_at_2.data,
)
requisition_schedule.schedule_day_and_time.append(schedule_day_and_time_1)
requisition_schedule.schedule_day_and_time.append(schedule_day_and_time_2)
db.session.commit()
Don't know if this the best practice or not, but I think this better than the solution on my question above.

SqlAlchemy Table and Query Issues

Still wrapping my head around SqlAlchemy and have run into a few issues. Not sure if it is because I am creating the relationships incorrectly, querying incorrect, or both.
The general idea is...
one-to-many from location to user (a location can have many users but users can only have one location).
many-to-many between group and user (a user can be a member of many groups and a group can have many members).
Same as #2 above for desc and user.
My tables are created as follows:
Base = declarative_base()
class Location(Base):
__tablename__ = 'location'
id = Column(Integer, primary_key=True)
name = Column(String)
group_user_association_table = Table('group_user_association_table', Base.metadata,
Column('group_id', Integer, ForeignKey('group.id')),
Column('user_id', Integer, ForeignKey('user.id')))
class Group(Base):
__tablename__ = 'group'
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary=group_user_association_table, backref='group')
desc_user_association_table = Table('desc_user_association', Base.metadata,
Column('desc_id', Integer, ForeignKey('desc.id')),
Column('user_id', Integer, ForeignKey('user.id')))
class Desc(Base):
__tablename__ = 'desc'
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary=desc_user_association_table, backref='desc')
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
user_name = Column(String)
location_id = Column(Integer, ForeignKey('location.id'))
groups = Column(String, ForeignKey('group.id'))
descs = Column(String, ForeignKey('desc.id'))
location = relationship('Location', backref='user')
Here are some examples as to how I am creating the data (all being scraped from the web):
location = Location(id=city[1], name=city[0]) #city = ('name', id)
profile = User()
profile.id = int(str(span2class[0].a['href'][7:]))
profile.user_name = str(span2class[0].a.img['alt'])
profile.location_id = location.id
g = Group(id=gid, name=str(group.contents[0])) # add the group to the Group table
self.db_session.add(g)
# Now add the gid to a list that will be added to the profile that eventually gets added to the user table
profile.groups.append(str(gid)) # stick the gid into the list
profile.groups = ','.join(profile.groups) # convert list to csv string
# Repeat basically same thing above for desc
self.db_session.add(profile)
self.db_session.commit()
As far as queries go, I've got some of the basic ones working such as:
for instance in db_session.query(User).all():
print instance.id, instance.user_name
But when it comes to performing a join to get (for example) group.id and group.name for a specific user.id... nothing I've tried has worked. I am guessing that the form would be something like the following:
db_session.query(User, Group).join('users').filter(User.id==42)
but that didn't work.
Joins works from left to right, so you should join on the relationship from User to Group:
db_session.query(User, Group).join(User.group).filter(User.id == 42)
But this return you a list of tuples (<User>, <Group>), so if the user belongs to 2 or more groups, you will receive 2 or more rows.
If you really want to load both the user and its groups in one (SQL) query, a better way would be to load a user, but configure query to preload groups:
u = (session.query(User)
.options(joinedload(User.group))
.get(42)
)
print("User = {}".format(u))
for g in u.group:
print(" Group = {}".format(g))

delete one-to-one relationship in flask

I'm currently developing an application using flask and I'm having a big problem to delete items in an one-to-one relationship. I have the following structure in my models:
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.String(8), primary_key = True)
password = db.Column(db.String(26))
class Student(db.Model):
__tablename__ = 'student'
user_id = Column(db.String(8), ForeignKey('user.user_id'), primary_key = True)
user = db.relationship('User')
class Professor(db.Model):
__tablename__ = 'professor'
user_id = Column(db.String(8), ForeignKey('user.user_id'), primary_key = True)
user = db.relationship('User')
What I want to do is delete the Student or the Professor if I delete the user. I have tried to test it using the code below, however, when I check my database, the student and professor are still there and my tests don't pass. I tried to include the cascade parameter when I set the relationship but it doesn't work. I have found in the internet to use this parameter: single_parent=True, but it also doesn't work.
user1 = User(user_id='user1234',password='alsdjwe1')
user2 = User(user_id='user2345',password='asfr5421')
student1 = Student(user = user1)
professor1 = Professor(user = user2)
db.session.delete(user1)
db.session.delete(user2)
I'd be glad if somebody can help me with this.
Thank you very much,
Thiago.
Use the cascade argument in your relationships.
class Student(db.Model):
__tablename__ = 'student'
user_id = Column(db.String(8), ForeignKey('user.user_id'), primary_key = True)
user = db.relationship('User', cascade='delete')
class Professor(db.Model):
__tablename__ = 'professor'
user_id = Column(db.String(8), ForeignKey('user.user_id'), primary_key = True)
user = db.relationship('User', cascade='delete')
You might want to look into delete-orphan if your use case needs it.

Flask help understanding primaryjoin/secondaryjoin on a many-to-many relationship

I have the classes User and Listing, and I am trying to create a many-to-many relationship so that a User can have many favorite listings, and any listing can be favorited by many users.
I've been using this as a reference but this is my first many-to-many relationship so any help would be appreciated.
InvalidRequestError: One or more mappers failed to initialize - can't
proceed with initialization of other mappers. Original exception was:
Could not determine relationship direction for primaryjoin condition
'favorites_table.user_id = :user_id_1', on relationship
User.favorites. Ensure that the referencing Column objects have a
ForeignKey present, or are otherwise part of a ForeignKeyConstraint on
their parent Table, or specify the foreign_keys parameter to this
relationship.
models.py
favorites_table = db.Table('favorites_table',
db.Column('user_id', db.Integer, db.ForeignKey('listing.id')),
db.Column('listing_id', db.Integer, db.ForeignKey('user.id'))
)
class User(db.Model):
id = db.Column(db.Integer, primary_key = True)
listings = db.relationship('Listing', backref = 'manager', lazy = 'dynamic')
favorites = db.relationship('Listing',
secondary=favorites_table,
primaryjoin = ('favorites_table.c.user_id == id'),
secondaryjoin = ('favorites_table.c.listing_id == id'),
backref = db.backref('user', lazy = 'dynamic'),
lazy = 'dynamic')
def favorite_listing(self, listing):
if not self.is_favorite(listing):
self.favorites.append(listing)
return self
def unfavorite_listing(self, listing):
if self.is_favorite(listing):
self.favorites.remove(listing)
return self
def is_favorite(self, listing):
return self.favorites.filter(favorites_table.c.listing_id == listing.id).count() > 0
class Listing(db.Model):
id = db.Column(db.Integer, primary_key = True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
Since your model has all required ForeignKeys specified, sqlachemy is smart enough to figure out the parameters for primaryjoin and secondaryjoin itself. So this should work just fine:
favorites = db.relationship('Listing',
secondary = favorites_table,
# primaryjoin = 'favorites_table.c.user_id == User.id',
# secondaryjoin = 'favorites_table.c.listing_id == Listing.id',
backref = db.backref('users', lazy = 'dynamic'),
lazy = 'dynamic',
)
If you really wish to be explicit, you can uncomment the two rows above for exactly the same result. Note that I added a model name before each id column specified.
Please note that in your favorites_table relationship table column user_id points to Listing.id while listing_id -> User.id, while it looks like it should be the other way around.

Filed in SQLAlchemy of user's list

I have one model that is Users in which there is a field in this model that I would like to store a list of Users. The idea is that you can add frieds and store them somewhere.
class User (db.Model):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique = True)
email = Column(String(120), unique = True)
password = Column(String(50))
date = Column(DateTime(), default=datetime.now())
friends = "Should be a list of users"
I have thought to have a string with the id of each user but, is there any posibility to do it with a relationship to the same model? like this:
friends = relationship("User")
Thanks a lot!
Proposed solutions based on Adjacency List Relationships would only work in case when someone can be a friend of maximum one person, which I do not believe to be the case in the real world.
A pattern you need to apply in this case is called Self-Referential Many-to-Many Relationship. Please read the sample linked to above. In order to make it work for your model, you would need to create additional table to keep the pairs of friends, and configure the relationship as below:
# object model
t_userfriend = Table("user_friend", Base.metadata,
Column("user_id", Integer, ForeignKey("users.id"), primary_key = True),
Column("friend_id", Integer, ForeignKey("users.id"), primary_key = True),
)
class User (Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(50), unique = True)
# ...
friends = relationship("User",
secondary = t_userfriend,
primaryjoin = (id == t_userfriend.c.user_id),
secondaryjoin = (id == t_userfriend.c.friend_id),
backref = "friend_of",
)
I guess that the other question you need to ask yourself is whether in your model if A is a friend of B, does this mean that B is a friend of A? In case this is true, you might want/need to:
either store just one side of the relationship, and calculate the other
make sure you always store both sides to the relationship
You can use Adjacency List Relationship and this link have the same issue so you can learn from it.
How to create relationship many to many in SQLAlchemy (python, flask) for model User to itself
Yes you can do it with Adjacency List Relationships.
class User (db.Model):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique = True)
email = Column(String(120), unique = True)
password = Column(String(50))
date = Column(DateTime(), default=datetime.now())
friends = relationship("User",
backref=backref('parent', remote_side=[id])
)

Categories