Flask-Admin使用SQLAlchemy上下文相关功能创建视图

时间:2018-06-28 05:11:06

标签: python flask flask-sqlalchemy flask-admin

我有一个数据模型,该模型的列取决于其他列的值,按照this页上的说明,我创建了一个上下文相关函数,该函数用于确定该特定列的值。创作,像这样:

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)

这种方法相当不错,我可以从命令行或使用脚本创建行而不会出现问题。

我还使用Flask-Admin向应用程序中添加了ModelView

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


admin.add_view(MyModelView(MyModel, db.session))

这也很不错,直到我单击 list 视图中的 Create 按钮。我收到此错误:

  

AttributeError:“ NoneType”对象没有属性“ get_current_parameters”

由于create_modelModelView处理程序的实现是this

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

,并且在实例化模型时这里没有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

现在,我可以重新定义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

尽管此方法有效,但我怀疑它会破坏某些内容。 Flask-Admin具有populate_obj表单和字段方法的几种实现,因此,我想确保所有内容的安全。

执行此操作的正确方法是什么?

1 个答案:

答案 0 :(得分:0)

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

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

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')

启动此应用时,打印的内容是:

1
2
3
getting value from context...
4

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

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

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

根据您的目标,您可以选择几种方法来克服此问题:

1)仅将my_column的值添加到数据库中时:

只需忽略表单中的字段,然后将其添加到数据库中即可确定。

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

2)仅在将其添加到数据库时才进行计算,但之后可以对其进行编辑:

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