How should I get acces to flask sqlalchemy with app context in Flask? [duplicate] - flask

This question already has answers here:
What is the correct way to populate fields from database or session data?
(1 answer)
How to populate wtform select field using mongokit/pymongo?
(2 answers)
Closed yesterday.
There is a file forms.py, it creates a list of tuples that contain the id and the name of the country (this is necessary to create a form with a choice countries in the select tag). The problem is that the country data is stored in a database that I access through flask_sqlalchemy. Which in turn requires app context. I'm using an app factory and I don't understand how I can get the app context. Maybe the problem is in the architecture of the project?
forms.py
def country_choices():
with current_app._get.app_context():
country_choices = [ (c.id, c.country_name) for c in\
Countries.query.with_entities(Countries.id, Countries.country_name) ]
return country_choices
class UserForm(FlaskForm):
username = StringField('username', validators=[DataRequired(message=blank_field_error), Length(min=4, max=20, message=username_lenght_error), username__is_unique, NoneOf(list(username_bad_symbols), message=username_bad_symbols_error)])
email = EmailField('email', validators=[DataRequired(message=blank_field_error), Email(message=email_error), email__is_unique])
password = PasswordField('password', validators=[DataRequired(), Length(min=8, max=255, message=password_lenght_error)])
confirm_password = PasswordField('confirm_password', validators [DataRequired(message=blank_field_error), EqualTo('password', message=password_equal_error)])
country = SelectField('country', validators=[DataRequired()], choices=country_choices() )
submit = SubmitField('submit')
init.py
from flask_uploads import configure_uploads
from flask import Flask
from .extensions import mail, db, manager, a_lot_photos
# Импорт Blueprint'ов
from app.users import users
from app.help import help
from app.views import main
def create_app():
app = Flask(__name__)
app.config.from_object('config.Config')
# Инициализация расширений
db.init_app(app)
mail.init_app(app)
manager.init_app(app)
configure_uploads(app, a_lot_photos)
from .jinja_filters import time_ago_format, time_to_end_format, format_price
app.jinja_env.filters['time_ago_format'] = time_ago_format
app.jinja_env.filters['time_to_end_format'] = time_to_end_format
app.jinja_env.filters['format_price'] = format_price
with app.app_context():
# Подключение blueprint'ов
app.register_blueprint(main, url_prefix='')
app.register_blueprint(users, url_prefix='/users')
app.register_blueprint(help, url_prefix='/help')
return app
app.py
from app import create_app
app = create_app()
app.app_context().push()
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
I tried to use current_app, but useless.
with current_app._get_current_object.app_context(): too.

Related

Blueprint error multiple dashapp into flask

I'd like to host multiple dashapp into a flak server. Each dashapp shall be accessible with a login and password.
Some users can access different dashapps.
I tried the dash_auth.BasicAuth. It works perfectly but only for one dashapp.
So I tried to authenticate with flask_httpauth. Here again, it works well for one dashboard, but not for 2 and more because of blueprints.
My flask_app.py:
import dash
from flask import Flask, render_template, redirect, Blueprint
import dash_bootstrap_components as dbc
from flask_httpauth import HTTPDigestAuth
from apps.dashboard import Dashboard
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello from Flask!'
#others routes
auth = HTTPDigestAuth()
users = {
"john": "hello",
"susan": "bye"
}
#auth.get_password
def get_pw(username):
if username in users:
return users.get(username)
return None
url1 = '/dahsboard1'
dash_app1 = dash.Dash(__name__, server = app, external_stylesheets=[dbc.themes.BOOTSTRAP])
dash_app1.config.suppress_callback_exceptions = True
dash_app1.layout = Dashboard(dash_app1, 'data1', 'Title1', url1).layout
#app.route(url1)
#app.route(url1 + '/')
#app.route('/dash1')
#auth.login_required
def render_dashboard1():
return dash_app1.index()
url2 = '/dashboard2'
dash_app2 = dash.Dash(name='app2', server = app, external_stylesheets=[dbc.themes.BOOTSTRAP])
dash_app2.config.suppress_callback_exceptions = True
dash_app2.layout = Dashboard(dash_app2, 'data2', 'Title2', url2).layout
#app.route(url2)
#app.route(url2 + '/')
#app.route('/dash2')
#auth.login_required
def render_dashboard2():
return dash_app2.index()
if __name__ == '__main__':
app.run(debug=True)
The error:
ValueError: The name '_dash_assets' is already registered for a different blueprint. Use 'name=' to provide a unique name.
I undestand that a blueprint is created at each dashapp creation. After the first call :
print(app.blueprints)
returns
{'_dash_assets': <Blueprint '_dash_assets'>}
How can I add different blueprint names for each dashapp created ? Or more generally, how can I manage authentification for several dashapps running on one flask server ?
EDTIT:
I can solve this problem using this argument at dashboard creation
url_base_pathname = '/fake-url/'
But it leads to another problem: I can't protect this route with
#app.route('/fake-url/')
#auth.login_required(role=['admin'])
def render_dashboard():
return dash_app.app.index()
So the question is: how can I protect the route used in the dash creation with the argument url_base_pathname ?
You may have already solved this by now but will leave the solution here for the community. First you will need to set url_base_pathname for example:
dash_app2 = dash.Dash(
name='app2',
server = app,
url_base_pathname='/your_url_of_choice/'
external_stylesheets=[dbc.themes.BOOTSTRAP])
This will resolve that error.

