Search code examples
pythonflasksqlalchemyflask-sqlalchemyflask-wtforms

How to properly give sql-alchemy query with relations to WTforms?


I have a problem that I'm working on several days.

I have a Flask app and in some place I want to edit some data in my DB. Data model I want to edit is similar to(using Flask-SQLAlchemy):

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(64))
    last_name = db.Column(db.String(64))
    address = db.relationship('Address', backref='user', lazy='dynamic')

class Address(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    street = db.Column(db.String(64))
    region = db.Column(db.String(64))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

Having defined model, I designed next form:

    class AddUser(FlaskForm):
        first_name = StringField("first_name", validators=[ InputRequired()])
        last_name = StringField("last_name", validators=[ InputRequired()])
        region = StringField("region", validators=[ InputRequired()])

In Flask, edit function looks like this:

@app.route('/edit_user/<int:id>', methods=['GET', 'POST'])
def edit_user(id):
    qry = User.query.filter_by(id=id).first()

    if qry:
        form = AddUser(obj=qry)
        if request.method == 'POST' and form.validate():
            # save edits
            qry.first_name = form.first_name .data
            qry.last_name = form.last_name.data
            qry.address.region = form.region.data
            db.session.commit()
            flash('Socnet updated successfully!')
            return redirect(url_for("view_record", user=form.id.data, level='overview'))
        return render_template('add_user.html', form=form, id=id)
    else:
        return 'Error loading #{id}'.format(id=id)

Unfortunately, the form looks like

First name: My_name
Last name: My_lastname
Region: [<Address 1>]

So it looks like WTForms doesn't know what to do with relationships. If I write the template manually, I will use it like qry.adrress.region and it would work without any problems, but I really don't want to build the forms by myself.

This case is not unique. The same issues arises with a table autogeneration module.

I know I miss something valuable. Please, help.


Solution

  • as an easy solution, if the user can have only one address, change the relationship to one-to-one or modify your user model to include the address

    if you want to use one-to-one: just change the relationship line as follows:

    address = db.relationship('Address', backref='user', useList=False)
    

    or one model for both user and address

    class User(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        first_name = db.Column(db.String(64))
        last_name = db.Column(db.String(64))
        street = db.Column(db.String(64))
        region = db.Column(db.String(64))
    

    then you form will be as follows:

    class AddUser(FlaskForm):
        first_name = StringField("first_name", validators=[ InputRequired()])
        last_name = StringField("last_name", validators=[ InputRequired()])
        street = StringField("street", validators=[ InputRequired()])
        region = StringField("region", validators=[ InputRequired()])
    

    then flask route

    @app.route('/edit_user/<int:id>', methods=['GET', 'POST'])
    def edit_user(id):
        qry = User.query.filter_by(id=id).first()
    
        if qry:
            form = AddUser(obj=qry)
            if request.method == 'POST' and form.validate():
                # save edits
                qry.first_name = form.first_name .data
                qry.last_name = form.last_name.data
                qry.street = form.street.data
                qry.region = form.region.data
                db.session.commit()
                flash('Socnet updated successfully!')
                return redirect(url_for("view_record", user=form.id.data, level='overview'))
            return render_template('add_user.html', form=form, id=id)
        else:
            return 'Error loading #{id}'.format(id=id)