python - Wtforms - 如何管理 SQLite JSON 类型的呈现和创建动态表单
问题描述
我正在尝试使用烧瓶、wtforms 和 wtforms_alchemy 创建两种形式。我要坚持的三个主要试金石是:
- 我试图避免必须在表单类中手动创建表单字段,而让繁重的工作
wtforms_alchemy
先使用 db 模型,然后添加必要的预处理和后处理功能。 - 我试图避免手动处理 jinja 模板中的表单字段,而是通过 wtform 函数管理渲染,并将实际渲染留给
bootstrap-flask
'srender_form
宏。 - 我试图避免必须使用模型或表单数据中的模型手动填充表单数据,而是将其留给
wtforms
'populate_obj
NewPortalsForm
我希望从字典的 json 列表中填充此表单(请参阅PORTAL_LIST
下面的 MRE)。当直接向表单提供 json 对象时,这适用于大多数字段。但是,有一个字段是 SQLite 的 JSON 类型,需要转储到 StringField 中。
- 我想在用户添加门户时将此字段隐藏起来,但如果可能的话,我仍然想向他们显示非交互式/只读表中的额外字段列表,而无需手动呈现模板中的内容(第一列是字段名称,第二列是字段类型)。
- 当
populate_obj
为每个门户调用时,我需要以某种方式将隐藏字符串字段 bavk 中的值转换为 json dict,否则由于类型错误(将字符串放入 JSON 列)而引发错误的 db 模型。
AccountForm
valid_kwargs
我真的很难找出这种表单的最佳方法,因为由于可变字段,我需要能够动态生成表单。
- 这里的想法是使用关联门户的
kwargs
键和值来推断字段类型(伪代码:将允许我们确定要呈现的字段的类型,它始终是 StringField 或 BooleanField)kwargs
valid_kwargs
Account.portal.valid_kwargs[kwarg]
- 但是,即使我克服了动态字段渲染挑战,我也需要弄清楚如何能够正确配置表单的
populate_obj
功能,以将所有数据传输到 Account 模型中。
最小可重现示例
这是我目前拥有的:
- 对于表单 1,我被困在如何在表单提交时将 json dict 从字符串转换回来,并且不知道从哪里开始从表单字段呈现表格以向最终用户显示只读版本
valid_kwargs
及其类型。 - 对于表单 2,我不确定从哪里开始使用动态字段,更不用说
populate_obj
将表单数据转换回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)
解决方案
推荐阅读
- r - 如何绘制具有两组的 CoxPH 模型?
- java - 在片段android studio中处理appbar中的clickevent
- outlook - 尽管获得了同意的权限,但 Microsoft Graph API 读取邮件状态代码 404 未找到错误
- sql - 在按行过滤时聚合行
- javascript - 如何在不使用 selenium nodeJS 在铬浏览器中打开打印预览的情况下将页面打印为 PDF?
- javascript - 在前一个倒计时结束后显示新的倒计时
- javascript - 由 react native html to pdf 创建的 pdf 未显示在提供的路径中
- javascript - React - 为什么道具不更新
- python - 如何从日期时间字符串中提取日期值并根据测试文件中的特定字段转换为日期格式
- javascript - 在javascript中重定向网页