flask-admin different view for multiple databases

I have two different databases. They are: stock and commodity. Each database has two tables as follows:
Stock: User_trade_stock, stock_prices
Commodity: User_trade_commodity, commodity_prices
I try to build a web app to handle two databases with flask. When I apply flask-admin to them as follows
admin.add_view(UserView(User_trade_stock, db.session))
admin.add_view(UserView(User_trade_commodity, db.session))
I gives the following eror:
Assertion Error: A name collision occurred between blueprints. Blueprints that are created on the fly need unique name.
I tried to add the bind to the db.session as follows
admin.add_view(UserView(User_trade_stock, db.session(bind='stock_bind')))
admin.add_view(UserView(User_trade_commodity, db.session='commodity_bind')))
I got the following error:
scoped session is already present; no new arguments may be specified
Any helps would be appreciated
There are a couple of issues.
Flask-Admin uses a view's lower cased class name for the automatically generated Blueprint name. As you are using UserView twice you have a Blueprint name collision. To overcome this you can specify an endpoint name when you instance a view, for example:
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(TestView(StockTest, db.session, category='Stock', name='Test', endpoint='stock-test'))
admin.add_view(TestView(CommodityTest, db.session, category='Commodity', name='Test', endpoint='commodity-test'))
and to get the urls of the views you would use the following code:
url_for('stock-test.index')
url_for('stock-test.edit')
url_for('commodity-test.index')
url_for('commodity-test.edit')
Secondly, if you want to use the bind feature of Flask-Sqlalchemy you should use the __bind_key__ attribute on the table model, for example:
class User(db.Model):
__bind_key__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
Here is a single file example illustrating both concepts. You will need to run the flask commands flask create-databases and flask populate-databases before you use the app itself. Note I've used a mixin class, TestMixin, to define the model columns.
import click
from flask import Flask
from flask.cli import with_appcontext
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from faker import Faker
from sqlalchemy import Integer, Column, Text
db = SQLAlchemy()
app = Flask(__name__)
app.config['SECRET_KEY'] = '123456790'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_BINDS'] = {
'stock': 'sqlite:///stock.db',
'commodity': 'sqlite:///commodity.db'
}
db.init_app(app)
class TestMixin(object):
id = Column(Integer, primary_key=True)
name = Column(Text(), unique=True, nullable=False)
class StockTest(db.Model, TestMixin):
__bind_key__ = 'stock'
class CommodityTest(db.Model, TestMixin):
__bind_key__ = 'commodity'
#click.command('create-databases')
#with_appcontext
def create_databases():
db.drop_all(bind=['stock', 'commodity'])
db.create_all(bind=['stock', 'commodity'])
#click.command('populate-databases')
#with_appcontext
def populate_databases():
_faker = Faker()
db.session.bulk_insert_mappings(StockTest, [{'name': _faker.name()} for _ in range(100)])
db.session.bulk_insert_mappings(CommodityTest, [{'name': _faker.name()} for _ in range(100)])
db.session.commit()
class TestView(ModelView):
pass
app.cli.add_command(create_databases)
app.cli.add_command(populate_databases)
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(TestView(StockTest, db.session, category='Stock', name='Test', endpoint='stock-test'))
admin.add_view(TestView(CommodityTest, db.session, category='Commodity', name='Test', endpoint='commodity-test'))
#app.route('/')
def index():
return 'Click me to get to Admin!'
if __name__ == '__main__':
app.run()

Flask-Migrate - 'Please edit configuratio/connection/logging settings' on 'flask db init'

