Foreign keys, relationship between models in sqlalchemy flask - python

I have these two models:
class User(db.Model, UserMixin):
__tablename__ = 'users'
__table_args__ = (
PrimaryKeyConstraint('id',),
)
id = db.Column(db.Integer, primarky_key=True)
username = db.Column(db.String, unique=True, nullable=False)
email = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
class Review(db.Model):
__tablename__ = 'reviews'
__table_args__ = (
PrimaryKeyConstraint('id', ),
)
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
I wanted to create a relationship between these two tables so that when a user writes a review it should go to Review model and it has a relation with user_id in Users model. I have tried lots of answers but they are returning different kinds of errors I am confused, can anybody help?

You can use db.relationship to create a one-to-many relationship between users and reviews.
class User(db.Model, UserMixin):
__tablename__ = 'users'
__table_args__ = (
PrimaryKeyConstraint('id',),
)
id = db.Column(db.Integer, primarky_key=True)
username = db.Column(db.String, unique=True, nullable=False)
email = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
reviews = db.relationship('Review', backref='user', cascade='all, delete, delete-orphan')
class Review(db.Model):
__tablename__ = 'reviews'
__table_args__ = (
PrimaryKeyConstraint('id', ),
)
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
This would allow for user.reviews and review.user

Try this:
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship(User, backref='user_reviews', lazy=True)
And read the documentation.

Related

I am getting sqlalchemy.exc.InvalidRequestError in my pythonanywhere error log. How can I fix it?

