In my flask app, I store an instance of a user object in the session
session["user"] = user #Where user is an object
I found out that when updating the object stored in the session directly and commit, the changes are not reflected in the database.
However, I found that when updating the database object directly, the changes are reflected in the database while the session object is unchanged.
If I try a method like
user.username = username
user.password = password
db.commit() #I called my database session 'db', probably not a good idea but not the point
session["user"] = user
it throws:
sqlalchemy.orm.exc.DetachedInstanceError: Instance is not bound to a Session; attribute refresh operation cannot proceed
What could be a possible solution to update the database correctly and reflect the changes to the flask session? Thanks in advance.
Try setting {'expire_on_commit': False} like,
db = SQLAlchemy(app, session_options={
'expire_on_commit': False
})
Related
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
what is the use of load_user(user_id) method ? and when will it called ? In documentation it is mentioned that it is used to reload the user object from the user ID stored in the session . I don't understand from where session gets the user_id and where the method reloads the user object.And also why it needs to write in models.py file ?
During the logging we pass the user object in login_user() method so does this method add the user object in session and sets this user to current_user ?
When a logined user returns to your website, their browser will send cookie related to your website to the server. Like other modern web framework, flask will not store the user's credential inside the cookie, but store a session ID instead. Then flask will use this session ID to lookup the database and find the correct user information and send it back to the browser. So here is the load_userfunction kicks in. load_user is a callback function used by the flask-login login manager. When flask needs to look up and load the user related to a specific session ID, it will call this function. This is also why it is in the model.py since it is directly related to the database.
I want to override setup_db method for my add-on because in current situation you can't take argument from url with db name, and if the user has more than 1 database I can`t run my login link from incognito.
I don't want the user to go to /web/database/selector first.
I was thinking about the user going to /web/login?db=example_db_name and then somehow redirect to my login link.
("somehow" because if you type it, it redirects you to /web/login, so i cant add redirect from login page).
I'm doing that assuming that in odoo.conf user has
db_name = False, dbfilter = .
If you faced the same problem, here is my solution. It`s overriding the default method which is kinda bad usually, but in our situation there isnt much we can do.
from odoo import http
class Rooting(http.Root):
def setup_db(self, httprequest):
db = httprequest.session.db
# Check if session.db is legit
if db:
if db not in http.db_filter([db], httprequest=httprequest):
httprequest.session.logout()
db = None
if not db:
if 'db' in httprequest.args:
db = httprequest.args['db']
httprequest.session.db = db
if not db:
httprequest.session.db = http.db_monodb(httprequest)
http.Root.setup_db = Rooting.setup_db
I want to have a Flask route that deletes all instances of a SQLAlchemy model, VisitLog. I call VisitLog.query.delete(), then redirect back to the page, but the old entries are still present. There was no error. Why weren't they deleted?
#app.route('/log')
def log():
final_list = VisitLog.query.all()
return render_template('log.html', loging=final_list)
#app.route('/logclear')
def logclear():
VisitLog.query.delete()
return redirect("log.html", code=302)
Clear database
Just like other write operations, you must commit the session after executing a bulk delete.
VisitLog.query.delete()
db.session.commit()
I wonder how SQLAlchemy tracks changes that are made outside of SQLAlchemy (manual change for example)?
Until now, I used to put db.session.commit() before each value that can be changed outside of SQLAlchemy. Is this a bad practice? If yes, is there a better way to make sure I'll have the latest value? I've actually created a small script below to check that and apparently, SQLAlchemy can detect external changes without db.session.commit() being called each time.
Thanks,
P.S: I really want to understand how all the magics happen behind SQLAlchemy work. Does anyone has a pointer to some docs explaining the behind-the-scenes work of SQLAlchemy?
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# Use SQLlite so this example can be run anywhere.
# On Mysql, the same behaviour is observed
basedir = os.path.abspath(os.path.dirname(__file__))
db_path = os.path.join(basedir, "app.db")
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + db_path
db = SQLAlchemy(app)
# A small class to use in the test
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
# Create all the tables and a fake data
db.create_all()
user = User(name="old name")
db.session.add(user)
db.session.commit()
#app.route('/')
def index():
"""The scenario: the first request returns "old name" as expected.
Then, I modify the name of User:1 to "new name" directly on the database.
On the next request, "new name" will be returned.
My question is: how SQLAlchemy knows that the value has been changed?
"""
# Before, I always use db.session.commit()
# to make sure that the latest value is fetched.
# Without db.session.commit(),
# SQLAlchemy still can track change made on User.name
# print "refresh db"
# db.session.commit()
u = User.query.filter_by(id=1).first()
return u.name
app.run(debug=True)
The "cache" of a session is a dict in its identity_map (session.identity_map.dict) that only caches objects for the time of "a single business transaction" , as answered here https://stackoverflow.com/a/5869795.
For different server requests, you have different identity_map. It is not a shared object.
In your scenario, you requested the server 2 separated times. The second time, the identity_map is a new one (you can easily check it by printing out its pointer), and has nothing in cache. Consequently the session will request the database and get you the updated answer. It does not "track change" as you might think.
So, to your question, you don't need to do session.commit() before a query if you have not done a query for the same object in the same server request.
Hope it helps.
Im new to sqlalchemy, Im query object and expunge of session for prevent the session save change, after add the object with change at session but not flush or commit.
when the ends controller, the session save my object, i dont want that, I want the object is lost if I did not flush or commit
my code:
object = model.DBSession.query(model.Object).filter_by( field = value ).first()
model.DBSession.expunge(object)
object.field = gfhggghfg
object.field2 = hsjsjsjsjs
model.DBSession.add(object)
#finish controller turbogearsr the session save the change. I have autocommit and autoflush = False
You don't even need to expunge the object, as TurboGears provides a transaction manager you can just doom the transaction so that TurboGears will throw it away instead of committing the changes:
import transaction
#expose('mytemplate')
def my_controller(self):
object = model.DBSession.query(model.Object).filter_by( field = value ).first()
object.field = 'Hello'
transaction.doom() # This will prevent changes to be committed.
return dict(value=object.field)
To disable it for the whole project edit config/app_cfg.py and add:
base_config.use_transaction_manager = False