форма flask-admin: ограничить значение поля 2 в зависимости от значения поля 1
одна функция, которую я изо всех сил пытаюсь реализовать в flask-admin, - это когда пользователь редактирует форму, чтобы ограничить значение поля 2 после установки Поля 1.
позвольте мне привести упрощенный пример в словах (фактический случай использования более запутан). Затем я покажу полную суть, которая реализует этот пример, минус функция "constrain".
Допустим у нас есть база данных, которая отслеживает некоторые программы "рецепты" для вывода отчетов в различных форматах. The recipe
таблица из нашей базы образцов есть два рецепта: "серьезный отчет", "ASCII Art".
чтобы реализовать каждый рецепт, мы выбираем один из нескольких методов. The method
таблица нашей базы данных имеет два метода:" tabulate_results","pretty_print".
каждый метод имеет свои параметры. The methodarg
таблица имеет два имени параметров для "tabulate_results" ("строки", "display_total") и два параметра для"pretty_print" ("embellishment_character", "lines_to_jump").
теперь для каждого из рецепты ("серьезный отчет", "ASCII Art") нам необходимо предоставить значение аргументов их соответствующих методов ("tabulate_results", "pretty_print").
для каждой записи recipearg
таблица позволяет выбрать рецепт (это поле 1, например "серьезный отчет") и имя аргумента (это поле 2). Проблема в том, что отображаются все возможные имена аргументов, тогда как они должны быть ограничены на основе значения поля 1.
что фильтрация / стесняя механизм можем ли мы реализовать так, что как только мы выберем "серьезный отчет", мы знаем, что будем использовать метод" tabulate_results", так что доступны только аргументы" rows "и" display_total"?
Я думаю, что какое-то волшебство AJAX, которое проверяет поле 1 и задает запрос для значений поля 2, но понятия не имеет, как действовать.
вы можете увидеть это, играя с сутью: нажмите на Recipe Arg
tab. В первой строке ("серьезный отчет") при попытке изменить значение " Methodarg при нажатии на него доступны все четыре имени аргументов, а не только два.
# full gist: please run this
from flask import Flask
from flask_admin import Admin
from flask_admin.contrib import sqla
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///a_sample_database.sqlite'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Create admin app
admin = Admin(app, name="Constrain Values", template_mode='bootstrap3')
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
class Method(db.Model):
__tablename__ = 'method'
mid = Column(Integer, primary_key=True)
method = Column(String(20), nullable=False, unique=True)
methodarg = relationship('MethodArg', backref='method')
recipe = relationship('Recipe', backref='method')
def __str__(self):
return self.method
class MethodArg(db.Model):
__tablename__ = 'methodarg'
maid = Column(Integer, primary_key=True)
mid = Column(ForeignKey('method.mid', ondelete='CASCADE', onupdate='CASCADE'), nullable=False)
methodarg = Column(String(20), nullable=False, unique=True)
recipearg = relationship('RecipeArg', backref='methodarg')
inline_models = (Method,)
def __str__(self):
return self.methodarg
class Recipe(db.Model):
__tablename__ = 'recipe'
rid = Column(Integer, primary_key=True)
mid = Column(ForeignKey('method.mid', ondelete='CASCADE', onupdate='CASCADE'), nullable=False)
recipe = Column(String(20), nullable=False, index=True)
recipearg = relationship('RecipeArg', backref='recipe')
inline_models = (Method,)
def __str__(self):
return self.recipe
class RecipeArg(db.Model):
__tablename__ = 'recipearg'
raid = Column(Integer, primary_key=True)
rid = Column(ForeignKey('recipe.rid', ondelete='CASCADE', onupdate='CASCADE'), nullable=False)
maid = Column(ForeignKey('methodarg.maid', ondelete='CASCADE', onupdate='CASCADE'), nullable=False)
strvalue = Column(String(80), nullable=False)
inline_models = (Recipe, MethodArg)
def __str__(self):
return self.strvalue
class MethodArgAdmin(sqla.ModelView):
column_list = ('method', 'methodarg')
column_editable_list = column_list
class RecipeAdmin(sqla.ModelView):
column_list = ('recipe', 'method')
column_editable_list = column_list
class RecipeArgAdmin(sqla.ModelView):
column_list = ('recipe', 'methodarg', 'strvalue')
column_editable_list = column_list
admin.add_view(RecipeArgAdmin(RecipeArg, db.session))
# More submenu
admin.add_view(sqla.ModelView(Method, db.session, category='See Other Tables'))
admin.add_view(MethodArgAdmin(MethodArg, db.session, category='See Other Tables'))
admin.add_view(RecipeAdmin(Recipe, db.session, category='See Other Tables'))
if __name__ == '__main__':
db.drop_all()
db.create_all()
db.session.add(Method(mid=1, method='tabulate_results'))
db.session.add(Method(mid=2, method='pretty_print'))
db.session.commit()
db.session.add(MethodArg(maid=1, mid=1, methodarg='rows'))
db.session.add(MethodArg(maid=2, mid=1, methodarg='display_total'))
db.session.add(MethodArg(maid=3, mid=2, methodarg='embellishment_character'))
db.session.add(MethodArg(maid=4, mid=2, methodarg='lines_to_jump'))
db.session.add(Recipe(rid=1, mid=1, recipe='Serious Report'))
db.session.add(Recipe(rid=2, mid=2, recipe='ASCII Art'))
db.session.commit()
db.session.add(RecipeArg(raid=1, rid=1, maid=2, strvalue='true' ))
db.session.add(RecipeArg(raid=2, rid=1, maid=1, strvalue='12' ))
db.session.add(RecipeArg(raid=3, rid=2, maid=4, strvalue='3' ))
db.session.commit()
# Start app
app.run(debug=True)
1 ответов
я вижу два способа решения этой проблемы:
1-Когда Flask-Admin генерирует форму, добавьте data
атрибуты mid
каждого methodArg
в каждом option
в теге methodArg
выбрать. Тогда есть некоторый фильтр кода JS option
теги на основе выбранного рецепта.
редактировать
примерный попробовать на data-mid
атрибут каждого option
:
def monkeypatched_call(self, field, **kwargs):
kwargs.setdefault('id', field.id)
if self.multiple:
kwargs['multiple'] = True
html = ['<select %s>' % html_params(name=field.name, **kwargs)]
for (val, label, selected), (_, methodarg) in zip(field.iter_choices(), field._get_object_list()):
html.append(self.render_option(val, label, selected, **{'data-mid': methodarg.mid}))
html.append('</select>')
return HTMLString(''.join(html))
Select.__call__ = monkeypatched_call
блокатор заключается в том, что вызовы рендеринга запускаются из шаблонов jinja, поэтому вы в значительной степени застряли в обновлении виджета (Select
будучи самым низким уровнем в WTForms, и используется в качестве базы для колбы-администратора Select2Field
).
после получения этих data-mid
на каждом из ваших вариантов вы можете продолжить просто привязку change
на выбор рецепта и отображения methodarg в option
, что соответствует data-mid
. Учитывая колбу-админ использует select2
, возможно, вам придется сделать некоторые настройки JS (самым простым уродливым решением было бы очистить виджет и воссоздать его для каждого change
событие срабатывает)
в целом, я нахожу это менее надежным, чем второе решение. Я сохранил monkeypatch, чтобы дать понять, что это не должно использоваться в производстве imho. (второе решение немного менее навязчивое)
2-Используйте поддерживаемое ajax-завершение в Flask-Admin, чтобы взломать свой путь к получению параметров, которые вы хотите на основе выбранного рецепт:
сначала создайте пользовательский AjaxModelLoader, который будет отвечать за выполнение запроса правильного выбора в DB:
class MethodArgAjaxModelLoader(sqla.ajax.QueryAjaxModelLoader):
def get_list(self, term, offset=0, limit=10):
query = self.session.query(self.model).filter_by(mid=term)
return query.offset(offset).limit(limit).all()
class RecipeArgAdmin(sqla.ModelView):
column_list = ('recipe', 'methodarg', 'strvalue')
form_ajax_refs = {
'methodarg': MethodArgAjaxModelLoader('methodarg', db.session, MethodArg, fields=['methodarg'])
}
column_editable_list = column_list
затем обновите Flask-Admin's form.js
чтобы получить браузер, чтобы отправить вам информацию о рецепте вместо methodArg
имя, который должен быть завершаются автоматически. (или вы можете отправить оба query
и сделайте некоторый анализ arg в вашем AjaxLoader, так как Flask-Admin не делает никакого анализа на query
, ожидая, что это будет строка I предположим [0]. Таким образом, вы бы сохранили автозаполнение)
data: function(term, page) {
return {
query: $('#recipe').val(),
offset: (page - 1) * 10,
limit: 10
};
},
этот фрагмент взят из Flask-Admin's form.js
[1]
очевидно, что это требует некоторой настройки и параметризации (потому что такое хакерское решение блокирует использование другого ajax-заполненного выбора в остальной части вашего администратора приложения + обновление на form.js
непосредственно, как это было бы сделать обновление Flask-Admin
чрезвычайно громоздко)
в целом, я недоволен обоими решениями и этой витриной, что всякий раз, когда вы хотите выйти из треков фреймворка / инструмента, вы можете оказаться в сложных тупиках. Это может быть интересный запрос / проект функции для тех, кто готов внести свой вклад реальные решение вверх по течению к колбе-Admin.