To add the task, I did:
task = TaskList(task_name=form_add_task.task_name.data, doer=current_user) db.session.add(task) db.session.commit()
Now, user and tasks has one to many relationship. What I am trying to do in the UI is display checkbox for each of the task for the specific user(task_status column). If the user selects checkboxes(which could be one or many) and click on update button, I want to change the task_status to 1 by updating it.
How do I just update the task_status to 1 for a specific user if they select one or more checkboxes?
models.py
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False, index=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(128))
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
tasks = db.relationship('TaskList', backref='doer', lazy='dynamic')
class TaskList(db.Model):
id = db.Column(db.Integer, primary_key=True)
task_name = db.Column(db.String(140), nullable=False)
task_status = db.Column(db.Integer, default=0)
date_created = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
forms.py
class AddTaskForm(FlaskForm):
task_name = StringField('task', validators=[DataRequired()])
add_task_submit = SubmitField('Add Task')
class UpdateTaskForm(FlaskForm):
task_status = BooleanField()
update_task_submit = SubmitField('Update Task')
routes.py
#app.route('/mytask', methods=['POST', 'GET'])
#login_required
def my_task():
form_add_task = AddTaskForm()
form_update_task = UpdateTaskForm()
if form_add_task.validate_on_submit():
task = TaskList(task_name=form_add_task.task_name.data, doer=current_user)
db.session.add(task)
db.session.commit()
user = User.query.filter_by(username=current_user.username).first()
task_list = user.tasks.filter_by(task_status=0)
if form_update_task.validate_on_submit():
if form_update_task.task_status.data is True:
**HOW DO I UPDATE THE CHECKBOXES**
return render_template('my_task.html', title='my tasks',
form_add_task=form_add_task,
form_update_task=form_update_task,
task_list=task_list,
user=user
)
Firstly, you need something in your update task form to identify your task. Right now the object only has a boolean field but it has no way to tell flask or sql alchemy on which task should be updated. I would thus add task.id or, following your logic, task.task_name fields to the update form.
class UpdateTaskForm(FlaskForm):
#task_id = IntegerField(#add relevant params here#)
task_name = StringField('task', validators=[DataRequired()])
task_status = BooleanField()
update_task_submit = SubmitField('Update Task')
Afterwards i would use the task.id or task.task_name fields to fetch the task that needs to be updated from the database, set the task_status field to 1 and persist it.
task = TaskList.query.filter_by(id=form_update_task.id).first()
# or
# task = TaskList.query.filter_by(task_name=form_update_task.name).first()
if task is not None:
task.task_status = 1
db.session.add(task)
db.session.commit()
Related
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).
models.py
#login_manager.user_loader
def load_user(username):
return User.query.get(username)
class User(db.Model,UserMixin):
__tablename__ = 'user_accounts'
id = db.Column(db.Integer,primary_key=True)
username = db.Column(db.String(50),unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20),default='default.jpg')
password = db.Column(db.String(80), nullable=False)
task = db.relationship('Tasks', backref='author', lazy=True)
def __repr__(self):
return f'User <{self.id}> {self.username}'
class Tasks(db.Model):
__tablename__ = 'tasks'
id = db.Column(db.Integer,primary_key=True)
title = db.Column(db.String(100),nullable=False)
content = db.Column(db.Text)
user = db.Column(db.String(50),db.ForeignKey('user_accounts.username'),nullable=False)
date_created = db.Column(db.DateTime, default=datetime.datetime.utcnow,nullable=False)
completed = db.Column(db.Boolean,default=False,nullable=False)
def __repr__(self):
return f'{self.title} by {self.user} ({self.date_created})'
Error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'tasks.author' could not find table 'user' with which to generate a foreign key to target column 'username'
Here I have a problem that when I trying to run db.create_all() in terminal I am getting the above error message. I am a bit confused in the usage of backref for db.relationship as my target is to represent a user could have specific task and a task is owned by that user so may I ask how could I properly organize the relationship between two tables in order to create a table with no error messages?
I am using Flask as a Rest API for my WebApp.
In the frontend i use the User object quite often, which is why i need it from the backend to work with the user data.
My concern is, that the user object has an attribute password, which is obviously also sent to the frontend, when i make a request for a user object.
Should i define another class like UserPublic to send to the frontend and just strip out the password or is there a better way to do this with Flask, SQLAlchemy, Marshmallow?
I'm not sure if it's even a problem sending the password hash+salt to the frontend. I mean, i don't need it there, so why send it? Password check for login purposes is done in the backend anyway.
This is my User class:
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True,autoincrement=True, nullable=False)
public_id = db.Column(db.String, nullable=False)
fname = db.Column(db.String, nullable=False)
lname= db.Column(db.String, nullable=False)
bday = db.Column(db.Date, nullable=False)
street = db.Column(db.String, nullable=False)
zip = db.Column(db.String, nullable=False) #Zip used
city = db.Column(db.String, nullable=False)
country = db.Column(db.String, nullable=False, default='Germany')
password = db.Column(db.String, nullable=False)
admin = db.Column(db.Boolean, default=False)
email = db.Column(db.String, nullable=False)
iban = db.Column(db.String)
bic = db.Column(db.String)
gender = db.Column(db.CHAR, default='m', nullable=False)
created_by = db.Column(db.String)
updated_by = db.Column(db.String)
membership_status_id = db.Column(db.Integer, db.ForeignKey('membership_status.membership_status_id'))
member_since = db.Column(db.Date)
bookings = db.relationship('Booking', backref="User", lazy='select')
Marshmallow Schema:
class UserSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = User
include_fk = True
This is the endpoint to get a user object:
#app.route('/users/<public_id>', methods=['GET'])
#jwt_required
def get_user(public_id):
logger.info('Getting user with id: '+str(public_id))
current_user = User.query.filter_by(public_id=get_jwt_identity()).first()
if not current_user.admin:
return jsonify({'message' : 'Not privileged for this action'})
user = User.query.filter_by(public_id=public_id).first()
if not user:
return jsonify({'message' : 'No user found with id '+str(public_id)})
user_schema = UserSchema()
return user_schema.jsonify(user), 200
See doc about overriding generated fields.
Here's how to exclude the field from the auto-generated schema:
class UserSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = User
include_fk = True
exclude = ("password", )
# You may want to only exclude id on dump but keep it on load
# In this case, add it here by calling `auto_field` yourself
password = ma.auto_field(load_only=True)
This question already has an answer here:
How to implement following/followers relationship in SQLAlchemy
(1 answer)
Closed 5 years ago.
I have a user class as detailed below:
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.Unicode(length=128), unique=True)
username = db.Column(db.Unicode(length=128), unique=True)
_password = db.Column("password", db.String(length=60))
admin = db.Column(db.Boolean, default=False)
joined = db.Column(db.DateTime, default=datetime.utcnow)
confirmed = db.Column(db.Boolean, default=False)
profile_picture = db.Column(db.Unicode(length=128), unique=True, nullable=True)
twitter = db.Column(db.Unicode(length=256), unique=True, nullable=True)
github = db.Column(db.Unicode(length=256), unique=True, nullable=True)
I would like to add another column to the user class which is a list of users. How can I accomplish this?
I think the proper name of what I am looking for is a self-referential one-to-many relationship.
Based on your comment you want to store a association table that stores which user follows which user(s). This is what is known as a many-to-many relation. Since a user can follow many other users, and a user can be followed by many users.
For that we need to define an additional table, and relationship(http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many) to specify the usage of that table, for instance:
class UserFollows(db.Model):
__tablename__ = 'user_follows'
follower = Column(Integer, ForeignKey('users.id'))
followee = Column(Integer, ForeignKey('users.id'))
Now we can define two virtual columns to the User class and specify that SQLAlchemy should look into the user_follows table for this:
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.Unicode(length=128), unique=True)
username = db.Column(db.Unicode(length=128), unique=True)
_password = db.Column("password", db.String(length=60))
admin = db.Column(db.Boolean, default=False)
joined = db.Column(db.DateTime, default=datetime.utcnow)
confirmed = db.Column(db.Boolean, default=False)
profile_picture = db.Column(db.Unicode(length=128), unique=True, nullable=True)
twitter = db.Column(db.Unicode(length=256), unique=True, nullable=True)
github = db.Column(db.Unicode(length=256), unique=True, nullable=True)
followers = db.relationship('User',
secondary = followers,
primaryjoin = (UserFollows.c.follower == id),
secondaryjoin = (followers.c.followee == id))
follows = db.relationship('User',
secondary = followers,
primaryjoin = (UserFollows.c.followee == id),
secondaryjoin = (followers.c.follower == id))
Now a User object has two attributes followers and follows which act as collections of users that store the persons the User follows as well as the followers of that User.
Ive created two models, Basket and BasketItem, shown below. A basket can have many basket items so I'm using a foreign key to create this relationship.
You can see that the Basket model has a updated_at field which holds a timestamp of when the basket was last updated. I would like this field to be updated when a BasketItem is added, removed or updated.
I've added an event listener but my solution doesn't seem very elegant. Is there a better way of doing this?
class Basket(Base):
__tablename__ = "baskets"
id = Column(String(45), primary_key=True, default=uuid4)
shop = Column(String(45), nullable=False)
currency_code = Column(String(3), nullable=False)
total = Column(String(15), nullable=False, default=0)
status = Column(Enum("open", "closed"), nullable=False, default="open")
created_at = Column(DateTime, default=datetime.datetime.utcnow)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.utcnow)
items = relationship("BasketItem", backref="basket")
class BasketItem(Base):
__tablename__ = "basket_items"
basket_id = Column(String(45), ForeignKey('baskets.id'), primary_key=True)
product_url = Column(String(90), nullable=False, primary_key=True)
quantity = Column(Integer, nullable=False)
#event.listens_for(BasketItem, 'after_insert')
#event.listens_for(BasketItem, 'after_update')
#event.listens_for(BasketItem, 'after_delete')
def basket_item_change(mapper, connection, target):
db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
basket = db_session.query(Basket).get(target.basket_id)
basket.updated_at = datetime.datetime.utcnow()
db_session.commit()
I think it's good to use event listener but you can do something like below. When you add a new basketitem I am sure you are query parent first and then add this child with that parent. If yes then:
basket = Basket.query.get(id)
if basket:
basket.update()
# you can perform delete or update too
item = BasketItem(basket=basket, ...)
db.session.add(item)
db.session.commit()
class Basket(db.Model):
...
def update(self):
self.updated_at = datetime.utcnow()
db.session.add(self)
db.session.commit()