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).
Related
I'm using flask-sqlalchemy, this is not the first relations that i've built, but for some reason it gives me an error when i start flask:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'mapped class User->users'. Original exception was: 'Table' object has no attribute 'sender_id'
These are two models that i'm trying to connect via ForeignKeys:
User:
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
#relations
posts = db.relationship('Post', back_populates='author', lazy='dynamic')
messages_sent = db.relationship('Message', foreign_keys='messages.sender_id',
back_populates='author', lazy='dynamic')
messages_received = db.relationship('Message', foreign_keys='messages.recipient_id',
back_populates='recipient', lazy='dynamic')
last_message_read_time = db.Column(db.DateTime)
followed = db.relationship(
'User', secondary=followers,
primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id),
backref=db.backref('followers', lazy='dynamic'), lazy='dynamic')
def __repr__(self):
return '<User {}>'.format(self.username)
And Messsage:
class Message(db.Model):
__tablename__ = 'messages'
id = db.Column(db.Integer, primary_key=True)
sender_id = db.Column(db.Integer, db.ForeignKey('users.id'))
recipient_id = db.Column(db.Integer, db.ForeignKey('users.id'))
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
#relations
author = db.relationship('User', back_populates='messages_sent', lazy='dynamic')
recipient = db.relationship('User', back_populates='messages_received', lazy='dynamic')
def __repr__(self):
return '<Message {}>'.format(self.body)
I've checked the database itself and it certainly has sender_id in the correct table, tried to change "foreign_keys" parameter to directly name of model Message.sender... Tried to change parameters of lazy on those relations. It still gives me same error.
This is subtle but messages in this case is a table so columns are referenced off of c, like messages.c.sender_id. To use the column of the model class (the mapped class) you would do Message.sender_id.
So...
messages_sent = db.relationship('Message', foreign_keys='messages.c.sender_id',
back_populates='author', lazy='dynamic')
# OR
messages_sent = db.relationship('Message', foreign_keys='Message.sender_id',
back_populates='author', lazy='dynamic')
There is some information here but it doesn't explain the table vs class situation: handling-multiple-join-paths I think I would just use ORM style references until you are more comfortable and then you could use table references if needed.
I have a example about one-to-many relationship in Flask-SQLAlchemy:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
email = db.Column(db.String(120), unique=True)
posts = db.relationship('Post', backref='user')
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('user.id')
According to Declaring Models, I can get list of posts of User U by U.posts. But in constrast, how can I get author's name of Post P as an attribute like P.author_name, instead of P.user.name?
try P.user, according to what you defined in your backref
I can modify the Post class by:
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('user.id')
#cached_property
def author_name(self):
return self.user.name
then, it will see P.author_name as P.user.name.
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()
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
I have a 3 tables: users, posts and comments. I'm trying to get username of comment author.
This is my models.py:
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Integer, unique=True, index=True)
comments = db.relationship('Comment', backref='author', lazy='dynamic')
#i'm trying to:
comment_author = db.relationship('Comment', backref='comment_author_username', lazy='dynamic')
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(32), index=True)
comments = db.relationship('Comment', backref='post', lazy='dynamic')
body = db.Column(db.Text)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
#im trying to do:
#comment_author_username = db.Column(db.String(64), db.ForeignKey('users.username'))
but getting an error:
AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.comments - 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.
If i'm using in template {{comment.author_id}} it works properly(shows comment author id), but {{comment.author_id.username}} shows nothing. How i can get comment author username?
You are making TWO relation from table User (comments, comment_author) to table Comment (That basically they are the same).
The author_id in table Post has a db.ForeignKey('users.id') but there is no refer to table Post in your User table.
Basically what you want is, There are some posts that they have their own author, and for each post there are some comments that they have also their authors. The relation between your Post and User is One-To-Many and the relation between your Post and Comment is also One-To-Many. The Relation between Comment and User is One-To-Many. By sqlalchemy, Your Tables will be like below:
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Integer, unique=True, index=True)
posts = db.relationship('Post', backref='poster', lazy='dynamic')
comments = db.relationship('Comment', backref='commenter', lazy='dynamic')
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(32), index=True)
body = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
comments = db.relationship('Comment', backref='comment_on_post', lazy='dynamic')
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
and for adding data, you can do like:
s = db.session
user = User(username='Alex')
post = Post(title='First try', body='This is my first try!')
comment = Comment(body='This is a useful post!')
user.posts.append(post)
user.comments.append(comment)
post.comments.append(comment)
s.add(user)
s.add(post)
s.add(comment)
s.commit()
s.close()
and for retrieve data:
s = db.session
comments = models.Comment.query.all()
for c in comments:
print c.user_id
s.close()