Version Migration

Migrating to 5.0.0

Flask-AppBuilder 5.0.0 introduces major breaking changes to modernize the framework and improve maintainability. This version removes deprecated features and updates dependencies for better compatibility with modern Flask ecosystem.

Major Changes Summary:

  1. Removed MongoDB/MongoEngine Support - Complete removal of deprecated MongoDB backend

  2. Removed OpenID 2.0 Support - Deprecated authentication method removed (OAuth 2.0 preserved)

  3. Removed RestCRUDView - Deprecated view class removed to simplify inheritance

  4. Updated SQLAlchemy Support - Added SQLAlchemy 2.x and Flask-SQLAlchemy 3.x compatibility

  5. CLI Command Changes - Updated command-line interface

Breaking Changes and Migration Guide

1. MongoDB/MongoEngine Removal

MongoDB support has been completely removed. If you are using MongoDB:

Before (v4.x):

from flask_appbuilder.security.mongoengine import MongoEngineSecurityManager
from flask_appbuilder.models.mongoengine import ModelItem

# In config.py
SQLALCHEMY_DATABASE_URI = 'mongodb://localhost:27017/mydb'

# Security manager
appbuilder = AppBuilder(app, db.session, security_manager_class=MongoEngineSecurityManager)

After (v5.x):

from flask_appbuilder.security.sqla import SecurityManager
from flask_appbuilder.models.sqla import Model

# In config.py
SQLALCHEMY_DATABASE_URI = 'postgresql://user:pass@localhost/mydb'  # Use SQL database

# Security manager (default)
appbuilder = AppBuilder(app, db.session)

Migration Steps: 1. Export your MongoDB data to SQL format 2. Set up PostgreSQL, MySQL, or SQLite database 3. Convert MongoEngine models to SQLAlchemy models 4. Update imports to use SQLAlchemy interfaces 5. Remove MongoDB dependencies from requirements.txt

2. OpenID 2.0 Authentication Removal

OpenID 2.0 support has been removed. Migrate to OAuth 2.0, LDAP, or database authentication.

Before (v4.x):

# In config.py
AUTH_TYPE = AUTH_OID
OPENID_PROVIDERS = [
    {'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id'},
    {'name': 'Yahoo', 'url': 'https://me.yahoo.com'},
]

After (v5.x):

# In config.py - Use OAuth 2.0 instead
AUTH_TYPE = AUTH_OAUTH
OAUTH_PROVIDERS = [
    {
        'name': 'google',
        'token_key': 'access_token',
        'icon': 'fa-google',
        'remote_app': {
            'client_id': 'GOOGLE_CLIENT_ID',
            'client_secret': 'GOOGLE_CLIENT_SECRET',
            'api_base_url': 'https://www.googleapis.com/oauth2/v2/',
            'client_kwargs': {'scope': 'email profile'},
            'server_metadata_url': 'https://accounts.google.com/.well-known/openid_configuration'
        }
    }
]

Migration Steps: 1. Remove AUTH_OID and OPENID_PROVIDERS from config 2. Set up OAuth 2.0 providers with proper client credentials 3. Update authentication type to AUTH_OAUTH, AUTH_LDAP, or AUTH_DB 4. Test authentication flow with new providers

3. RestCRUDView Removal

The deprecated RestCRUDView class has been removed to simplify inheritance hierarchy.

Before (v4.x):

from flask_appbuilder import RestCRUDView

class MyView(RestCRUDView):
    datamodel = SQLAInterface(MyModel, db.session)

After (v5.x):

from flask_appbuilder import ModelView

class MyView(ModelView):
    datamodel = SQLAInterface(MyModel, db.session)

Migration Steps: 1. Replace RestCRUDView imports with ModelView 2. Update class inheritance from RestCRUDView to ModelView 3. Remove any usage of deprecated REST API methods

4. SQLAlchemy 2.x and Flask-SQLAlchemy 3.x Compatibility

Flask-AppBuilder now supports both SQLAlchemy 1.4+ and 2.x with Flask-SQLAlchemy 2.x and 3.x.

