SQLAlchemy: delete single entry from many-to-many table [duplicate] - python

I am having problems deleting records from PostTag table that contains Post and Tag column. This is my relation table:
tags = db.Table('PostTag',
db.Column('Tag', db.Integer, db.ForeignKey('Tag.Id')),
db.Column('Post', db.Integer, db.ForeignKey('Post.Id'))
)
and
tags = db.relationship(Tag, secondary=tags, backref=db.backref('Post', lazy='dynamic'))
When I do this:
from models.Post import Post
posts = Post.query.join(Post.tags).filter(Post.Id == id).all()
if(posts):
return posts
return False
and then
for posttag in post[0].tags:
db.session.delete(posttag)
db.session.flush()
Rows from many-to-many relation are deleted but also records from Tag table.
I just need to delete records from PostTag table for some condition (Post=1 for example)
I searched the internet but I did not find anything conclusive. I do not need cascade on many-to-many relationship.
This is sql log:
297 Query DELETE FROM `PostTag` WHERE `PostTag`.`Tag` = 14 AND `PostTag`.`Post` = 3
297 Query DELETE FROM `PostTag` WHERE `PostTag`.`Tag` = 14 AND `PostTag`.`Post` = 18
297 Query DELETE FROM `Tag` WHERE `Tag`.`Id` = 14
Last row 297 Query DELETE FROMTagWHERETag.Id= 14 should not be there.
UPDATE / maybe solution
I kind of solved this with:
sql = 'DELETE FROM PostTag WHERE Post=%s'
db.engine.execute(sql, post, ())
But that is not just ORM way. Same goes for insert. I will try to get this resolved ORM way. I will post answer if I make this problem go away.

Try this:
post = db.session.query(Post).get(1)
post.tags = []
db.session.commit()
Here we are redefining the collection post.tags to the empty array and committing the changes. To explain this I'll refer to the SQLAlchemy docs:
Collections in SQLAlchemy are transparently instrumented. Instrumentation means that normal operations on the collection are tracked and result in changes being written to the database at flush time.
So SQLAlchemy keeps track of the changes we make to the collection post.tags and updates it on commit.
If we had only one tag (say sometag) we could use the remove method like this:
post = db.session.query(Post).get(1)
post.tags.remove(sometag)
db.session.commit()

Try to remove objects from the collection instead:
post[0].tags.clear()
You could also remove individual items:
post[0].tags.remove(posttag)

Related

How to populate a dropdown box with a database query instead of a list in flask

this could be a very easy question but as I novice I have to ask here sorry as I have not found the answer so far after a lot of playing with it.
I'm using flask with a python list of food types to allow users to input a food item into a form and have this verified with validators to make sure the item is in the list. The inputted food items from the form then gets stored in a db table.
I wish to replace this list approach with a sql query from a db of pre defined food types, whilst using the SQLAlchemy as this is used elsewhere in the app.
A simplified version of the code is as follows: -
#db connection is already setup and working OK in another part of the app
DB_URL = 'postgresql+psycopg2://{user}:{pw}#{url}:{port}/{db}'.format(user=POSTGRES_USER, pw=POSTGRES_PW, url=POSTGRES_URL, port=POSTGRES_PORT, db=POSTGRES_DB)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
db = SQLAlchemy(app)
#example of the list
eaten = ['bread', 'crackers', 'ham', 'bacon']
#where the 'food_word' variable gets defined from an input and validated
food_word_1 = StringField('add the food type you have eaten here:', validators=[Optional(), AnyOf(eaten)])
I've tried replacing the list with eaten = db.execute('SELECT food_name FROM food_type') (table & column) with no luck.
I'm not sure if I need to create some kind of class/methods in the model.py for this Select/GET operation or even use something like pandas (which I also have in the app) to do this job.
Any guidance appreciated!
thanks, md
SQLAlchemy is an Object Relational Mapper. It helps to interact with the database without SQL query. It is an abstraction to the database. So you should not write SQL query here. Rather you have to create an inherited class from db.Model. Like below.
class FoodType(db.Model):
id = db.Column(db.Integer, primary_key=True)
food_name = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return '<User %r>' % self.food_name
Then for fetching data, you have to call the query function,
db.query.all()
# or
db.query.filter_by()
The result will be a single list.
If you using postgres directly, without SQLAlchemy, then SQL query will be like,
>>> conn = psycopg2.connect(DATABASE_URL)
>>> cur = conn.cursor()
>>> cur.execute('SELECT food_name FROM food_type')
>>> cur.fetchall()
[['bread'], ['crackers'], ['ham'], ['bacon']]
If you want to convert as a single list,
eaten = [i[0] for i in db.fetchall()]

