WTForms:从带有关系的SQLAlchemy字段设置默认值
在这里,有很多问题的标题听起来和我接下来要描述的很相似,但根据我花了几个小时的研究来看,这个问题是独一无二的。所以我就开始吧!
我正在写我的第一个Flask应用程序。我使用SQLAlchemy来处理模型层,使用WTForms来处理表单。这个应用程序将是一个轻量级的个人财务管理工具,我可能不会真的用它来做什么严肃的事情。我有一个表用来列出所有的交易,还有一个表用来列出所有的支出类别(比如食品、衣物等等)。交易表里有一列(“category”),它引用了类别表。在视图中,我用一个元素来表示类别列表。
我遇到的问题是,在编辑交易时,我不知道怎么告诉WTForms把元素设置为一个特定的预定义值。(是的,我知道在定义表单时可以设置默认值,但这不是我想问的。)
模型看起来是这样的(不相关的字段已被删除):
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False, unique=True)
# ...
class Trans(db.Model):
# ...
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
category = db.relationship('Category',
backref=db.backref('trans', lazy='dynamic'))
# ...
forms.py:
def category_choices():
return [('0', '')] + [(c.id, c.name) for c in Category.query.all()]
class TransactionForm(Form):
# ...
category = SelectField('Category', coerce=int, validators=[InputRequired()])
# ...
路由(POST还没有实现):
@app.route('/transactions/edit/<trans_id>', methods=['GET', 'POST'])
def trans_edit(trans_id):
transaction = Trans.query.get(trans_id)
form = forms.TransactionForm(obj=transaction)
form.category.choices = forms.category_choices()
form.category.default = str(transaction.category.id)
#raise Exception # for debugging
return render_template('trans.html',
title='Edit Transaction',
form=form)
最后是模板(Jinja2):
{{ form.category(class='trans-category input-medium') }}
正如你在路由中看到的,我试图把form.category.default设置为transaction.category.id,但这并没有奏效。我觉得我的问题在于,我是在表单创建之后才设置“默认值”。我被迫这样做,因为模型是通过SQLAlchemy从数据库中获取的。根本原因似乎是form.category是一个对象(由于它们之间的关系),而WTForms似乎不太好处理。我想我不是第一个遇到这个问题的人……我需要重新调整模型以更好地兼容WTForms吗?我还有什么选择?
谢谢!
1 个回答
我在评论中提到过这个。听起来你可以试试WTForm的SQLAlchemy扩展。这样可以在你的表单中创建一个下拉列表,用于选择类别。
我这里的例子稍微有点不同。我是在将博客文章和主题关联起来。也就是说,很多文章共享一个主题。我想在你的情况下,很多交易会共享一个类别。
表单
from wtforms.ext.sqlalchemy.fields import QuerySelectField #import the ext.
def enabled_topics(): # query the topics (a.k.a categories)
return Topic.query.all()
class PostForm(Form): # create your form
title = StringField(u'title', validators=[DataRequired()])
body = StringField(u'Text', widget=TextArea())
topic = QuerySelectField(query_factory=enabled_topics, allow_blank=True)
模型
这里重要的是a) 确保你正确地定义了关系,b) 在初始化时添加主题,因为你需要用它来创建新的条目。class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(80))
body = db.Column(db.Text)
# one-to-many with Topic
topic = db.relationship('Topic', backref=db.backref('post', lazy='dynamic'))
def __init__(self, title, body, topic):
self.title = title
self.body = body
self.topic = topic
class Topic(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
def __init__(self, name):
self.name = name
视图
这里没什么特别的。只是一个普通的视图,用来生成表单和处理提交的结果。@app.route('/create', methods=['GET', 'POST'])
@login_required
def create_post():
form = PostForm()
if form.validate_on_submit():
post = Post(title=form.title.data, body=form.body.data,
topic=form.topic.data)
db.session.add(post)
db.session.commit()
Topic.update_counts()
flash('Your post has been published.')
return redirect(url_for('display_post', url=url))
posts = Post.query.all()
return render_template('create_post.html', form=form, posts=posts)
模板
这里也没有什么花哨的。只要确保在模板中像处理基本字段那样渲染这个字段就可以了。因为WTForms的SQLAlchemy扩展会为你处理所有这些,不需要复杂的循环。{% extends "base.html" %}
{% block title %}Create/Edit New Post{% endblock %}
{% block content %}
<H3>Create/Edit Post</H3>
<form action="" method=post>
{{form.hidden_tag()}}
<dl>
<dt>Title:
<dd>{{ form.title }}
<dt>Post:
<dd>{{ form.body(cols="35", rows="20") }}
<dt>Topic:
<dd>{{ form.topic }}
</dl>
<p>
<input type=submit value="Publish">
</form>
{% endblock %}
就这样!现在我的文章表单有了一个主题的下拉列表。用你的术语来说,当你加载一笔交易时,该交易的默认类别会在下拉列表中高亮显示。正确的说法是,通过在交易模型中定义的关系加载与该交易相关的类别。
另外要注意,如果一笔交易有多个“默认”类别,还有一个多选的SQLAlchemy扩展可以使用。
现在,我的问题是如何处理多对多的关系……我想把存储在多对多表中的标签字符串传递到一个文本区域字段。对此没有SQLAlchemy扩展可用!
我在这里发布了那个问题 这里