如何将 SQLAlchemy 表单 "db.session.commit()" 修复为错误的 parent object?

How to fix SQLAlchemy form "db.session.commit()" to the wrong parent object?

我的 SQLAlchemy 表单将 db.session.commit() 提交给 .first() parent?

我有一个“one-to-many”的情况,我可以将新的 child 添加到特定的 parent object,但是由于某些原因,当我提交更改时child,它会自动提交到 .first() parent。不确定我是如何陷入这种情况的,我认为这只是我代码中某处的错误输入。我想提交对

的更改

如何将 db.session.commit() 提交到正确的 parent?

app.py

# Parent
class botList(db.Model):
    __tablename__ = 'botlist'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(200), nullable=False)
    channel = db.Column(db.String(200), nullable=False)
    bots = db.Column(db.Integer, nullable=False)
    status = db.Column(db.String(200), nullable=False)
    igUsername = db.Column(db.String(200), nullable=True)
    igPassword = db.Column(db.String(200), nullable=True)
    ytUsername = db.Column(db.String(200), nullable=True)
    ytPassword = db.Column(db.String(200), nullable=True)
    scrapingAccounts = db.relationship("scrapingAccount", backref="owner", lazy='dynamic')

    def __repr__(self):
        return '<Username %r>' % self.id

# Child
class scrapingAccount(db.Model):
    __tablename__ = 'scrapingaccount'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False)
    username = db.Column(db.String(200), nullable=False)
    owner_id = db.Column(db.Integer, db.ForeignKey("botlist.id"))


@app.route('/update/<int:id>', methods=['GET', 'POST'])
def updateBot(id):
    bot_to_update = botList.query.get_or_404(id)
    if request.method == "POST":
        if request.form.get("updateBotButton"):
            bot_to_update.username = request.form['username']
            bot_to_update.channel = request.form['channel']
            bot_to_update.bots = request.form['bots']
            bot_to_update.status = request.form['status']
            bot_to_update.igUsername = request.form['igUsername']
            bot_to_update.igPassword = request.form['igPassword']
            bot_to_update.ytUsername = request.form['ytUsername']
            bot_to_update.ytPassword = request.form['ytPassword']
            try:
                db.session.commit()
                return redirect('#')
            except:
                return "There was a problem updating that bot."

        elif request.form.get("addAccountButton"):
            if request.method == "POST":
                name = request.form['addigname']
                username = request.form['addiguser']
                new_account = scrapingAccount(name=name, username=username)
                try:
                    db.session.add(new_account)
                    db.session.commit()
                    return redirect('#')
                except:
                    return "There was a problem adding an account."
            else:
                return redirect('#')

        elif request.form.get("updateAccountButton"):
            if request.method == "POST":
                account = scrapingAccount.query.filter_by(owner_id=bot_to_update.id, id=request.form['accountid']).first_or_404()
                account.name = request.form['igname']
                account.username = request.form['iguser']
                try:
                    db.session.commit()
                    return redirect('#')
                except:
                    return "There was a problem updating an account."
            else:
                return redirect('#')
    else:
        return render_template("update.html", bot=bot_to_update)

update.html

{% for account in bot.scrapingAccounts %}
<form action="/update/{{bot.id}}" method="POST">
    <input type="hidden" name="accountid" value="{{ account.id }}"/>
    <input type="text" name="igname" id="igname" placeholder="eg. Jane Doe" value="{{account.name}}"/>
    <input type="text" name="iguser" id="iguser" placeholder="eg. jandoe" value="{{account.username}}"/>
    <input type="submit" name="updateAccountButton" value="Update">
</form>
{% endfor %}

您正在通过 id 列查询 table scrapingaccount,并将可能不相关的帐户返回到您的机器人列表。您要按 owner_id 列查询 scrapingaccount

这一行:

accounts = scrapingAccount.query.get_or_404(id)

应该是:

accounts = db.session.query(scrapingAccount).filter(scrapingAccount.owner_id == id).all()

编辑:我假设您函数中的 id 是 BotList table.

id

我同意前面一位演讲者 (@PGHE) 的观点。
这不是 session。 原因是您对“scrapingAccounts”的查询。
查询基于在 URL 中传递的 id。但是,这属于BotList。