SQLAlchemy left join using subquery

Lets say there's a table "posts" which contains blog posts, and another table "favorites" which links a username to a post. Many users can favorite a post, so the relationship is one post to many favorites.
I am trying to figure out the syntax to join posts to favorites, but I only want those favorites where the user is the current user.
I want some thing like:
current_user = 'testuser'
posts.query.outerjoin(favorites, and_(posts.post_id == favorites.post_id, favorites.username == current_user)).limit(10).all()
This get me really close, except it seems like the "favorites.username == current_user" condition is basically getting ignored. This is what I am looking for in actual SQL:
SELECT *
FROM posts p
LEFT JOIN (
SELECT * FROM favorites f WHERE f.user_id = 'testuser'
) ff ON ff.post_id = p.post_id
LIMIT 10
It's also worth mentioning that I have defined the relationship on posts like:
favorites = db.relationship("favorites")
And I have defined the foreign key on favorites like this:
post_id = db.Column(db.String(255), db.ForeignKey('posts.post_id'))
How can I accomplish this in SQLAlchemy?
Really you just need to replace the outerjoin with join, and the filter would work just fine.
Also, if your favorites table contains no additional information and only links users and posts, you should consider simply defining a `Many to Many' relationship. In the documentation examples Parent/Child would be your User/Post.
Update-1: just to answer second part of the question given your comment, the query below should give you an idea:
current_user = 2
subq = (db.session.query(favorites)
.filter(favorites.user_id == current_user).subquery('ff'))
q = (db.session.query(posts, subq.c.score)
.outerjoin(subq, subq.c.post_id == posts.post_id))
q = q.order_by(subq.c.score.desc())
for post, score in q:
print(post, score)

How to delete records from many-to-many (secondary) table in SQLAlchemy?

I am having problems deleting records from PostTag table that contains Post and Tag column. This is my relation table:
tags = db.Table('PostTag',
db.Column('Tag', db.Integer, db.ForeignKey('Tag.Id')),
db.Column('Post', db.Integer, db.ForeignKey('Post.Id'))
)
and
tags = db.relationship(Tag, secondary=tags, backref=db.backref('Post', lazy='dynamic'))
When I do this:
from models.Post import Post
posts = Post.query.join(Post.tags).filter(Post.Id == id).all()
if(posts):
return posts
return False
and then
for posttag in post[0].tags:
db.session.delete(posttag)
db.session.flush()
Rows from many-to-many relation are deleted but also records from Tag table.
I just need to delete records from PostTag table for some condition (Post=1 for example)
I searched the internet but I did not find anything conclusive. I do not need cascade on many-to-many relationship.
This is sql log:
297 Query DELETE FROM `PostTag` WHERE `PostTag`.`Tag` = 14 AND `PostTag`.`Post` = 3
297 Query DELETE FROM `PostTag` WHERE `PostTag`.`Tag` = 14 AND `PostTag`.`Post` = 18
297 Query DELETE FROM `Tag` WHERE `Tag`.`Id` = 14
Last row 297 Query DELETE FROMTagWHERETag.Id= 14 should not be there.
UPDATE / maybe solution
I kind of solved this with:
sql = 'DELETE FROM PostTag WHERE Post=%s'
db.engine.execute(sql, post, ())
But that is not just ORM way. Same goes for insert. I will try to get this resolved ORM way. I will post answer if I make this problem go away.
Try this:
post = db.session.query(Post).get(1)
post.tags = []
db.session.commit()
Here we are redefining the collection post.tags to the empty array and committing the changes. To explain this I'll refer to the SQLAlchemy docs:
Collections in SQLAlchemy are transparently instrumented. Instrumentation means that normal operations on the collection are tracked and result in changes being written to the database at flush time.
So SQLAlchemy keeps track of the changes we make to the collection post.tags and updates it on commit.
If we had only one tag (say sometag) we could use the remove method like this:
post = db.session.query(Post).get(1)
post.tags.remove(sometag)
db.session.commit()
Try to remove objects from the collection instead:
post[0].tags.clear()
You could also remove individual items:
post[0].tags.remove(posttag)

SQLAlchemy order by hybrid property that references relationship

My SQLAlchemy models:
class Cover(db.Model):
# ... a bunch of other fields ...
#hybrid_property
def number_of_requests(self):
if self.requests:
return len(self.requests)
return 0
#number_of_requests.expression
def number_of_requests(cls):
return func.count(cls.requests)
class Request(db.Model):
# ... a bunch of other fields ...
# Cover that this request is requesting
cover_id = db.Column(db.Integer, db.ForeignKey('cover.id')
cover = db.relationship('Cover',
backref=backref("requests", cascade="all, delete-orphan"))
So, a simple one-to-many relationship between Cover and Request. The number_of_requests hybrid property should return the number of Requests associated with that particular Cover.
Now, in one of my Flask routes, I'm trying to grab the top 5 Covers by number of Requests. Here's what that looks like now:
# Get top cover requests
covers = Cover.query.order_by(Cover.number_of_requests).limit(5).all()
Unfortunately, this gives
ProgrammingError: (ProgrammingError) missing FROM-clause entry for table "request"
I suspect this is because in number_of_requests(cls) I'm trying to count the size of the requests list but SQLAlchemy hasn't included the request table in the original query. Any ideas on how to do that to avoid getting this error?
Change your expression part to:
#number_of_requests.expression
def number_of_requests(cls):
return (select([func.count(Request.id)])
.where(Request.cover_id == cls.id))
and read Correlated Subquery Relationship Hybrid again.

How to query database by id using SqlAlchemy?

I need to query a SQLAlchemy database by its id something similar to
User.query.filter_by(username='peter')
but for id. How do I do this? [Searching over Google and SO didn't help]
Query has a get function that supports querying by the primary key of the table, which I assume that id is.
For example, to query for an object with ID of 23:
User.query.get(23)
Note: As a few other commenters and answers have mentioned, this is not simply shorthand for "Perform a query filtering on the primary key". Depending on the state of the SQLAlchemy session, running this code may query the database and return a new instance, or it may return an instance of an object queried earlier in your code without actually querying the database. If you have not already done so, consider reading the documentation on the SQLAlchemy Session to understand the ramifications.
You can query a User with id = 1 like this
session.query(User).get(1)
get() is not as your expected sometimes. If your transaction was done:
>>> session.query(User).get(1)
[SQL]: BEGIN (implicit)
[SQL]: SELECT user.id AS user_id, user.name AS user_name, user.fullname AS user_fullname
FROM user
WHERE user.id = ?
[SQL]: (1,)
<User(u'ed', u'Ed Jones')>
If you are in a transaction, get() will give you the result object in memory without query the database:
>>> session.query(User).get(1)
<User(u'ed', u'Ed Jones')>
better to use this:
>>> session.query(User.name).filter(User.id == 1).first()
[SQL]: SELECT user.name AS user_name
FROM user
WHERE user.id = ?
LIMIT ? OFFSET ?
[SQL]: (1, 1, 0)
(u'Edwardo',)
If you use tables reflection you might have problems with the solutions given.
(The previous solutions here didn't work for me).
What I ended up using was:
session.query(object.__class__).get(id)
(object was retrieved by reflection from the database, this is why you need to use .__class__)
I hope this helps.
First, you should set id as the primary key.
Then you could use the query.get() method to query objects by id which is already the primary key.
Since the query.get() method to query objects by the primary key.
Inferred from Flask-SQLAlchemy documentation
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy()
db.init_app(app)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
def test():
id = 1
user = User.query.get(id)

Categories

Resources