在flaskadmin中,我一直在努力实现的一个特性是,当用户编辑表单时,一旦设置了字段1,就限制字段2的值。在
让我用文字给出一个简化的例子(实际用例更复杂)。然后我将展示实现该示例的完整要点,去掉“约束”特性。在
假设我们有一个数据库,它跟踪一些软件“配方”,以各种格式输出报告。我们的示例数据库的recipe
表有两个诀窍:“严重报告”,“ASCII-Art”。在
为了实现每个配方,我们从几种方法中选择一种。我们数据库的method
表有两种方法:“将结果制表”、“打印”。在
每个方法都有参数。methodarg
表有两个用于“tablate”结果的参数名(“rows”,“display_total”),两个用于“pretty_print”(“修饰字符”,“行数”到“跳转”)的参数。在
现在,对于每个配方(“严肃报告”、“ASCII Art”)我们需要提供它们各自方法的参数值(“tablate_results”、“pretty_print”)。在
对于每个记录,recipearg
表允许我们选择一个配方(即字段1,例如“严重报告”)和参数名称(即字段2)。问题是所有可能的参数名都会显示出来,而它们需要根据字段1的值进行约束。在
我们可以实现什么样的过滤/约束机制,这样一旦我们选择了“严重报告”,我们就知道我们将使用“tablate_results”方法,从而只有“rows”和“display_total”参数可用?在
我在想一些AJAX向导,它检查字段1并设置字段2值的查询,但不知道如何继续。在
您可以通过使用要点来看到这一点:单击Recipe Arg
选项卡。在第一行(“严重报告”)中,如果您试图通过单击“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-当Flask Admin生成表单时,在
methodArg
选择中的每个methodArg
标记上添加data
属性和每个methodArg
标记的data
属性。然后让一些JS代码根据所选的配方过滤option
标记。编辑
下面尝试在每个
option
上添加data-mid
属性:拦截器实际上是那些render调用是从jinja模板触发的,因此您几乎无法更新一个小部件(
Select
是WTForms中最低级的一个,并被用作Flask Admin的Select2Field
)的基础)。在获得每个选项的}。考虑到Flask Admin使用
data-mid
之后,可以继续在配方的select上绑定change
,并显示具有匹配的option
方法参数{select2
,您可能需要做一些JS调整(最简单的难看的解决方案是清理小部件并为每个触发的change
事件重新创建它)总的来说,我发现这个解决方案不如第二个解决方案可靠。我保留了monkeypatch是为了明确这不应该在imho的生产中使用。(第二种解决方案的侵入性稍低)
2-使用Flask Admin中支持的ajax补全功能,根据所选配方,轻松获得所需选项:
首先,创建一个自定义AjaxModelLoader,它将负责对数据库执行正确的选择查询:
^{pr2}$然后,更新Flask Admin的
form.js
,让浏览器向您发送配方信息,而不是需要自动完成的methodArg
名称。(或者您可以在query
中同时发送,并在AjaxLoader中执行一些arg解析,因为Flask Admin对query
不做任何解析,希望它是一个字符串,我想是[0]。这样,就可以保持自动完成)这个片段来自Flask管理员的
form.js
[1]显然,这需要一些调整和参数化(因为这样一个老套的解决方案会阻止您在应用程序的其余部分使用其他ajax填充的select+直接对
form.js
进行更新会使升级Flask-Admin
非常麻烦)总的来说,我对这两个解决方案和这个展示都不满意,无论何时,只要你想离开框架/工具的轨道,你都会陷入复杂的死胡同。这可能是一个有趣的特性请求/项目,对于那些愿意向Flask Admin上游贡献真正的解决方案的人来说。
相关问题 更多 >
编程相关推荐