WTForms:从带有关系的SQLAlchemy字段设置默认值

1 投票
1 回答
2859 浏览
提问于 2025-04-18 02:38

在这里,有很多问题的标题听起来和我接下来要描述的很相似,但根据我花了几个小时的研究来看,这个问题是独一无二的。所以我就开始吧!

我正在写我的第一个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 个回答

3

我在评论中提到过这个。听起来你可以试试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扩展可用!

我在这里发布了那个问题 这里

撰写回答