Have spent literally the past three days trying to figure this out and just can't seem to get it. Have reviewed other posts here and elsewhere regarding the same issue (see here, here, here, among others) countless times and am apparently just too dense to figure it out myself. So here we are, any help would be greatly appreciated.
Project structure:
/project-path/
/app.py
/config.py
/.flaskenv
/app/__init__.py
/app/models.py
/app/routes.py
config.py
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'bleh'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
app.py
from app import create_app
app = create_app()
.flaskenv
FLASK_APP=app.py
/app/init.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_marshmallow import Marshmallow
from config import Config
db = SQLAlchemy()
migrate = Migrate()
ma = Marshmallow()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
from app.models import Borrower
db.init_app(app)
migrate.init_app(app, db)
from app.routes import borrowers_api
app.register_blueprint(borrowers_api)
return app
/app/models.py
from app import db, ma
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
class Borrower(db.Model):
__tablename__ = 'borrowers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
borrower_name = db.Column(db.String(100), nullable=False, unique=True, index=True)
def __init__(self, borrower_name):
self.borrower_name = borrower_name
class BorrowerSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Borrower
include_relationships = True
load_instance = True #Optional: deserialize to model instances
borrower_schema = BorrowerSchema()
borrowers_schema = BorrowerSchema(many=True)
/app/routes.py
from flask import Blueprint, request, jsonify
from app.models import Borrower, borrower_schema, borrowers_schema
from app import db
borrowers_api = Blueprint('borrowers_api', __name__)
# Add a borrower
#borrowers_api.route('/borrower', methods=['POST'])
def add_borrower():
borrower_name = request.get_json(force=True)['borrower_name']
new_borrower = Borrower(borrower_name)
db.session.add(new_borrower)
db.session.commit()
return borrower_schema.jsonify(new_borrower)
# Get single borrower by id
#borrowers_api.route('/borrower/<id>', methods=['GET'])
def get_borrower(id):
borrower = Borrower.query.get(id)
return borrower_schema.jsonify(borrower)
Yet all I continue to run into the following:
(env-name) C:\Users\...\flask-scratchwork\flask-restapi-tryagain>flask db init
Creating directory C:\Users\...\flask-scratchwork\flask-restapi-tryagain\migrations ... done
Creating directory C:\Users\...\flask-scratchwork\flask-restapi-tryagain\migrations\versions ... done
Generating C:\Users\...\flask-scratchwork\flask-restapi-tryagain\migrations\alembic.ini ... done
Generating C:\Users\...\flask-scratchwork\flask-restapi-tryagain\migrations\env.py ... done
Generating C:\Users\...\flask-scratchwork\flask-restapi-tryagain\migrations\README ... done
Generating C:\Users\...\flask-scratchwork\flask-restapi-tryagain\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'C:\\Users\\...\\flask-scratchwork\\flask-restapi-tryagain\\migrations\\alembic.ini' before proceeding.
Where am I going wrong?
Nevermind, think the answer was actually just proceed with flask db migrate despite:
Please edit configuration/connection/logging settings in 'C:\\Users\\...\\flask-scratchwork\\flask-restapi-tryagain\\migrations\\alembic.ini' before proceeding.
So for anyone else spending hours trying to discern precisely what type of edits flask-migrate wants you to make to to the "configuration/connection/logging settings" in alembic.ini...the answer is seemingly to just proceed with flask db migrate

Zoho CRM Python SDK v2 initialization problem for Django

