Вопрос

I'm have a Flask project and am making forms that tie closly to SQLAlchemy models. In my MySQL database there is a table of Houses and a table of Garages. I want to use wtforms.ext.sqlalchemy.orm.model_form() to dynamically make my "Garage" form in the Controller code, but (here's the catch) add in a select field for the Foreign Keys to the "House" table. I assume QuerySelectField() is the way to go.

The rule: All new Garages have to have a parent House.

I am using the Flask-SQLAlchemy extension (flask.ext.sqlalchemy.SQLAlchemy) and the Flask-WTForms extension (flask.ext.wtf.Form) so the code looks a bit different than the examples found elsewhere on stackoverflow and in the respective documentations for Flask, SQLAlchemy and WTForms.

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
# let's use our models to make forms!
from flask.ext.wtf import Form
from wtforms.ext.sqlalchemy.orm import model_form, validators
from wtforms.ext.sqlalchemy.fields import QuerySelectField

app = Flask(__name__)
#setup DB connection with the flask-sqlalchemy plugin
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://user:pass@localhost/mydb'
db = SQLAlchemy(app)

Here are my models:

class House(db.Model):
    __tablename__ = 'House'
    Id = db.Column(db.Integer, primary_key=True)
    Name = db.Column(db.String(32))
    Description = db.Column(db.String(128))

    @staticmethod
    def get(houseId):
        return db.session.query(House).filter_by(Id=houseId).one()

def getAllHouses():
    return House.query

Here is my router for the page to add a Garage:

@app.route("/garage/add", methods=["GET", "POST"]) #add new garage to DB
def addGarage():
    MyForm = model_form(Garage, base_class=Form,exclude_fk=False)
    garage = Garage()
    form = MyForm(request.form, garage, csrf_enabled=False)

THIS IS THE LINE I'M UNSURE ABOUT:

form.ParentHouse_FK = QuerySelectField(u'Assign To', query_factory=getAllHouses, get_label="Name")


    if request.method == "GET":
        return render_template('addGarage.html', form=form)
    elif form.validate_on_submit():
        form.populate_obj(garage)
        db.session.add(garage)
        db.session.commit()
    else:
        return render_template("errors.html", form=form)
    return redirect("/garage")

My template looks like this:

<form method="POST" action="/garage/add">
    <legend>Add Garage</legend>
    <div>{{ form.ParentHouse_FK.label }}{{ form.ParentHouse_FK }}</div>
    <div>{{ form.Name.label }}{{ form.Name(placeholder="Name") }}</div>
    <div>{{ form.Description.label }}{{ form.Description(placeholder="Description") }}</div>

    <button type="submit" class="btn">Submit</button>
</form>

Note the last part of that 1st <div> has form.ParentHouse_FK without the () on purpose otherwise I get a AttributeError: 'UnboundField' object has no attribute '__call__' error. As it is, I still get an error: UnboundField(QuerySelectField, (u'Assign To',), {'get_label': 'Name', 'query_factory': <function getAllHouses at 0x29eaf50>})

My goal is to add in a Field to form to represent all the available Foreign Key possibilities to current House rows (and then populate Garage.ParentHouse_FK in the Garage table for the new Garage entry). I know I could ignore then (otherwise awesome) model_form shortcut and define all my Forms directly, but the Models of this project are likely to change over time and it would be great to just update the Model without having to update the Form code too. Ideally, I'd also have a for loop in the Jinja2 template to show all fields.

Any clues how to properly use QuerySelectField() along with model_form() to get what I'm after?

Thanks!

Это было полезно?

Решение

The problem is not QuerySelectField, it's that you're trying to add fields to an already instantiated form. This requires some special care. Here's what a field goes through when a form is instantiated, you could add your own field this way.

unbound_field = QuerySelectField(...)
form._unbound_fields.append(("internal_field", unbound_field))
bound_field = unbound_field.bind(form, "field_name", prefix=form._prefix, translations=form._get_translations())
form._fields["field_name"] = bound_field
form.field_name = bound_field

However, there is an easier way. Add the field to the form class before instantiating it, and this will happen automatically.

Form = model_form(...)
unbound_field = QuerySelectField(...)
Form.field_name = unbound_field
form = Form(...)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top