<form action="/update/{{bot.id}}" method="POST">

但是,您在这里申请一个帐户。

accounts = scrapingAccount.query.get_or_404(id)

最明智的做法是不要将所有内容都写在同一个路由中,而是将任务分配给多个。这不仅改进了您的应用程序的结构,而且使它更加清晰。
CRUD 范式可以作为更好结构的指南。使用 blueprints 可以使 这更容易。


以下方法是一种 hacky 的快速修复方法。

# Parent
class botList(db.Model):
    __tablename__ = 'botlist'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(200), nullable=False)
    channel = db.Column(db.String(200), nullable=False)
    bots = db.Column(db.Integer, nullable=False)
    status = db.Column(db.String(200), nullable=False)
    igUsername = db.Column(db.String(200), nullable=True)
    igPassword = db.Column(db.String(200), nullable=True)
    ytUsername = db.Column(db.String(200), nullable=True)
    ytPassword = db.Column(db.String(200), nullable=True)
    scrapingAccounts = db.relationship("scrapingAccount", 
        backref="owner", 
        lazy='dynamic', 
        # Make sure that the referenced child objects are deleted when the 
        # parent object is deleted.
        cascade="all, delete-orphan"
    )

    def __repr__(self):
        return '<Username %r>' % self.id

# Child
class scrapingAccount(db.Model):
    __tablename__ = 'scrapingaccount'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False)
    username = db.Column(db.String(200), nullable=False)
    # To avoid unassigned accounts, you can forbid empty entries for the foreign key.
    # An error will be thrown if you try to set it to null / none.
    owner_id = db.Column(db.Integer, db.ForeignKey("botlist.id"), nullable=False)


@app.route('/update/<int:id>', methods=['GET', 'POST'])
def updateBot(id):
    bot_to_update = botList.query.get_or_404(id)
    if request.method == "POST":
        # If it is a POST request.
        if request.form.get("updateBotButton"):
            bot_to_update.username = request.form['username']
            bot_to_update.channel = request.form['channel']
            bot_to_update.bots = request.form['bots']
            bot_to_update.status = request.form['status']
            bot_to_update.igUsername = request.form['igUsername']
            bot_to_update.igPassword = request.form['igPassword']
            bot_to_update.ytUsername = request.form['ytUsername']
            bot_to_update.ytPassword = request.form['ytPassword']
            try:
                db.session.commit()
                return redirect('#')
            except:
                return "There was a problem updating that bot."

        elif request.form.get("addAccountButton"):
            name = request.form['addigname']
            username = request.form['addiguser']
            # You want to add an account to the bot here, so you have to set the
            # reference, otherwise it will be saved, but it does not belong anywhere.
            new_account = scrapingAccount(
                name=name,
                username=username,
                owner_id=bot_to_update.id
            )
            db.session.add(new_account)
            # Instead of assigning the associated bot id to the new account and
            # adding it to the session, you can also add it to the scrapingAccounts
            # list. Then you don't need to add it to the session as the parent is 
            # already part of it. The owner_id is assigned automatically.
            #
            # bot_to_update.scrapingAccounts.add(new_account)
            try:
                db.session.commit()
                return redirect('#')
            except:
                return "There was a problem adding an account."

        elif request.form.get("updateAccountButton"):
            # Request the account using its id and owner_id.
            account = scrapingAccount.query.filter_by(
                owner_id=bot_to_update.id,
                id=request.form['accountid']
            ).first_or_404()
            account.name = request.form['igname']
            account.username = request.form['iguser']
            try:
                db.session.commit()
                return redirect('#')
            except:
                return "There was a problem updating an account."
    else:
        return render_template("update.html", bot=bot_to_update)
{% for account in bot.scrapingAccounts %}
<form action="/update/{{bot.id}}" method="POST">
    <input type="hidden" name="accountid" value="{{ account.id }}"/>
    <input type="text" name="igname" id="igname" placeholder="eg. Jane Doe" value="{{account.name}}"/>
    <input type="text" name="iguser" id="iguser" placeholder="eg. jandoe" value="{{account.username}}"/>
    <input type="submit" name="updateAccountButton" value="Update accounts">
</form>
{% endfor %}

另一种选择是在路由中使用可选参数 / URL.