Key Changes:

  • Query Syntax Updates: Some query patterns may need updates for SQLAlchemy 2.x

  • Session Handling: Improved session management compatibility

  • Relationship Loading: Updated lazy loading syntax support

Before (SQLAlchemy 1.x patterns):

# Old query patterns that may need updates
users = session.query(User).filter_by(active=True).all()
result = session.execute("SELECT * FROM users WHERE active = 1")

After (SQLAlchemy 2.x compatible):

# Modern patterns (works with both 1.4+ and 2.x)
from sqlalchemy import select, text

users = session.scalars(select(User).where(User.active == True)).all()
result = session.execute(text("SELECT * FROM users WHERE active = :active"), {"active": 1})

Migration Steps: 1. Update SQLAlchemy to 1.4+ or 2.x in your requirements 2. Update Flask-SQLAlchemy to 2.x or 3.x 3. Test your application thoroughly 4. Update any custom query patterns if needed

5. CLI Command Changes

The fab create-app command has been simplified.

Before (v4.x):

fab create-app myapp --engine SQLAlchemy

After (v5.x):

fab create-app myapp

Migration Steps: 1. Remove --engine parameter from scripts using fab create-app 2. SQLAlchemy is now the only supported database engine

6. Import Path Changes

Some imports have been removed or changed:

Removed Imports:

# These imports will fail in v5.x
from flask_appbuilder.const import AUTH_OID  # Removed
from flask_appbuilder.security.mongoengine import MongoEngineSecurityManager  # Removed
from flask_appbuilder.models.mongoengine import ModelItem  # Removed
from flask_appbuilder import RestCRUDView  # Removed

Updated Imports:

# Use these instead
from flask_appbuilder.const import AUTH_OAUTH, AUTH_DB, AUTH_LDAP
from flask_appbuilder.security.sqla import SecurityManager
from flask_appbuilder.models.sqla import Model
from flask_appbuilder import ModelView

7. Security Manager Session Access Changes

The method to access the security manager’s database session has changed.

Before (v4.x):

# Old session access method
session = appbuilder.sm.get_session

After (v5.x):

# New session access method
session = appbuilder.sm.session

Migration Steps: 1. Replace appbuilder.sm.get_session with appbuilder.sm.session 2. Update any code that accesses the security manager’s database session

8. Application Context Required for Database Queries

Database queries through the security manager now require an application context.

Before (v4.x):

# Worked outside application context
users = appbuilder.sm.get_all_users()

After (v5.x):

# Requires application context
with app.app_context():
    users = appbuilder.sm.get_all_users()

Migration Steps: 1. Wrap database queries in with app.app_context(): blocks 2. Ensure application context is available when accessing database through security manager 3. Test all database operations in your application

9. SQLAInterface Exception Handling Changes

The SQLAInterface class no longer automatically swallows exceptions and includes a new commit parameter.

Before (v4.x):

# Exceptions were automatically handled
interface = SQLAInterface(MyModel)
interface.add(item)  # Automatic commit

After (v5.x):

# Exceptions are now propagated, commit parameter available
interface = SQLAInterface(MyModel)
interface.add(item)  # Default: commit=True

# Or with manual commit control
interface.add(item, commit=False)
# Must call commit manually later

Migration Steps: 1. Add proper exception handling around SQLAInterface operations 2. Use commit=False parameter if you need manual transaction control 3. Test error handling in your data access code

10. Application Reference Changes

The appbuilder.get_app method has been removed.

Before (v4.x):

# Removed method
app = appbuilder.get_app

After (v5.x):

# Use Flask's current_app (recommended)
from flask import current_app
app = current_app

# Or use direct reference (deprecated)
app = appbuilder.app

Migration Steps: 1. Replace appbuilder.get_app calls with from flask import current_app 2. Use current_app instead of the removed method 3. Update imports to include current_app where needed

11. Model __tablename__ Requirement

All user models now require an explicit __tablename__ attribute.

Before (v4.x):

# tablename was optional/auto-generated
class MyModel(Model):
    id = Column(Integer, primary_key=True)

After (v5.x):

# tablename is now required
class MyModel(Model):
    __tablename__ = 'my_model'
    id = Column(Integer, primary_key=True)