Im trying to integrate the Zoho CRM v2 SDK with my Django app.
On the Django runserver, im able to get access tokens and using the refresh method and store them in the zcrm_oauthtokens.pkl file. The sdk then automatically refreshes the access token using the refresh token, so no problem here. However on my production server (heroku) im getting this error message:
2019-01-16T11:07:22.314759+00:00 app[web.1]: 2019-01-16 11:07:22,314 - Client_Library_OAUTH - ERROR - Exception occured while fetching oauthtoken from db; Exception Message::'NoneType' object has no attribute 'accessToken'
It seems to me that the tokens are being saved to file, but when the sdk try to access them it is looking for them in a DB and not the file specified in the token_persistence_path.
In my settings.py I have this:
ZOHO_CLIENT_ID = config('ZOHO_CLIENT_ID')
ZOHO_CLIENT_SECRET = config('ZOHO_CLIENT_SECRET')
ZOHO_REDIRECT_URI = config('ZOHO_REDIRECT_URI')
ZOHO_CURRENT_USER_EMAIL = 'jamesalexander#mylastwill.co.uk'
ZOHO_PATH = os.path.join(BASE_DIR, 'wills_online', 'zoho')
zoho_config = {'apiBaseUrl': "https://www.zohoapis.com",
'currentUserEmail': ZOHO_CURRENT_USER_EMAIL,
'client_id': ZOHO_CLIENT_ID,
'client_secret': ZOHO_CLIENT_SECRET,
'redirect_uri': ZOHO_REDIRECT_URI,
'token_persistence_path': ZOHO_PATH}
and in a views file I have this:
from zcrmsdk import *
import logging
from django.shortcuts import HttpResponse
from wills.models import PersonalDetails, ZoHoRecord, WillDocument
from wills_online.decorators import start_new_thread
from wills_online.settings import zoho_config
logger = logging.getLogger(__name__)
class ZohoRunOnce:
def __init__(self):
self.already_run = False
def run_once(self):
if not self.already_run:
print('zoho init run once')
ZCRMRestClient.initialize(zoho_config)
self.already_run = True
zoho_init = ZohoRunOnce()
zoho_init.run_once()
print(zoho_config['token_persistence_path'])
def zoho_callback():
return HttpResponse(200)
#start_new_thread
def zoho_personal_details(request):
""" updates or create a user account on zoho on profile completion """
personal_details_ob = PersonalDetails.objects.get(user=request.user)
zoho_ob = ZoHoRecord.objects.get(user=request.user)
try:
if zoho_ob.account:
record = ZCRMRecord.get_instance('Accounts', zoho_ob.account)
record.set_field_value('Account_Name', request.user.email)
record.set_field_value('Name', personal_details_ob.full_name)
record.set_field_value('Email', request.user.email)
record.set_field_value('Address_Line_1', personal_details_ob.address_line_1)
record.set_field_value('Address_Line_2', personal_details_ob.address_line_2)
record.set_field_value('Post_Town', personal_details_ob.post_town)
record.set_field_value('Post_Code', personal_details_ob.post_code)
record.set_field_value('Dob_Day', personal_details_ob.dob_day)
record.set_field_value('Dob_Month', personal_details_ob.dob_month)
record.set_field_value('Dob_Year', personal_details_ob.dob_year)
record.set_field_value('Gender', personal_details_ob.sex)
record.set_field_value('Marital_Status', personal_details_ob.marital_status)
record.set_field_value('Partner_Name', personal_details_ob.partner_full_name)
record.set_field_value('Partner_Gender', personal_details_ob.partner_gender)
record.set_field_value('Partner_Email', personal_details_ob.partner_email)
record.set_field_value('Children', personal_details_ob.children)
record.set_field_value('Pets', personal_details_ob.pets)
record.update()
else:
user = ZCRMUser.get_instance(name='James Alexander')
record = ZCRMRecord.get_instance('Accounts')
record.set_field_value('Account_Owner', user)
record.set_field_value('Account_Name', request.user.email)
record.set_field_value('Name', personal_details_ob.full_name)
record.set_field_value('Email', request.user.email)
record.set_field_value('Address_Line_1', personal_details_ob.address_line_1)
record.set_field_value('Address_Line_2', personal_details_ob.address_line_2)
record.set_field_value('Post_Town', personal_details_ob.post_town)
record.set_field_value('Post_Code', personal_details_ob.post_code)
record.set_field_value('Dob_Day', personal_details_ob.dob_day)
record.set_field_value('Dob_Month', personal_details_ob.dob_month)
record.set_field_value('Dob_Year', personal_details_ob.dob_year)
record.set_field_value('Gender', personal_details_ob.sex)
record.set_field_value('Marital_Status', personal_details_ob.marital_status)
record.set_field_value('Partner_Name', personal_details_ob.partner_full_name)
record.set_field_value('Partner_Gender', personal_details_ob.partner_gender)
record.set_field_value('Partner_Email', personal_details_ob.partner_email)
record.set_field_value('Children', personal_details_ob.children)
record.set_field_value('Pets', personal_details_ob.pets)
response = record.create()
# save account id to db for future updates
zoho_ob.account = response.details['id']
zoho_ob.save()
except ZCRMException as ex:
logger.log(1, ex.status_code)
logger.log(1, ex.error_message)
logger.log(1, ex.error_details)
logger.log(1, ex.error_content)
print(ex.status_code)
print(ex.error_message)
print(ex.error_content)
print(ex.error_details)
Ive tried running ZCRMRestClient.initialize(zoho_config) in settings.py, with no luck.
My method for getting the access token and refresh token, which seems to work is:
import os
import pprint
from sys import argv
import django
import requests
import zcrmsdk
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wills_online.settings')
django.setup()
def zoho_refresh_token(code):
""" supply a self client token from the zoho api credentials from web site """
zoho_config = {"apiBaseUrl": "https://www.zohoapis.com",
"currentUserEmail": settings.ZOHO_CURRENT_USER_EMAIL,
"client_id": settings.ZOHO_CLIENT_ID,
"client_secret": settings.ZOHO_CLIENT_SECRET,
"redirect_uri": settings.ZOHO_REDIRECT_URI,
"token_persistence_path": settings.ZOHO_PATH}
pprint.pprint(zoho_config)
print('working')
address = f'https://accounts.zoho.com/oauth/v2/token?code={code}&redirect_uri={settings.ZOHO_REDIRECT_URI}&client_id={settings.ZOHO_CLIENT_ID}&client_secret={settings.ZOHO_CLIENT_SECRET}&grant_type=authorization_code'
response = requests.post(address)
data = response.json()
pprint.pprint(data)
zcrmsdk.ZCRMRestClient.initialize(zoho_config)
oauth_client = zcrmsdk.ZohoOAuth.get_client_instance()
refresh_token = data['refresh_token']
print(type(refresh_token))
oauth_client.generate_access_token_from_refresh_token(refresh_token, settings.ZOHO_CURRENT_USER_EMAIL)
print(refresh_token)
print('finished')
if name == 'main':
zoho_refresh_token(argv[1])
This is driving me mad. Help would be greatly appreciated. This is my first post so go easy, lol.
For future reference, you will need to define persistence_handler_class and persistence_handler_path in your configuration dictionary. You will also need a handler class and a user-defined model to store the results. Sample code follows:
# settings.py
import zcrmsdk
configuration_dictionary = {
'apiBaseUrl': 'https://www.zohoapis.com',
'apiVersion': 'v2',
'currentUserEmail': ZOHO_CURRENT_USER_EMAIL,
'sandbox': 'False',
'applicationLogFilePath': '',
'client_id': ZOHO_CLIENT_ID,
'client_secret': ZOHO_CLIENT_SECRET,
'redirect_uri': ZOHO_REDIRECT_URI,
'accounts_url': 'https://accounts.zoho.com',
'access_type': 'online',
'persistence_handler_class': ZOHO_HANDLER_CLASS,
'persistence_handler_path': ZOHO_HANDLER_PATH,
}
zcrmsdk.ZCRMRestClient.initialize(configuration_dictionary)
# zoho.models.py
from django.db import models
from zcrmsdk.OAuthClient import ZohoOAuthTokens
class ZohoOAuthHandler:
#staticmethod
def get_oauthtokens(email_address):
oauth_model_instance = ZohoOAuth.objects.get(user_email=email_address)
return ZohoOAuthTokens(oauth_model_instance.refresh_token,
oauth_model_instance.access_token,
oauth_model_instance.expiry_time,
user_email=oauth_model_instance.user_email)
#staticmethod
def save_oauthtokens(oauth_token):
defaults = {
'refresh_token': oauth_token.refreshToken,
'access_token': oauth_token.accessToken,
'expiry_time': oauth_token.expiryTime,
}
ZohoOAuth.objects.update_or_create(user_email=oauth_token.userEmail, defaults=defaults)
class ZohoOAuth(models.Model):
refresh_token = models.CharField(max_length=250)
access_token = models.CharField(max_length=250)
expiry_time = models.BigIntegerField()
user_email = models.EmailField()
In this example ZOHO_HANDLER_CLASS = 'ZohoOAuthHandler' and ZOHO_HANDLER_PATH = 'zoho.models'
The first time you go to use this you will need a grant_token from https://accounts.zoho.com/developerconsole. For the scope use aaaserver.profile.READ,ZohoCRM.modules.ALL to start (see https://www.zoho.com/crm/developer/docs/api/oauth-overview.html#scopes)
Before you can use the api you'll need to run the code below in a django shell. This uses a grant token to generate your initial access and refresh tokens. Afterwards, the api should handle refreshing your access token.
grant_token = GRANT_TOKEN
import zcrmsdk
oauth_client = zcrmsdk.ZohoOAuth.get_client_instance()
oauth_tokens = oauth_client.generate_access_token(grant_token)

