Flask-Admin使用SQLAlchemy上下文相关函数创建视图

osj*_*ick 13 python flask flask-sqlalchemy flask-admin

我有一个具有依赖于其它列的值一列,按照该指令的数据模型,这个页面,我创建了用于确定在创建这个特定列,像这样的值上下文敏感的功能:

def get_column_value_from_context(context):
    # Instructions to produce value

    return value


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)
    title = db.Column(db.String(128),
                      nullable=False)
    description = db.Column(db.String(256),
                            nullable=False)
Run Code Online (Sandbox Code Playgroud)

这种方法非常不错,我可以从命令行或使用脚本创建没有问题的行.

我还ModelView使用Flask-Admin在应用程序中添加了一个:

class MyModelView(ModelView):
    can_view_details = True
    can_set_page_size = True
    can_export = True


admin.add_view(MyModelView(MyModel, db.session))
Run Code Online (Sandbox Code Playgroud)

在我单击列表视图中的" 创建"按钮之前,这也很有效.我收到此错误:

AttributeError:'NoneType'对象没有属性'get_current_parameters'

因为在这里执行create_model处理程序ModelView这样的:

def create_model(self, form):
    """
        Create model from form.
        :param form:
            Form instance
    """
    try:
        model = self.model()
        form.populate_obj(model)
        self.session.add(model)
        self._on_model_change(form, model, True)
        self.session.commit()
    except Exception as ex:
        if not self.handle_view_exception(ex):
            flash(gettext('Failed to create record. %(error)s', error=str(ex)), 'error')
            log.exception('Failed to create record.')

        self.session.rollback()

        return False
    else:
        self.after_model_change(form, model, True)


    return model
Run Code Online (Sandbox Code Playgroud)

并且这里没有context实例化模型的时间.所以,我创建了一个自定义视图,可以重新定义创建处理程序中的模型实例化:

class CustomSQLAView(ModelView):
    def __init__(self, *args, **kwargs):
        super(CustomSQLAView, self).__init__(*args, **kwargs)

    def create_model(self, form):
        """
            Create model from form.
            :param form:
                Form instance
        """
        try:
            model = self.get_populated_model(form)
            self.session.add(model)
            self._on_model_change(form, model, True)
            self.session.commit()
        except Exception as ex:
            if not self.handle_view_exception(ex):
                flash(gettext('Failed to create record. %(error)s', error=str(ex)), 'error')
                log.exception('Failed to create record.')

            self.session.rollback()

            return False
        else:
            self.after_model_change(form, model, True)

        return model

    def get_populated_model(self, form):
        model = self.model()
        form.populate_obj(model)

        return model
Run Code Online (Sandbox Code Playgroud)

现在我可以重新定义get_populated_model方法以通常的方式实例化模型:

class MyModelView(CustomSQLAView):
    can_view_details = True
    can_set_page_size = True
    can_export = True

    def get_populated_model(self, form):
        model = self.model(
            name=form.name.data,
            title=form.title.data,
            description=form.description.data,
        )

        return model
Run Code Online (Sandbox Code Playgroud)

尽管如此,我怀疑它打破了一些东西.Flask-Admin有几种populate_obj表单和字段方法的实现,所以我想保证一切安全.

这样做的正确方法是什么?

Joo*_*ost 2

因此,有几个因素会影响这个问题。

但首先,用一个功能齐全的小型应用程序来说明:

from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask import Flask

app = 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')
Run Code Online (Sandbox Code Playgroud)

当我们启动这个应用程序时,打印的内容是:

1
2
3
getting value from context...
4
Run Code Online (Sandbox Code Playgroud)

因此,只有在提交get_column_value_from_context,该函数才会启动。当您在创建视图中“创建”新模型时,您还没有上下文,因为您还没有向数据库提交任何内容。当您将实例提交到数据库时,您只能获取设置默认值的上下文!这就是为什么,在flask admin的源代码中,会发生这种情况:

if getattr(default, 'is_callable', False):
    value = lambda: default.arg(None)  # noqa: E731
Run Code Online (Sandbox Code Playgroud)

他们检查您指定的默认值是否是一个函数,如果是,他们会在没有上下文的情况下调用它(因为还没有上下文!)。

根据您的目标,您有多种选择来克服这个问题:

my_column1)只计算添加到数据库时的值:

只需忽略表单中的字段,当您将其添加到 db 时就会确定。

class MyModelView(ModelView):
    form_excluded_columns = ['my_column']
Run Code Online (Sandbox Code Playgroud)

2)仅在将其添加到数据库时计算它,但之后使其可编辑:

class MyModelView(ModelView):
    form_edit_rules = ['name', 'my_column']
    form_create_rules = ['name']
Run Code Online (Sandbox Code Playgroud)