2015-11-11 27 views
9

Một tính năng mà tôi đang cố gắng thực hiện trong bình-quản trị là khi người dùng chỉnh sửa biểu mẫu, hạn chế giá trị của Trường 2 sau khi Trường 1 có đã được thiết lập.dạng bình chứa-quản trị: Giá trị giới hạn của trường 2 tùy thuộc vào giá trị của trường 1

Hãy để tôi đưa ra một ví dụ đơn giản bằng các từ (trường hợp sử dụng thực tế phức tạp hơn). Sau đó, tôi sẽ hiển thị một ý chính đầy đủ để thực hiện ví dụ đó, trừ đi tính năng "hạn chế".

Giả sử chúng tôi có cơ sở dữ liệu theo dõi một số "công thức nấu ăn" phần mềm để báo cáo đầu ra ở nhiều định dạng khác nhau. Bảng recipe của cơ sở dữ liệu mẫu của chúng tôi có hai công thức: "Báo cáo nghiêm trọng", "Nghệ thuật ASCII".

Để thực hiện từng công thức, chúng tôi chọn một trong một số phương pháp. Bảng method của cơ sở dữ liệu của chúng tôi có hai phương thức: "tabulate_results", "pretty_print".

Mỗi phương pháp có tham số. Bảng methodarg có hai tên thông số cho "tabulate_results" ("hàng", "display_total") và hai tham số cho "pretty_print" ("embellishment_character", "lines_to_jump").

Bây giờ cho mỗi công thức ("Báo cáo nghiêm túc", "Nghệ thuật ASCII"), chúng tôi cần cung cấp giá trị của các đối số của các phương thức tương ứng của chúng ("tabulate_results", "pretty_print").

Đối với mỗi bản ghi, bảng recipearg cho phép chúng tôi chọn công thức (đó là Trường 1, ví dụ "Báo cáo nghiêm trọng") và tên đối số (đó là Trường 2). Vấn đề là tất cả các tên đối số có thể được hiển thị, trong khi chúng cần phải bị ràng buộc dựa trên giá trị của trường 1.

Cơ chế lọc/ràng buộc nào có thể thực hiện như vậy khi chúng tôi chọn "Báo cáo nghiêm trọng", chúng tôi biết sẽ sử dụng phương pháp "tabulate_results" để chỉ có các đối số "hàng" và "display_total" có sẵn?

Tôi đang nghĩ một số thuật sĩ AJAX kiểm tra trường 1 và đặt truy vấn cho giá trị trường 2, nhưng không biết cách tiến hành.

Bạn có thể thấy điều này bằng cách chơi với ý chính: nhấp vào tab Recipe Arg. Trong hàng đầu tiên ("Báo cáo nghiêm trọng"), nếu bạn cố gắng chỉnh sửa giá trị "Methodarg" bằng cách nhấp vào nó, tất cả bốn tên đối số đều có sẵn, thay vì chỉ hai.

# 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

Đối với giao diện cơ sở dữ liệu linh hoạt, tính năng này phải có. Một câu trả lời hay sẽ giúp ích cho nhiều người. Chắc chắn dữ liệu chính nó có thể cấu trúc khác nhau nhưng đó không phải là điểm. Thêm tiền thưởng. :) –

Trả lời

5

Tôi thấy hai cách để tacking vấn đề này:

1- Khi Flask-Admin tạo ra các hình thức, thêm data thuộc tính với mid của mỗi methodArg trên mỗi thẻ option trong methodArg chọn. Sau đó, có một số mã JS lọc các thẻ option dựa trên công thức được chọn.

EDIT

Đây là một thử thăm dò tại đặt một thuộc tính data-mid trên mỗi 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 

