首页 > 解决方案 > Wtforms - 如何管理 SQLite JSON 类型的呈现和创建动态表单

问题描述

我正在尝试使用烧瓶、wtforms 和 wtforms_alchemy 创建两种形式。我要坚持的三个主要试金石是:

  1. 我试图避免必须在表单类中手动创建表单字段,而让繁重的工作wtforms_alchemy先使用 db 模型,然后添加必要的预处理和后处理功能。
  2. 我试图避免手动处理 jinja 模板中的表单字段,而是通过 wtform 函数管理渲染,并将实际渲染留给bootstrap-flask'srender_form宏。
  3. 我试图避免必须使用模型或表单数据中的模型手动填充表单数据,而是将其留给wtforms'populate_obj

NewPortalsForm

我希望从字典的 json 列表中填充此表单(请参阅PORTAL_LIST下面的 MRE)。当直接向表单提供 json 对象时,这适用于大多数字段。但是,有一个字段是 SQLite 的 JSON 类型,需要转储到 StringField 中。

AccountForm

valid_kwargs我真的很难找出这种表单的最佳方法,因为由于可变字段,我需要能够动态生成表单。

最小可重现示例

这是我目前拥有的:

# pip install flask flask-sqlalchemy wtforms flask-wtf wtforms-alchemy bootstrap-flask
from sqlalchemy.ext.mutable import MutableDict
from flask_sqlalchemy import SQLAlchemy
from wtforms.fields import SubmitField, FormField, FieldList, StringField
from flask_wtf import FlaskForm
from wtforms_alchemy import model_form_factory
from flask_bootstrap import Bootstrap
from flask import Flask, render_template_string, jsonify

app = Flask(__name__)
app.config["SECRET_KEY"] = "secret"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'

db = SQLAlchemy(app)
bootstrap = Bootstrap(app)
# simple bootstrap html template for form rendering
HTML = """
<!doctype html>
<html lang="en">
    <head>
        {% block head %}
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=yes">
            {% block styles %}
                {{ bootstrap.load_css() }}
            {% endblock %}
            <title>Form Tests</title>
        {% endblock %}
    </head>
    <body>
        <!-- Your page content -->
        <div class="container">
            {% block content %}
                {% from 'bootstrap/form.html' import render_form %}
                {{ render_form(form) }}
            {% endblock %}
        </div>
        {% block scripts %}
            {{ bootstrap.load_js() }}
        {% endblock %}
    </body>
</html>
"""

class BaseMixin(object):
    def as_dict(self):
        return {c.name: getattr(self, c.name) for c in self.__table__.columns}
       
    def update(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

class Portal(db.Model, BaseMixin):
    __tablename__ = 'portal'
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    module = db.Column(db.String(100), nullable=False)
    klass = db.Column(db.String(100), nullable=False)
    # for extra fields, key/value = fieldname/str_repr_of_fieldtype ('str'/'bool')
    valid_kwargs = db.Column(MutableDict.as_mutable(db.JSON), nullable=False, default=dict())

class Account(db.Model, BaseMixin):
    __tablename__ = 'account'
    id = db.Column(db.Integer, primary_key=True)
    portal_id = db.Column(db.Integer, db.ForeignKey('portal.id'))  # one-to-one relationship
    username = db.Column(db.String(100), nullable=False)
    password = db.Column(db.String(100), nullable=False)
    # for extra fields, specific to each portal (key/value = fieldname/fieldvalue)
    kwargs = db.Column(MutableDict.as_mutable(db.JSON), nullable=False, default=dict())
    # portal = accessible thanks to portal_id/portal.id ForeignKey

# override ModelForm in order to have form.validate_on_submit()
ModelForm = model_form_factory(FlaskForm)
       
class NewPortalForm(ModelForm):
    class Meta:
        model = Portal
        csrf = False  # required since this is used as a subform in FieldList(FormField())
    valid_kwargs = StringField()  # how do I manage to utilize json.dumps on form submission?
    
class NewPortalsForm(FlaskForm):
    portals = FieldList(FormField(NewPortalForm))
    submit = SubmitField('Save')

PORTAL_LIST = [
    # each dict in the list is a portal_definition I want to save to the database and use to dynamically render a form based on the definition
    {
        # name, module, and klass are fields present in every form_definition
        "name": "a",  # name of the form, for display in the ui
        "module": "b",  # module of the form
        "klass": "c",  # class in the module - combined with module for dynamic import
        # valid_kwargs is a dict of additional fieldname: fieldtype items, which indicate additional variable fields I want to add to this portal_definition
        "valid_kwargs": {
            "username": "str",  # would render as a StringInput
            "password": "str", 
            "folder": "str",
            "download_history": "bool"  # would render as a CheckboxInput
        }
    },
    {
        "name": "d",
        "module": "e",
        "klass": "f",
        "valid_kwargs": {
            "username": "str",
            "password": "str", 
            "table": "str"
        }
    }
]

@app.route('/create-portals', methods=['get', 'post'])
def create_portals():
    form = NewPortalsForm(portals=PORTAL_LIST)
    if form.validate_on_submit():
        for portal in form.portals.entries:
            portal_obj = Portal()
            portal.form.populate_obj(portal_obj)
            db.session.add(portal_obj)
        db.session.commit()
        return jsonify([p.as_dict() for p in Portal.query.all()]) 
    return render_template_string(HTML, form=form)
    
if __name__ == "__main__":
    db.create_all()
    app.run(debug=True)

标签: pythonpython-3.xflasksqlalchemywtforms

解决方案


推荐阅读