from yff import db, login_manager, app
from flask_login import UserMixin
import datetime
import jwt
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
password = db.Column(db.String(120), unique=False, nullable=False)
email = db.Column(db.String(50), unique=False, nullable=False)
profile_pic = db.Column(db.String(30), unique=False, default="default.jpg")
is_moderator = db.Column(db.Boolean, default=False)
posts = db.relationship('Posts', backref='author', lazy=True)
likes = db.relationship('Likes', backref='user', passive_deletes=True)
comments = db.relationship('Comment', backref='user', passive_deletes=True)
def get_reset_token(self):
encoded = jwt.encode({'user_id':self.id, "exp":datetime.datetime.now() + datetime.timedelta(hours = 0.5)}, app.config['SECRET_KEY'], algorithm='HS256')
return encoded
#staticmethod
def verify_secret_token(token):
try:
decoded = jwt.decode(token, options={"verify_signature": False})
user_id = decoded['user_id']
except:
return None
return User.query.get(user_id)
def __repr__(self):
return f'{self.username}, {self.email}'
class Image(db.Model):
id = db.Column(db.Integer, primary_key=True)
title=db.Column(db.String(120), nullable=False)
date_created = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now(tz=datetime.timezone.utc))
img_location = db.Column(db.String(600), nullable=False)
mimetype = db.Column(db.String(10))
post_name = db.Column(db.String(150),nullable=False, unique=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
manga_name = db.Column(db.String(100), unique=False, nullable=False)
class Posts(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('image.id', ondelete="CASCADE"), nullable=False)
likes = db.Column(db.Integer, db.ForeignKey('likes.id', ondelete='CASCADE'))
date_created = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now(tz=datetime.timezone.utc))
comments = db.relationship('Comment', backref='Posts', passive_deletes=True)
class Likes(db.Model):
id = db.Column(db.Integer, primary_key=True)
author = db.Column(db.Integer, db.ForeignKey('user.id', ondelete="CASCADE"), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('image.id', ondelete="CASCADE"), nullable=False)
liked_post = db.relationship('Posts', backref='post')
class Comment(db.Model): #import it in the __init__ file
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(1000), nullable=False)
date_created = db.Column(db.DateTime, nullable=False, default=datetime.datetime.now(tz=datetime.timezone.utc))
author = db.Column(db.Integer, db.ForeignKey('user.id', ondelete="CASCADE"), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('image.id', ondelete="CASCADE"), nullable=False)
The following is the entire error:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'mapped class Posts->posts'. Original exception was: Could not determine join condition between parent/child tables on relationship Posts.comments - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
I am getting this error in the pythonanywhere error log. I tried changing the positions of the backref statement but I that did not help. (I am new to web dev and I still haven't figured out how backref works)
I know how foreign keys work in SQL but I am still not experienced at OOP and SQLAlchemy, so please help me out.

List of foreign keys in relationship table flask-sqlalchemy

Hi have two data models (User and Song) and I want users to have several playlists (many to many relationship).
The thing is, inside a playlist there is a user_id and multiple song_ids (the song list could even be empty). However I don't understand how I can make this happen.
Here's what I have so far.
(I removed some fields and the method to simplify)
class User(db.Model):
__tablename__ = "user"
_id = db.Column(db.Integer, primary_key=True)
_first_name = db.Column(db.String, nullable=False)
_last_name = db.Column(db.String, nullable=False)
_username = db.Column(db.String, unique=True, nullable=False)
_email = db.Column(db.String, unique=True, nullable=False)
_password = db.Column(db.String, nullable=False) #TODO: save hashed pwd
_is_admin = db.Column(db.Boolean, default=False)
_accesses = db.relationship(Song.__name__, secondary=Access, backref='_accesses')
_playlists = db.Column(db.JSON) # CHANGE THIS => relationship user-song (?)
class Song(db.Model):
__tablename__ = "song"
_id = db.Column(db.Integer, primary_key=True)
_title = db.Column(db.String, unique=True, nullable=False)
_lyrics = db.Column(db.ARRAY(db.String), nullable=False)
_creator_id = db.Column(db.Integer, nullable=False) #TODO: add user_fk
_accesses = db.relationship(User.__name__, secondary=Access, backref='_accesses')
I've tried to create a relationship table but I just don't understand how to have multiple songs inside the relationship.
Should it be something like this?
playlists = db.Table(
db.Column('id', db.Integer, primary_key=True),
db.Column('name', db.String, nullable=False),
db.Column('user_id', db.Integer, db.ForeignKey(user_fk), nullable= False),
db.Column('songs_ids', db.ARRAY(db.Integer, db.ForeignKey(song_fk))),
db.Column('created_at', db.Datetime, default=datetime.now),
)
Or should I make a relationship song-playlist before making the playlist relationship?

Flask-SQLAlchemy: User role Models and relationship

I am trying to create a Flask-SqlAlchemy database model and need users to be linked to a single role. Example roles: "User", "Admin", "Moderator".
Surely I could use a simple relatioship, which just adds a user_role_id Column to the User, but I would also like to track the last modification.
class User:
id = db.Column(db.Integer, primary_key=True)
class UserRole:
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
class UserRoleLink(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
user_role_id = db.Column(db.Integer, db.ForeignKey("user_role.id"), primary_key=True)
last_modified = db.Column(db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
How do I complete this model with the required relationships? Also I would like to ensure, that a User can only have one Role, but Roles can be used as often as required.
Thank you in advance!
I think it should works for you:
class User:
id = db.Column(db.Integer, primary_key=True)
class UserRole:
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False, unique=True)
role_link = db.relationship('UserRoleLink', cascade='all, delete-orphan', uselist=False)
class UserRoleLink(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
user_role_id = db.Column(db.Integer, db.ForeignKey("user_role.id"), primary_key=True)
last_modified = db.Column(db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)

relationship for flask_sqlalchemy python3.9+

I can't find a way to get all messages from the user via user.messages and the same via channel.messages. I keep getting an error with foreignkeys. What do I need to change to my relationships to make it work?
I would like to be able to:
user.messages and channel.messages
and as an extra (if at all possible):
message.user.username or message.user.id
my classes:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
email = db.Column(db.String, unique=True, nullable=False)
admin = db.Column(db.Boolean, default=False)
mc_access = db.Column(db.Boolean, default=False)
pass_recov_code = db.Column(db.String, default=None)
all_messages = db.relationship("Message",
backref=db.backref("Message"),
primaryjoin="foreign(User.id) == remote(Message.sender_id)",
foreign_keys="[User.id, User.username]",
cascade="all, delete"
)
class Channel(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True, nullable=False)
uses_password = db.Column(db.Boolean, default=False)
password = db.Column(db.String)
online_users = db.Column(db.String, default=json.dumps([]))
messages = db.relationship("Message", cascade="all, delete", foreign_keys="[Channel.id]", backref=db.backref("channel"))
maker_id = db.Column(db.Integer, nullable=False)
class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
sender_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
sender_username = db.Column(db.String, db.ForeignKey("user.username"), nullable=False)
channel_id = db.Column(db.Integer, db.ForeignKey("channel.id"), nullable=False)
message = db.Column(db.String, default="", nullable=False)
send_on = db.Column(db.String, nullable=False)
edited = db.Column(db.Boolean, default=False)
Simply do the following:
class User(db.Model, UserMixin):
...
messages = db.relationship("Message", back_populates="user")
class Channel(db.Model):
...
messages = db.relationship("Message", back_populates="channel")
class Message(db.Model):
...
sender_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
channel_id = db.Column(db.Integer, db.ForeignKey("channel.id"), nullable=False)
user = db.relationship("User", back_populates="messages")
channel = db.relationship("Channel", back_populates="messages")
This way you can use user.messages, channel.messages, message.user.username and message.user.id.
Note: you can also use backref instead of back_populates, but I recommend using back_populates as it is more explicit.

How to attach User model with two different Models so that the properties of those models can be accessed via current_user

I am building a online quiz app where both teacher and students can login. Teachers can create quizzes and students can run those quizzes. How can I attach both Teacher and Student model with User model so that properties like Teacher_Name or Student_Class etc can be accessed via current_user?
This is not a problem regarding user role. Because using flask-security I can do that pretty easily but providing role means grouping users by their access level but not actually identifying them. I mean if a student logs in I need know who is this particular student represented by the current logged in user. Only then I can store his/her result with his/her record.
Followings are my sqlalchemy models...
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
class Student(db.Model):
__tablename__ = 'student'
id = db.Column(db.Integer, primary_key=True)
cls = db.Column(db.String(4), nullable=False, default='V')
sec = db.Column(db.String(1), nullable=False, default='A')
roll = db.Column(db.Integer, nullable=False, default=1)
name = db.Column(db.String(24), nullable=False)
dob = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
sex = db.Column(db.Enum(Gender), nullable=False, default=Gender.male)
results = db.relationship('Result', backref='student', lazy=True)
__table_args__ = (db.UniqueConstraint("cls", "roll", name="cls_roll"),)
class Teacher(db.Model):
__tablename__ = 'teacher'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(24), nullable=False)
subject = db.Column(db.String(4), nullable=False)
quiz_ques = db.Table('quiz_ques', db.Column('quiz_id', db.Integer, db.ForeignKey('quiz.id'), nullable=False),
db.Column('mcq_id', db.Integer, db.ForeignKey('mcq.id'), nullable=False),
db.PrimaryKeyConstraint('quiz_id', 'mcq_id'))
class Quiz(db.Model):
__tablename__ = 'quiz'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50), nullable=False, unique=True)
subject = db.Column(db.String(4), nullable=False)
marks = db.Column(db.Integer, nullable=False, default=1)
time_limit = db.Column(db.Integer, nullable=False, default=20)
questions = db.relationship('MCQ', secondary=quiz_ques, lazy='subquery', backref=db.backref('quizzes', lazy=True))
results = db.relationship('Result', backref='quiz', lazy=True)
class MCQ(db.Model):
__tablename__ = 'mcq'
id = db.Column(db.Integer, primary_key=True)
subject = db.Column(db.String(4), nullable=False)
topic = db.Column(db.String(150), nullable=False)
question = db.Column(db.String(255), nullable=False)
answers = db.Column(db.Text(), nullable=False)
class Result(db.Model):
__tablename__ = 'result'
id = db.Column(db.Integer, primary_key=True)
date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
marks_obtained = db.Column(db.Integer, nullable=False, default=0)
response = db.Column(db.Text(), nullable=True)
stud_id = db.Column(db.Integer, db.ForeignKey('student.id'), nullable=False)
quiz_id = db.Column(db.Integer, db.ForeignKey('quiz.id'), nullable=False)
The code should allow me to do something like this -
current_user.student.name = 'John Doe'
or
current_user.teacher.subject = 'Math'

Categories

Resources