Sorting in Flask via Jinja template variable issue when called

Issue

I have an HTML drop-down form that isn’t working, so I just changed it to buttons to test (I am very new to Flask and python in general so I am experimenting).

I am trying to give users the choice to sort posts by time added, names etc. At the moment I am just experimenting with these three and they’re in button form (will be in drop-down when I work out how):

<form action="{{ url_for('filters') }}" name="sort" method="POST">
        <button type="submit" id="latest" class="btn">Latest</button>
        <button type="submit" id="oldest" class="btn">Oldest</button>
        <button type="submit" id="names" class="btn">Name</button>
</form>

This is all on a page called: questions.html

The filters app is:
@app.route("/filters", methods=["GET", "POST"])

def filters():
    sort = request.form.get("sort")
    if sort == "oldest":
        questions = mongo.db.questions.find().sort("added_on", 1)
    if sort == "latest":
        questions = mongo.db.questions.find().sort("added_on", -1)
    if sort == "names":
        questions = mondo.db.questions.find().sort("created_by", 1)           
    
    return render_template("questions.html", questions=questions)

I am already calling questions and I have them all displaying properly via:

@app.route("/get_questions")
def get_questions():
    questions = mongo.db.questions.find().sort("added_on", -1)
    return render_template("questions.html", questions=questions)

But when I try calling the sort with the button I get this error:

UnboundLocalError: local variable 'questions' referenced before assignment

It’s calling it to line 40 which is: return render_template("questions.html", questions=questions) in the filters function.

So, what is it I am doing wrong and what is it I need to do to get the user sort options into a drop-down so they function?

Thanks kindly 🙂

Solution

HTML forms don’t have a name attribute, so you can remove that attribute. What you likely want to do is add a name and a value attribute to your buttons, as the name-value pair is what gets sent to the server when the form is submitted.

Later on, when you’ve verified that the code works, you can swap the buttons back out for the dropdown select input like you were planning to initially. For buttons, it’s something like:

<form action="{{ url_for('filters') }}" method="POST">
    <button type="submit" name="sort" value="latest" class="btn">Latest</button>
    <button type="submit" name="sort" value="oldest" class="btn">Oldest</button>
    <button type="submit" name="sort" value="names" class="btn">Name</button>
</form>

For a select based dropdown it’d be something like:

<form action="{{ url_for('filters') }}" method="POST">
    <select name="sort">
        <option value="latest">Latest</option>
        <option value="oldest">Oldest</option>
        <option value="names">By name</option>
    </select>
    <button type="submit">Apply filter</button>
</form>

The second part of the issue — the UnboundLocalError — is because none of the tests for checking the sort variable yield a True value, therefore the questions variable is undefined, in other words, unbound. You could, for example, have a default value for sort if none is provided in the request, or use a different if structure, with if, elif and else, with the else acting as the default. Something like:

def filters():
    sort = request.form.get("sort")

    if sort == "oldest":
        questions = mongo.db.questions.find().sort("added_on", 1)
    elif sort == "latest":
        questions = mongo.db.questions.find().sort("added_on", -1)
    elif sort == "names":
        questions = mondo.db.questions.find().sort("created_by", 1)
    else:
        # if `sort` is none of the above, default to showing the latest questions
        questions = mongo.db.questions.find().sort("added_on", -1)         
    
        return render_template("questions.html", questions=questions)

Edit

My answer about the UnboundLocalError remains the same, but as I re-read your question I realised there is a cleaner way to do this. The filters should likely be handled by query string parameters, not form data. Doing it with query string params means that the URL will be shareable along with the filter applied.

The general idea is that a single route handles showing the questions as well as handling the application of the filter. You could try something like:

@app.route("/questions")
def questions():
    sort = request.args.get("sort")
    if sort == "oldest":
        questions = mongo.db.questions.find().sort("added_on", 1)
    elif sort == "latest":
        questions = mongo.db.questions.find().sort("added_on", -1)
    elif sort == "names":
        questions = mondo.db.questions.find().sort("created_by", 1)
    else:
        # if `sort` is none of the above, default to showing the latest questions
        questions = mongo.db.questions.find().sort("added_on", -1) 
    return render_template("questions.html", questions=questions)

And the filter buttons would look like this. When the for is submitted, the filter is added as a query param in the URL.

<form action="{{ url_for('questions') }}" method="GET">
    <button type="submit" name="sort" value="latest" class="btn">Latest</button>
    <button type="submit" name="sort" value="oldest" class="btn">Oldest</button>
    <button type="submit" name="sort" value="names" class="btn">Name</button>
</form>

Answered By – Karl Sutt

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published