[PYTHON] flask-admin 양식 : 필드 1의 값에 따라 필드 2의 값 제한
PYTHONflask-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- 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에게 실제 솔루션 업스트림에 기꺼이 기여하려는 누군가를위한 흥미로운 기능 요청 / 프로젝트 일 수 있습니다.
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
'PYTHON' 카테고리의 다른 글
[PYTHON] Matplotlib 3D 플롯 zorder 문제 (0) | 2018.11.26 |
---|---|
[PYTHON] 파이썬 문자열 포맷 : % 대 연결 (0) | 2018.11.26 |
[PYTHON] 파이썬으로 모듈을 올바르게 임포트하기 (0) | 2018.11.26 |
[PYTHON] 어떻게 파이썬을 사용하여 디렉토리에있는 모든 이미지의 크기를 확인하려면? (0) | 2018.11.26 |
[PYTHON] 파이썬에서 다른 쓰레드가 덮어 쓰지 않고 raw_input ()에서 입력 값을 읽음 (0) | 2018.11.26 |