복붙노트

[PYTHON] flask-admin 양식 : 필드 1의 값에 따라 필드 2의 값 제한

PYTHON

flask-admin 양식 : 필드 1의 값에 따라 필드 2의 값 제한

내가 flask-admin에서 구현하기 위해 애 쓰고있는 한 가지 기능은 사용자가 양식을 편집하여 Field 1이 설정되면 Field 2의 값을 제한하는 경우입니다.

단어로 단순화 된 예제를 제공하겠습니다. 실제 사용 사례는 더 복잡합니다. 그런 다음 "제한"기능이없는 예제를 구현하는 전체 요점을 보여 드리겠습니다.

다양한 형식의 보고서를 출력하는 소프트웨어 "요리법"을 추적하는 데이터베이스가 있다고 가정 해 보겠습니다. 샘플 데이터베이스의 레서피 테이블에는 "심각한 보고서", "ASCII 아트"의 두 가지 조리법이 있습니다.

각 레시피를 구현하기 위해 여러 가지 방법 중 하나를 선택합니다. 데이터베이스의 메소드 테이블에는 "tabulate_results", "pretty_print"의 두 가지 메소드가 있습니다.

각 메소드에는 매개 변수가 있습니다. methodarg 테이블에는 "tabulate_results"( "rows", "display_total")에 대한 두 개의 매개 변수 이름과 "pretty_print"( "embellishment_character", "lines_to_jump")에 대한 두 개의 매개 변수가 있습니다.

이제 각각의 조리법 ( "심각한 보고서", "ASCII 예술")에 대해 각각의 방법 ( "tabulate_results", "pretty_print")의 인수 값을 제공해야합니다.

각 레코드에 대해 recipearg 테이블을 통해 조리법 (즉, "심각한 보고서"와 같은 필드 1)과 인수 이름 (필드 2)을 선택할 수 있습니다. 문제는 가능한 모든 인수 이름이 표시되는 반면 필드 1의 값에 따라 제한되어야한다는 것입니다.

"심각한 보고서"를 선택하면 "행렬"및 "display_total"인수 만 사용할 수 있도록 "tabulate_results"메서드를 사용한다는 것을 알고 있으므로 어떤 필터링 / 제한 메커니즘을 구현할 수 있습니까?

필드 1을 확인하고 필드 2 값에 대한 쿼리를 설정하는 AJAX 마법사를 생각 중이나 진행 방법을 모릅니다.

요지로 놀아서 이것을 볼 수 있습니다 : 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. ==============================

    1.이 문제를 해결하는 두 가지 방법이 있습니다.

    이 문제를 해결하는 두 가지 방법이 있습니다.

    1- Flask-Admin이 폼을 생성 할 때 methodArg select의 각 옵션 태그에 각 methodArg의 중간에 데이터 속성을 추가합니다. 그런 다음 일부 JS 코드에서 선택한 래서 피를 기반으로 옵션 태그를 필터링하십시오.

    편집하다

    다음은 각 옵션에 data-mid 속성을 넣는 시험적인 시도입니다.

    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 템플릿에서 트리거되므로 사실 Wgetorm에서 가장 낮은 레벨의 것으로 선택되고 Flask-Admin의 Select2Field에 대한 기본으로 사용됩니다. .

    각 옵션에서 데이터 중간 값을 얻은 후에는 레서피 선택에 변경 사항을 바인딩하고 일치하는 데이터 - 중간을 갖는 methodarg 옵션을 표시 할 수 있습니다. Flask-Admin이 select2를 사용한다고 가정 할 때 JS 조정을해야 할 수도 있습니다 (가장 쉬운 방법은 위젯을 정리하고 트리거 된 각 변경 이벤트에 대해 다시 작성하는 것입니다)

    전반적으로 두 번째 솔루션보다 덜 강력합니다. 나는 그것이 생산 imho에서 사용되어서는 안된다는 것을 분명히하기 위해 monkeypatch를 지켰다. (두 번째 솔루션은 약간 간섭이 적음)

    2- Flask-Admin에서 지원되는 Ajax-completion을 사용하여 선택한 레시피를 기반으로 원하는 옵션을 얻습니다.

    먼저 DB에 대한 올바른 선택 쿼리를 실행하는 커스텀 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를 업데이트하여 autocompleted해야하는 methodArg 이름 대신 recipe 정보를 보내도록 브라우저를 가져옵니다. (또는 Flask-Admin은 쿼리에서 파싱을 전혀하지 않기 때문에 AjaxLoader에서 구문 분석을 수행 할 수 있으며, 문자열은 [0]이라고 생각합니다. 이렇게하면 자동 완성을 유지할 수 있습니다)

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

    이 스 니펫은 Flask-Admin의 form.js [1]에서 가져옵니다.

    분명히 이것은 약간의 조정과 매개 변수화가 필요합니다. (해킹 된 솔루션을 사용하면 나머지 아약스가 포함 된 select를 사용하지 못하게 될 것이고 form.js의 업데이트는 Flask-Admin을 매우 번거롭게 업그레이드 할 것입니다. )

    전반적으로, 나는 프레임 워크 / 툴의 트랙을 벗어나고 싶을 때마다 복잡한 막 다른 골목에서 끝낼 수 있다는 두 가지 해결책과이 쇼케이스에 만족하지 않습니다. 이것은 Flask-Admin에게 실제 솔루션 업스트림에 기꺼이 기여하려는 누군가를위한 흥미로운 기능 요청 / 프로젝트 일 수 있습니다.

  2. from https://stackoverflow.com/questions/33660840/flask-admin-form-constrain-value-of-field-2-depending-on-value-of-field-1 by cc-by-sa and MIT license