flask-admin form: Constrain Value of Field 2 depending on Value of Field 1

前端 未结 1 655
遇见更好的自我
遇见更好的自我 2020-12-08 23:09

One feature I have been struggling to implement in flask-admin is when the user edits a form, to constrain the value of Field 2 once Field 1 has been set.

Let me giv

相关标签:
1条回答
  • 2020-12-08 23:46

    I see two ways of tacking this problem:

    1- When Flask-Admin generate the form, add data attributes with the mid of each methodArg on each option tag in the methodArg select. Then have some JS code filter the option tags based on the recipe selected.

    EDIT

    Here is a tentative try at putting a data-mid attribute on each 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
    

    The blocker is in the fact that those render calls are triggered from the jinja templates, so you are pretty much stuck updating a widget (Select being the most low-level one in WTForms, and is used as a base for Flask-Admin's Select2Field).

    After getting those data-mid on each of your options, you can proceed with just binding an change on your recipe's select and display the methodarg's option that have a matching data-mid. Considering Flask-Admin uses select2, you might have to do some JS tweaking (easiest ugly solution would be to clean up the widget and re-create it for each change event triggered)

    Overall, I find this one less robust than the second solution. I kept the monkeypatch to make it clear this should not be used in production imho. (the second solution is slightly less intrusive)

    2- Use the supported ajax-completion in Flask-Admin to hack your way into getting the options that you want based on the selected recipe:

    First, create a custom AjaxModelLoader that will be responsible for executing the right selection query to the 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
    

    Then, update Flask-Admin's form.js to get the browser to send you the recipe information instead of the methodArg name that needs to be autocompleted. (or you could send both in query and do some arg parsing in your AjaxLoader since Flask-Admin does no parsing whatsoever on query, expecting it to be a string I suppose [0]. That way, you would keep the auto-completion)

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

    This snippet is taken from Flask-Admin's form.js [1]

    Obviously, this needs some tweaking and parametrising (because doing such a hacky solution would block you from using other ajax-populated select in the rest of your app admin + the update on form.js directly like that would make upgrading Flask-Admin extremely cumbersome)

    Overall, I am unsatisfied with both solutions and this showcase that whenever you want to go out of the tracks of a framework / tool, you can end up in complex dead ends. This might be an interesting feature request / project for someone willing to contribute a real solution upstream to Flask-Admin though.

    0 讨论(0)
提交回复
热议问题