当前位置: 首页 > 面试题库 >

flask-admin表单:根据字段1的值约束字段2的值

濮阳旺
2023-03-14
问题内容

我一直努力在flask-admin中实现的一个功能是,当用户编辑表单时,一旦设置了字段1,便会约束字段2的值。

让我用语言给出一个简化的示例(实际用例更加复杂)。然后,我将展示实现该示例的完整要点,减去“约束”功能。

假设我们有一个数据库,该数据库跟踪一些软件“配方”以输出各种格式的报表。recipe我们的示例数据库的表有两个配方:“严重报告”,“ ASCII艺术”。

为了实现每种配方,我们从几种方法中选择一种。method我们数据库的表有两种方法:“ tabulate_results”,“
pretty_print”。

每种方法都有参数。该methodarg表具有“ tabulate_results”的两个参数名称(“ rows”,“ display_total”)和“
pretty_print”的两个参数名称(“ embellishment_character”,“ lines_to_jump”)。

现在,对于每个配方(“ Serious Report”,“ ASCII Art”),我们需要提供其各自方法(“ tabulate_results”,“
pretty_print”)的参数值。

对于每个记录,该recipearg表允许我们选择一个配方(即字段1,例如“ Serious
Report”)和一个参数名称(即字段2)。问题在于显示了所有可能的参数名称,而它们需要根据字段1的值进行约束。

我们可以实现哪种过滤/约束机制,以便一旦选择“ Serious Report”,就知道将使用“ tabulate_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生成表单时,在选择中的每个标签上添加每个data属性的属性。然后让一些JS代码根据所选配方过滤标签。mid``methodArg``option``methodArg``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中最底层的小部件,并且用作Flask-
Admin的基础Select2Field)。

得到这些后data- mid对你的每一个选择,你可以只用一个结合继续change在你的食谱的选择,并显示methodarg的option具有匹配data- mid。考虑到Flask-
Admin的使用select2,您可能需要做一些JS调整(最简单的丑陋解决方案是清理小部件并为每个change触发的事件重新创建它)

总体而言,我发现该解决方案不如第二种解决方案强大。我保留了monkeypatch,以明确表示此产品不应在生产恕我直言中使用。(第二种解决方案是侵入性稍差的)

2-使用Flask-Admin中受支持的ajax-completion来破解基于所选配方的所需选项:

首先,创建一个定制的AjaxModelLoader,它将负责对数据库执行正确的选择查询:

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
form.js,使浏览器向您发送配方信息,而不是methodArg需要自动完成的名称。(或者您可以同时发送queryAjaxLoader并在其中进行一些arg解析,因为Flask-
Admin不会进行任何解析query,期望它是我认为是[0]的字符串。那样,您将保持自动完成)

data: function(term, page) {
    return {
        query: $('#recipe').val(),
        offset: (page - 1) * 10,
        limit: 10
    };
},

该摘录摘自Flask-Admin的form.js [1]

显然,这需要一些调整和参数设置(因为执行这样的hacky解决方案会阻止您在应用程序管理员的其余部分中使用其他ajax填充的select以及form.js直接进行更新,那样升级将Flask- Admin非常麻烦)

总体而言,我对这两个解决方案都不满意,并且此展示表明,只要您想脱离框架/工具的轨道,就可能陷入复杂的死胡同。对于那些愿意为Flask-Admin上游贡献
真正 解决方案的人来说,这可能是一个有趣的功能请求/项目。



 类似资料:
  • 我有一个正在JSP中构建的Spring MVC表单,它将需要在字段中输入一个帐号。当我输入该数字时,我想对数据库运行一个查询,以拉回该特定数字的相关信息。然后,此数据将填充表单上的其他字段。 这是我在JSP中为将要输入的帐号编写的Spring绑定代码。因此,只要我输入这个数字,就会触发一个DB查询,将其他字段的数据带回来。 戴夫

  • 我想格式化一个<code>java.time。LocalTime,但格式可以根据其值而变化: 如果一天中的小时数为12或0,请使用格式 否则,请使用格式 我当然可以这样做: 但为此,我需要创建两个不同的格式化程序。 我只想使用一个可以多次重用的格式化程序: 我正在尝试使用来做到这一点: 我尝试使用< code > datetimeformatterbuilder . optional start(

  • 问题内容: 示例: File.txt的内容: 当使用’sort -k 1,1 File.txt’时,行的顺序不会改变,尽管我们期望: 如何根据绝对 数值 对包含数字的字段进行排序? 问题答案: 看看手册页进行排序 … 所以这是一个例子…

  • 问题内容: 在Flask 0.8中,我知道我可以使用来访问单个表单域form.fieldname.data,但是有一种简单的方法可以遍历所有表单域吗?我正在构建一个电子邮件正文,我想遍历所有字段并为每个字段创建一个字段名/值条目,而不是通过命名每个字段并附加来手动创建它。 问题答案: 你可以遍历表单数据: 你可以遍历所有表单字段:

  • 问题内容: 我需要创建一个自定义约束注释,该注释可以访问我的bean的另一个字段的值。我将使用此批注来验证字段,因为它取决于另一个字段的值,但是我定义该字段的方式是编译器在我的字段“必须为常量表达式”中说“批注属性的值”。 我以这种方式定义了它: 在我的豆子中,我想要这样的东西: 有什么方法可以定义注释,以便字段值可以是变量? 谢谢 问题答案: 最简单的方法是退后一步:您编写的约束/验证器在字段级

  • 问题内容: 我想知道模式草案03是否可行。我已经在其他地方使用了依赖项,我认为可能需要创造性地使用它们,以便使用它们来指定某些字段的属性。 我目前的最佳尝试(无效)将使您对我的追求有所了解。我想要一个默认值,当另一个字段具有特定值时是可选值。 问题答案: 草案的第3版绝对可以做到这一点。由于您具有允许的国家/地区的完整列表,因此您可以执行以下操作: 因此,您实际上为架构定义了两种子类型,一种用于需