Migration Steps: 1. Add __tablename__ attribute to all model classes 2. Choose appropriate table names following your naming convention 3. Ensure table names don’t conflict with existing database tables

12. New Configuration Option: FAB_CREATE_DB

A new configuration option FAB_CREATE_DB controls automatic database table creation.

New in v5.x:

# In config.py
FAB_CREATE_DB = True   # Default: automatically create tables
FAB_CREATE_DB = False  # Disable automatic table creation

Migration Steps: 1. Set FAB_CREATE_DB = False if you manage database schema manually 2. Keep default True value for automatic table creation (existing behavior) 3. Use this setting to control database initialization in different environments

13. Dependency Changes

Removed Dependencies: - flask-mongoengine - mongoengine - pymongo - flask-openid

Updated Dependencies: - SQLAlchemy 1.4+ or 2.x support - Flask-SQLAlchemy 2.x or 3.x support

Migration Steps: 1. Remove MongoDB and OpenID dependencies from requirements.txt 2. Update SQLAlchemy and Flask-SQLAlchemy versions 3. Install updated dependencies: pip install -r requirements.txt

Testing Your Migration

After completing the migration:

  1. Database Setup: Ensure your SQL database is properly configured

  2. Authentication Test: Verify login works with your chosen auth method

  3. View Testing: Test all your ModelView-based views

  4. API Testing: If using REST APIs, verify they work correctly

  5. Run Tests: Execute your test suite to catch any remaining issues

Getting Help

If you encounter issues during migration:

  1. Check the Flask-AppBuilder GitHub issues

  2. Review the Flask-AppBuilder documentation

  3. For SQLAlchemy 2.x specific issues, consult the SQLAlchemy migration guide

Migrating to 1.9.0

If you are using OAuth for authentication, this release will break your logins. This break is due to two reasons

One:

