Runner Bean
Runner Bean

Reputation: 5185

Python wtforms QuerySelectField: Returning something else than the primary key entry

Im using Flask to make a web app.

In a Nutshell: When we submit a form which uses QuerySelectField + sqlalchemy query to populate a select box, the actual value being submitted seems to be the primary key, but what if I need to submit another value? Say a table has two columns, ID and Item, now ID is the primary key, but the user selects the row not by primary key ID but by Item, Thats what they see the Item names, not the primary key ID's. But the form submits the ID's, I want t submit the item names instead.

Im finding this hard to explain so heres the long version

Long explanation

I have a model which makes the table nutrionalvalues which has two columns, the 'id' which is the primary key and the 'item' column which holds names of food types.

class nutritionalvalues(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    item = db.Column(db.String(200), nullable=False)

    def __repr__(self):
        return '{}'.format(self.item)

Now using sqlalchemy and flaskforms

def choice_query():
    return nutritionalvalues.query

class ChoiceForm(FlaskForm):
    item = QuerySelectField(query_factory=choice_query, allow_blank=False)

I make the class 'ChoiceForm' and use this to make a form with

form = ChoiceForm()
return render_template('index.html', form=form)

In the index.html template I have the markup

    <form action="/" method="POST">
        {{ form.csrf_token }}
        {{ form.item }}
        <input type="submit" value="Add Item">
    </form>

which when rendered the browser produces the html code

<form action="/" method="POST">
    <select id="item" name="item">
        <option value="1">&lt;nutritionalvalues 1&gt;</option>
        <option value="2">&lt;nutritionalvalues 2&gt;</option>
        <option value="3">&lt;nutritionalvalues 3&gt;</option>
    </select>
    <input type="submit" value="Add Item">
 </form>

Now say I use the 'get_label' argument, I can change the select options to the actual items

class ChoiceForm(FlaskForm):
    item = QuerySelectField(query_factory=choice_query, allow_blank=False, get_label='item')

so the html renders like this

<form action="/" method="POST">
    <select id="item" name="item">
        <option value="1">bread</option>
        <option value="2">salmon</option>
        <option value="3">pork</option>
    </select>
    <input type="submit" value="Add Item">
 </form>

but when submitting the form the select box returns '1' for 'bread', but I want it to return 'bread'

because I then use this to enter this data into another table as follows

if request.method == 'POST':
    food_item = request.form['item']
    new_food = eaten(item=food_item)

    try:
        db.session.add(new_food)
        db.session.commit()
        return redirect('/')

and I don't want to enter '1' as my food item I just ate, I want to enter 'bread'.

So how can I use the QuerySelectField + sqlalchemy query toolkit to pass the actual item name back when the form is submitted?

Upvotes: 0

Views: 802

Answers (1)

SuperShoot
SuperShoot

Reputation: 10871

The answer is very similar to what you are already doing with get_label, where get_label either accepts a string attribute name, or a one-argument callable that the objects are passed to in order to derive the label.

From the documentation:

For the most part, the primary keys will be auto-detected from the model, alternately pass a one-argument callable to get_pk which can return a unique comparable key.

Somewhat inconsistently, the get_pk argument doesn't accept a string attribute name, only a one-argument callable, but essentially does the same thing (your string passed to get_label gets turned into a one-argument callable using operator.attrgetter() [source]). So you can control the field of your object that is used to identify the selected option by passing a callable to get_pk, e.g., get_pk=operator.attrgetter("item"), or get_pk=lambda x: x.item will save you an import.

You may want to put a unique constraint on to nutritionalvalues.item if you intend on using it in this manner to ensure no bugs creep in if someone adds an item with a name that already exists.

Upvotes: 2

Related Questions