Các chặn được trong thực tế là những người làm cho các cuộc gọi được kích hoạt từ các mẫu jinja, vì vậy bạn đang khá nhiều khó khăn đang cập nhật một widget (Select là một trong những cấp thấp nhất trong WTForms, và được sử dụng như là một cơ sở cho Flask-Admin's Select2Field).

Sau khi nhận được những data-mid trên mỗi lựa chọn của bạn, bạn có thể tiến hành với chỉ ràng buộc một change trên một số của công thức nấu ăn của bạn và hiển thị của option rằng có một phù hợp với data-mid methodarg. Xem xét Flask-Admin sử dụng select2, bạn có thể phải thực hiện một số chỉnh sửa JS (giải pháp xấu xí dễ nhất là dọn dẹp tiện ích và tạo lại nó cho mỗi sự kiện change được kích hoạt)

Nhìn chung, tôi thấy cái này ít mạnh mẽ hơn giải pháp thứ hai. Tôi giữ khỉ để làm cho nó rõ ràng điều này không nên được sử dụng trong sản xuất imho. (Giải pháp thứ hai là hơi ít xâm nhập)

2- Sử dụng các hỗ trợ ajax-hoàn thành vào Flask-Admin hack theo cách của bạn vào nhận được các tùy chọn mà bạn muốn dựa trên công thức chọn:

Đầu tiên, tạo một AjaxModelLoader tùy chỉnh mà sẽ chịu trách nhiệm thực hiện truy vấn lựa chọn quyền 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 

Sau đó, cập nhật Flask-Admin của form.js để có được những trình duyệt để gửi cho bạn các thông tin công thức thay vì tên methodArg mà cần phải được tự động hoàn tất . (hoặc bạn có thể gửi cả hai trong query và làm một số phân tích cú pháp arg trong AjaxLoader của bạn kể từ khi Flask-Admin không phân tích cú pháp nào trên query, mong nó là một chuỗi tôi giả sử [0]. Bằng cách đó, bạn sẽ giữ tự động hoàn thành)

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

đoạn này được lấy từ Flask-Admin của form.js[1]

Rõ ràng, điều này cần một số tinh chỉnh và parametrising (bởi vì làm như một giải pháp hacky như vậy sẽ ngăn chặn bạn từ việc sử dụng khác ajax dân cư thưa thớt chọn trong phần còn lại của ứng dụng của bạn quản trị + cập nhật trên form.js trực tiếp như vậy sẽ thực hiện nâng cấp Flask-Admin cực kỳ cồng kềnh)

Nói chung, tôi không hài lòng với cả hai giải pháp và điều này cho thấy bất cứ khi nào bạn muốn thoát khỏi các rãnh của khung/công cụ, bạn có thể kết thúc trong các kết thúc chết phức tạp. Đây có thể là một yêu cầu/dự án tính năng thú vị cho một ai đó sẵn sàng đóng góp một giải pháp thực sự thực ngược dòng cho Flask-Admin.

+1

Sẽ tò mò muốn xem # 1 nếu bạn hiển thị mã làm việc, +1 cho các giải pháp. –

+0

Cảm ơn bạn rất nhiều vì câu trả lời của bạn. Đau khổ vì cảm lạnh ngay bây giờ nhưng mong muốn thử những ý tưởng của bạn (không chắc tôi hoàn toàn hiểu được ý tưởng đầu tiên). Bạn nghĩ rằng một cái gì đó dọc theo dòng của ý tưởng đầu tiên của bạn sẽ là mạnh mẽ nhất? Nhân tiện, nếu bạn phải đoán, đây có phải là loại tính năng sẽ biến nó thành 'bình-quản trị' ở giai đoạn nào đó không? – droptable

+0

Hans Schindler @: Tôi sẽ cố gắng làm việc gì đó cho 1-. Trở ngại lớn nhất của tôi bây giờ là lấy Flask-Admin để thêm thuộc tính 'data' vào thẻ' option'. @: hy vọng nhận được một số mã cho 1 sẽ làm cho nó rõ ràng hơn;) Đối với mạnh nhất, cả hai đều là hack và 1 có một số nhược điểm là tốt: bạn cần phải có được danh sách tất cả các methodArgs trước để JS có thể có một bức tranh đủ lớn để thực hiện việc lọc cho bạn. Cuối cùng, tôi không phải là người duy trì Flask-Admin, tôi đoán đó là một tính năng rất khó khăn để có được một cách tổng quát đúng đắn và vẫn có thể được coi là một trường hợp cạnh tranh. – bperson

Các vấn đề liên quan