There was a security issue when using the default builtin information getter for the providers (see github: Prevent masquerade attacks through oauth providers #472) This fix will prepend the provider to the user id. So your usernames will look like ‘google_<USER_ID>’

Two:

For google OAuth we migrated from the old and deprecated google plus API to OAuth2/v2, the old User.username field was based on the Google Plus display name, and now is based on a Google user_id.

In order to upgrade without breaking, you can override the current default OAuth information getter using something like this:

@appbuilder.sm.oauth_user_info_getter
def get_oauth_user_info(sm, provider, response=None):
# for GITHUB
    if provider == 'github' or provider == 'githublocal':
        me = sm.oauth_remotes[provider].get('user')
        return {'username': me.data.get('login')}
    # for twitter
    if provider == 'twitter':
        me = sm.oauth_remotes[provider].get('account/settings.json')
        return {'username': me.data.get('screen_name', '')}
    # for linkedin
    if provider == 'linkedin':
        me = sm.oauth_remotes[provider].get('people/~:(id,email-address,first-name,last-name)?format=json')
        return {'username': me.data.get('id', ''),
                'email': me.data.get('email-address', ''),
                'first_name': me.data.get('firstName', ''),
                'last_name': me.data.get('lastName', '')}
    # for Google
    if provider == 'google':
        me = sm.oauth_remotes[provider].get('userinfo')
        return {'username': me.data.get('id', ''),
                'first_name': me.data.get('given_name', ''),
                'last_name': me.data.get('family_name', ''),
                'email': me.data.get('email', '')}

There was a Fix for the oauth_user_info_getter decorator also, now it will obey the doc definition.

Any help you need feel free to submit an Issue!

Migrating to 1.8.0

On this release flask-appbuilder supports python 3.5, and returned to flask-babel original package (stopped using the fork flask-babelpkg for multiple translation directories).

You can and should, uninstall flask-babelpkg from your package list and change all your imports from:

from flask_babelpkg import ...

To:

from flask_babel import ...

Migrating from 1.2.X to 1.3.X

There are some breaking features:

1 - Security models have changed, users can have multiple roles, not just one. So you have to upgrade your db.

  • The security models schema have changed.

    If you are using sqlite, mysql, pgsql, mssql or oracle, use the following procedure:

    1 - Backup your DB.

    2 - If you haven’t already, upgrade to flask-appbuilder 1.3.0.

    3 - Issue the following commands, on your project folder where config.py exists:

    $ cd /your-main-project-folder/
    $ fabmanager upgrade-db
    

    4 - Test and Run (if you have a run.py for development)

    $ fabmanager run
    

    For sqlite you’ll have to drop role_id columns and FK yourself. follow the script instructions to finish the upgrade.

2 - Security. If you were already extending security, this is even more encouraged from now on, but internally many things have changed. So, modules have changes and changed place, each backend engine will have it’s SecurityManager, and views are common to all of them. Change:

from:

from flask_appbuilder.security.sqla.views import UserDBModelView
from flask_appbuilder.security.manager import SecurityManager

to:

from flask_appbuilder.security.views import UserDBModelView
from flask_appbuilder.security.sqla.manager import SecurityManager

3 - SQLAInteface, SQLAModel. If you were importing like the following, change:

from:

from flask_appbuilder.models import SQLAInterface

to:

from flask_appbuilder.models.sqla.interface import SQLAInterface

4 - Filters, filters import moved:

to:

from flask_appbuilder.models.sqla.filters import FilterStartsWith, FilterEqualFunction, FilterEqual

5 - Filters, filtering relationship fields (rendered with select2) changed:

from:

edit_form_query_rel_fields = [('group',
                               SQLAModel(Model1, self.db.session),
                               [['field_string', FilterEqual, 'G2']]
                              )
                            ]

to:

edit_form_query_rel_fields = {'group':[['field_string', FilterEqual, 'G2']]}

Migrating from 1.1.X to 1.2.X

There is a breaking feature, change your filters imports like this:

from:

flask_appbuilder.models.base import Filters, BaseFilter, BaseFilterConverter
flask_appbuilder.models.filters import FilterEqual, FilterRelation ....

to:

flask_appbuilder.models.filters import Filters, BaseFilter, BaseFilterConverter
flask_appbuilder.models.sqla.filter import FilterEqual, FilterRelation ....

Migrating from 0.9.X to 0.10.X

This new version has NO breaking features, all your code will work, unless you are hacking directly onto SQLAModel, Filters, DataModel etc.

But, to keep up with the changes, you should change these:

from flask_appbuilder.models.datamodel import SQLAModel
from flask_appbuilder.models.filters import FilterEqual, FilterContains

to:

from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.models.sqla.filters import FilterEqual, FilterContains

Migrating from 0.8.X to 0.9.X

This new version has a breaking feature, the way you initialize AppBuilder (former BaseApp) has changed. internal retro compatibility was created, but many things have changed

1 - Initialization of AppBuilder (BaseApp) has changed, pass session not SQLAlchemy db object. this is the breaking feature.

from (__init__.py)

BaseApp(app, db)

to (__init__.py)

AppBuilder(app, db.session)

2 - ‘BaseApp’ changed to ‘AppBuilder’. Has you already noticed on 1.

3 - BaseApp or now AppBuilder will not automatically create your models, after declaring them just invoke create_db method:

appbuilder.create_db()

4 - Change your models inheritance

from:

class MyModel(Model):
    id = Column(Integer, primary_key=True)
    first_name = Column(String(64), nullable=False)

to:

class MyModel(Model):
    id = Column(Integer, primary_key=True)
    first_name = Column(String(64), nullable=False)

5 - Although you’re not obligated, you should not directly use your flask.ext.sqlalchemy class SQLAlchemy. Use F.A.B. SQLA class instead, read the docs to know why.

from (__init__.py):

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask_appbuilder.baseapp import BaseApp


app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
baseapp = BaseApp(app, db)

to (__init__.py):

from flask import Flask
from flask_appbuilder import SQLA, AppBuilder

app = Flask(__name__)
app.config.from_object('config')
db = SQLA(app)
appbuilder = AppBuilder(app, db.session)

Migrating from 0.6.X to 0.7.X

This new version has some breaking features. You don’t have to change any code, main breaking changes are:

  • The security models schema have changed.

    If you are using sqlite, mysql or pgsql, use the following procedure:

    1 - Backup your DB.

    2 - If you haven’t already, upgrade to flask-appbuilder 0.7.0.

    3 - Issue the following commands, on your project folder where config.py exists:

    cd /your-main-project-folder/
    wget https://raw.github.com/dpgaspar/Flask-AppBuilder/master/bin/migrate_db_0.7.py
    python migrate_db_0.7.py
    wget https://raw.github.com/dpgaspar/Flask-AppBuilder/master/bin/hash_db_password.py
    python hash_db_password.py
    

    4 - Test and Run (if you have a run.py for development)

    python run.py
    

    If not (DB is not sqlite, mysql or pgsql), you will have to alter the schema yourself. use the following procedure:

    1 - Backup your DB.

    2 - If you haven’t already, upgrade to flask-appbuilder 0.7.0.

    3 - issue the corresponding DDL commands to:

    ALTER TABLE ab_user MODIFY COLUMN password VARCHAR(256)

    ALTER TABLE ab_user ADD COLUMN login_count INTEGER

    ALTER TABLE ab_user ADD COLUMN created_on DATETIME

    ALTER TABLE ab_user ADD COLUMN changed_on DATETIME

    ALTER TABLE ab_user ADD COLUMN created_by_fk INTEGER

    ALTER TABLE ab_user ADD COLUMN changed_by_fk INTEGER

    ALTER TABLE ab_user ADD COLUMN last_login DATETIME

    ALTER TABLE ab_user ADD COLUMN fail_login_count INTEGER

    4 - Then hash your passwords:

    wget https://raw.github.com/dpgaspar/Flask-AppBuilder/master/bin/hash_db_password.py
    python hash_db_password.py
    
  • All passwords are kept on the database hashed, so all your passwords will be hashed by the framework.

  • Please backup your DB before altering the schema, if you feel lost please post an issue on github

    https://github.com/dpgaspar/Flask-AppBuilder/issues?state=open

Migrating from 0.5.X to 0.6.X

This new version has some breaking features, that I hope will be easily changeable on your code.

If you feel lost please post an issue on github: https://github.com/dpgaspar/Flask-AppBuilder/issues?state=open

If you’re using the related_views attribute on ModelView classes, you must not instantiate the related classes. This is the correct form, it will be less memory and cpu resource consuming.

From this:

class MyView(GeneralView):
    datamodel = SQLAModel(Group, db.session)
    related_views = [MyOtherView()]

Change to this:

class MyView(GeneralView):
    datamodel = SQLAModel(Group, db.session)
    related_views = [MyOtherView]

Migrating from 0.2.X to 0.3.X

This new version (0.3.X) has many internal changes, if you feel lost please post an issue on github https://github.com/dpgaspar/Flask-AppBuilder/issues?state=open

All direct imports from your ‘app’ directory were removed, so there is no obligation in using the base AppBuilder-Skeleton.

Security tables have changed their names, AppBuilder will automatically migrate all your data to the new tables.

1 - Change your BaseApp initialization (views.py)

From this:

baseapp = BaseApp(app)

Change to this:

baseapp = BaseApp(app, db)

2 - Remove from OpenID and Login initialization (__init__.py)

From this:

app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
babel = Babel(app)
lm = LoginManager()
lm.init_app(app)
lm.login_view = 'login'
oid = OpenID(app, os.path.join(basedir, 'tmp'))

from app import models, views

Change to this:

app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)

from app import models, views

Migrating from 0.1.X to 0.2.X

It’s very simple, change this:

baseapp = BaseApp(app)
baseapp.add_view(GroupGeneralView, "List Groups","/groups/list","th-large","Contacts")
baseapp.add_view(PersonGeneralView, "List Contacts","/persons/list","earphone","Contacts")
baseapp.add_view(PersonChartView, "Contacts Chart","/persons/chart","earphone","Contacts")

To this:

baseapp = BaseApp(app)
baseapp.add_view(GroupGeneralView(), "List Groups","/groups/list","th-large","Contacts")
baseapp.add_view(PersonGeneralView(), "List Contacts","/persons/list","earphone","Contacts")
baseapp.add_view(PersonChartView(), "Contacts Chart","/persons/chart","earphone","Contacts")

Small change, you just have to instantiate your classes.