Why does Flask-Security Cause a new KVSession Record for Each Request?

I'm trying out using Flask-KVSession as an alternative session implementation for a Flask web site. I've created a test website (see Code 1 below). When I run this, I can use the browser to store values into the session by navigating between the various resources in my web browser. This works correctly. Also, when I look at the sessions table in the resulting SQLite database, I see a single record that was being used to store this session the entire time.
Then I try to add Flask-Security to this (see Code 2 below). After running this site (making sure to first delete the existing test.db sqlite file), I am brought to the login prompt and I log in. Then I proceed to do the same thing of jumping back and forth between the resources. I get the same results.
The problem is that when I look in the sqlitebrowser sessions table, there are 8 records. It turns out a new session record was created for EACH request that was made.
Why does a new session record get created for each request when using Flask-Security? Why isn't the existing session updated like it was before?
Code 1 (KVSession without Flask-Security)
import os
from flask import Flask, session
app = Flask(__name__)
app.secret_key = os.urandom(64)
#############
# SQLAlchemy
#############
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
DB_DIR = os.path.dirname(os.path.abspath(__file__))
DB_URI = 'sqlite:////{0}/test.db'.format(DB_DIR)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
#app.before_first_request
def create_user():
db.create_all()
############
# KVSession
############
from simplekv.db.sql import SQLAlchemyStore
from flask.ext.kvsession import KVSessionExtension
store = SQLAlchemyStore(db.engine, db.metadata, 'sessions')
kvsession = KVSessionExtension(store, app)
#app.route('/a')
def a():
session['last'] = 'b'
return 'Thank you for visiting A!'
#app.route('/b')
def b():
session['last'] = 'b'
return 'Thank you for visiting B!'
#app.route('/c')
def c():
return 'You last visited "{0}"'.format(session['last'])
app.run(debug=True)
Code 2 (KVSession WITH Flask-Security)
import os
from flask import Flask, session
app = Flask(__name__)
app.secret_key = os.urandom(64)
#############
# SQLAlchemy
#############
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
DB_DIR = os.path.dirname(os.path.abspath(__file__))
DB_URI = 'sqlite:////{0}/test.db'.format(DB_DIR)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
###########
# Security
###########
# This import needs to happen after SQLAlchemy db is created above
from flask.ext.security import (
Security, SQLAlchemyUserDatastore, current_user,
UserMixin, RoleMixin, login_required
)
# Define models
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
#app.before_first_request
def create_user():
db.create_all()
user_datastore.create_user(email='test#example.com', password='password')
db.session.commit()
############
# KVSession
############
from simplekv.db.sql import SQLAlchemyStore
from flask.ext.kvsession import KVSessionExtension
store = SQLAlchemyStore(db.engine, db.metadata, 'sessions')
kvsession = KVSessionExtension(store, app)
#app.route('/a')
#login_required
def a():
session['last'] = 'b'
return 'Thank you for visiting A!'
#app.route('/b')
#login_required
def b():
session['last'] = 'b'
return 'Thank you for visiting B!'
#app.route('/c')
#login_required
def c():
return 'You last visited "{0}"'.format(session['last'])
app.run(debug=True)
Version Info
Python 2.7.3
Flask==0.9
Flask==0.9
Flask-KVSession==0.3.2
Flask-Login==0.1.3
Flask-Mail==0.8.2
Flask-Principal==0.3.5
Flask-SQLAlchemy==0.16
Flask-Security==1.6.3
SQLAlchemy==0.8.1
Turns out this is related to a known problem with flask-login (which flask-security uses) when flask-login is used with a session storage library like KVSession.
Basically, KVSession needs to update the database with the new session information whenever data in the session is created or modified. And in the sample above, this happens correctly: the first time I hit a page, the session is created. After that, the existing session is updated.
However, in the background the browser sends a cookie-less request to my web server looking for my favicon. Therefore, flask is handling a request to /favicon.ico. This request (or any other request that would 404) is still handled by flask. This means that flask-login will look at the request and try to do its magic.
It so happens that flask-login doesn't TRY to put anything into the session, but it still LOOKS like the session has been modified as far as KVSession is concerned. Because it LOOKS like the session is modified, KVSession updates the database. The following is code from flask-login:
def _update_remember_cookie(self, response):
operation = session.pop("remember", None)
...
The _update_remember_cookie method is called during the request lifecycle. Although session.pop will not change the session if the session doesn't have the "remember" key (which in this case it doesn't), KVSession still sees a pop and assumes that the session changes.
The issue for flask-login provides the simple bug fix, but it has not been pushed into flask-login. It appears that the maintainer is looking for a complete rewrite, and will implement it there.