Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python

Is there a clean way to update and pass request.args to url_for() in Flask Jinja2?

I have created a flask app which retrieves results from a database and lists them on a page. Then I added pagination and filtering options.

My pagination design isn't that great and.. I have buttons on my template for next, previous, n-th page. I get the page number and the filtering options from the request.args.

When I am on /?page=2&search=something I want to create a button that when clicked it updates the url to /?page=3&search=something so far I have come up with this:

                <li>
                    <a href="{{ url_for('scoreboard.index', page=pagination[2] + 1,
                             result_name=request.args.get('result_name')) }}" aria-label="Next">
                        <span aria-hidden="true">&raquo;</span>
                    </a>
                <li>

pagination[2] is the current page, it is passed via the controller and I'm using result_name=request.args.get('result_name') in order to persist the filters.

Is this okay?

1 Answer

Denis Nutiu,

An asynchronous solution, which I am not sure whether is clean or not, will be to have the "next" or "previous" button redirect to the current + 1 page.

I don't know the implementation of your current routing is, but I would have something like:

@app.route("/index")
def index():

    try:
        next = request.args.get('next')
    expect Exception:  # Don't remember what exception it throws if can't get the argument. 
        # No next 'request', do regular stuffs
        # return render_template for regular index... 
    else:
        # Generate stuffs for the 'next' page
        # return render_template for next index...
<button onclick="getNextPage({{ currentIndex + 1 }})">Next</button>

<script>
    function getNextPage(nextIndex) {
        var uri = "/index/?next=" + nextIndex
        window.location.replace(uri);  
    }
</script>

If my solution is not beautiful for your eyes (btw I dont like it myself), check the suggested approach from the Flask's snippets collections.

http://flask.pocoo.org/snippets/44/

They basically make an Pagination object, which you can copy and page, that guy you a few functionalities. -Dan

Thank you for your reply! I google pagination before implementing it but I wanted to do my own thing, it's not that good and it looks awful.

Here's my controller:

@scoreboard.route("/", methods=['GET', 'POST'])
def index():
    """
    This method returns the index page.
    """
    results_per_page = flask.current_app.config["MAX_RESULTS_PER_PAGE"]
    max_pages = flask.current_app.config["MAX_PAGES"]

    # We're extracting the page argument from the url, if it's not present we set page_no to zero.
    page_no = utilities.to_zero_count(flask.request.args.get('page'))
    searched_name = flask.request.args.get('result_name')

    # The filters dictionary is used to filter the data
    filters = {}
    if searched_name is not None and searched_name is not '':
        filters['name'] = searched_name

    # Computing the offset for the results
    offset = page_no * results_per_page

    # We're getting the results length and data
    results_length = Result.query.filter_by(**filters).count()
    results = Result.query.filter_by(**filters).order_by(Result.score.desc()).offset(offset).limit(results_per_page)

    # This is used by the view to display available pages, if any.
    available_pages = math.floor((results_length - offset) / results_per_page)

    # Compute the available pages to the left
    pages_left = min(page_no, max_pages)
    # Compute the available pages to the right
    pages_right = min(max_pages, available_pages)
    # Create pagination information tuple
    pagination_information = results_length, results_per_page, page_no + 1, pages_left, pages_right

    return flask.render_template("index.html",
                                 results=results,
                                 pagination=pagination_information)

And here's my index.html

{% extends "root.html" %}

{% block main %}
{{ super() }}
<div class="panel panel-default">
    <!-- Default panel contents -->
    <div class="panel-heading"><span class="glyphicon glyphicon-globe"></span> Scoreboard</div>
    <div class="panel-body">
        <p>The results are send here via JSON from the java app, there are currently {{ pagination[0] }} results in the database.</p>
    </div>

  <!-- Table -->
    <table class="table">
        <thead>
            <tr>
                <th>GPU</th>
                <th>CPU</th>
                <th>Score</th>
                <th>Details</th>
            </tr>
        </thead>
        <tbody>
            {% for item in results %}
            <tr>
                <td>{{ item.gpu }}</td>
                <td>{{ item.cpu }}</td>
                <td>{{ item.score }}</td>
                <td><a href="{{ url_for('scoreboard.result', id=item.id) }}">See more</a></td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    {# [0] -> Results Lenght, [1] -> Max Items Per Page, [2] -> Current Page, [3] -> Pages Left, [4] -> Pages Right #}
    {% if pagination[0] > pagination[1] %}
    <nav aria-label="Page navigation">
        <div class="text-center">
            <ul class="pagination">
                {% if pagination[2] <= 1 %}
                <li class="disabled">
                    <span aria-hidden="true">&laquo;</span>
                {% else %}
                <li>
                    <a href="{{ url_for('scoreboard.index', page=pagination[2] - 1,
                             result_name=request.args.get('result_name')) }}" aria-label="Previous">
                        <span aria-hidden="true">&laquo;</span>
                    </a>
                </li>
                {% endif %}
                {# Things are kinda messy here #}
                {% for i in range(pagination[2] - pagination[3] - 1, pagination[2] - 1) %}
                    <li><a href="{{ url_for('scoreboard.index', page=i + 1,
                                 result_name=request.args.get('result_name')) }}">{{ i + 1 }}</a></li>
                {% endfor %}
                <li class="active"><a href="{{ url_for('scoreboard.index', page=pagination[2],
                                            result_name=request.args.get('result_name')) }}">{{ pagination[2] }}</a></li>
                {% for i in range(pagination[2], pagination[2] + pagination[4]) %}
                    <li><a href="{{ url_for('scoreboard.index', page=i + 1,
                                 result_name=request.args.get('result_name')) }}">{{ i + 1 }}</a></li>
                {% endfor %}
                {% if pagination[4] <= 0 %}
                <li class="disabled">
                        <span aria-hidden="true">&raquo;</span>
                {% else %}
                <li>
                    <a href="{{ url_for('scoreboard.index', page=pagination[2] + 1,
                             result_name=request.args.get('result_name')) }}" aria-label="Next">
                        <span aria-hidden="true">&raquo;</span>
                    </a>
                {% endif %}
                </li>
            </ul>
        </div>
    </nav>
    {% elif pagination[0] == 0 %}
    <div class="text-center">
        <h2>No results found :(</h2>
    </div>
    {% endif %}
</div>
{% endblock %}