Flask-Admin create view with SQLAlchemy context-sensitive functions Flask-Admin create view with SQLAlchemy context-sensitive functions flask flask

Flask-Admin create view with SQLAlchemy context-sensitive functions


So there are several factors which come in to play at this question.

But first, a small fully functional app to illustrate:

from flask_admin.contrib.sqla import ModelViewfrom flask_sqlalchemy import SQLAlchemyfrom flask_admin import Adminfrom flask import Flaskapp = Flask(__name__)db = SQLAlchemy(app)admin = Admin(app)app.secret_key = 'arstartartsar'def get_column_value_from_context(context):    print('getting value from context...')    if context.isinsert:        return 'its an insert!'    else:        return 'aww, its not an insert'class MyModel(db.Model):    id = db.Column(db.Integer, primary_key=True)    my_column = db.Column(db.String(64), nullable=False, default=get_column_value_from_context)    name = db.Column(db.String(32), nullable=False, unique=True, index=True)class MyModelView(ModelView):    form_excluded_columns = ['my_column']db.create_all()admin.add_view(MyModelView(MyModel, db.session))print('1')x = MyModel(name='hey')print('2')db.session.add(x)print('3')db.session.commit()print('4')

When we fire up this app, what's printed is:

123getting value from context...4

So, only after committing, does the get_column_value_from_context function fire up. When, in the create view, you 'create' a new model, you don't have a context yet because you're not committing anything to the database yet. You only get the context to set the default when you're committing your instance to the database! That why, in the source code of flask admin, this happens:

if getattr(default, 'is_callable', False):    value = lambda: default.arg(None)  # noqa: E731

They check if the default you specified is a function, and if so, they call it without context (because there is no context yet!).

You have several options to overcome this depending on your goals:

1) Only calculate the value of my_column when adding it to the database:

Simply ignore the field in the form, and it will be determined when you add it to the db.

class MyModelView(ModelView):    form_excluded_columns = ['my_column']

2) Only calculate it when adding it to the db, but making it editable afterwards:

class MyModelView(ModelView):    form_edit_rules = ['name', 'my_column']    form_create_rules = ['name']