Flask-Admin создает представление с помощью контекстно-зависимых функций SQLAlchemy
У меня есть модель данных, которая имеет столбец, который зависит от других значений столбца, следуя инструкциям на этой странице. Я создал контекстно-зависимую функцию, которая используется для определения значения этого конкретного столбца при создании, примерно так:
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)
Этот подход работает довольно прилично, я могу создавать строки без проблем из командной строки или с помощью скрипта.
Я также добавил 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))
Это также работает довольно прилично, пока я не нажму кнопку " Создать" в представлении списка. Я получаю эту ошибку:
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
и здесь нет 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
Несмотря на это, я подозреваю, что это что-то ломает. Колба-Admin имеет несколько реализации populate_obj
метода форм и полей, поэтому я хотел бы, чтобы держать все в безопасности.
Каков правильный способ сделать это?
Ответы
Ответ 1
Поэтому есть несколько факторов, которые возникают в этом вопросе.
Но во-первых, небольшое полнофункциональное приложение для иллюстрации:
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
функция get_column_value_from_context
. Когда в представлении create вы создаете новую модель, у вас еще нет контекста, потому что вы еще ничего не делаете в базе данных. Вы получаете контекст, чтобы установить значение по умолчанию, когда вы делаете свой экземпляр в базу данных! Вот почему, в исходном коде флэшка admin, это происходит:
if getattr(default, 'is_callable', False):
value = lambda: default.arg(None) # noqa: E731
Они проверяют, является ли заданная вами по умолчанию функция, и если это так, они называют ее без контекста (потому что пока нет контекста!).
У вас есть несколько вариантов преодоления этого в зависимости от ваших целей:
1) Только вычислить значение my_column
при добавлении его в базу данных:
Просто игнорируйте поле в форме, и оно будет определено, когда вы добавите его в db.
class MyModelView(ModelView):
form_excluded_columns = ['my_column']
2) Рассчитывайте только при добавлении его в db, но затем редактируя его:
class MyModelView(ModelView):
form_edit_rules = ['name', 'my_column']
form_create_rules = ['name']