Pagination
It is often impractical for an application to deal with a very large number of results. For this reason, APIs and web services use pagination controls to allow applications to request the results in small chunks or pages.
You may have noticed that Elasticsearch by default does not return more than 10 results. The optional size
parameter can be given in a search request to change this maximum. The following example asks for up to 5 search results to be returned:
results = es.search(
query={
'multi_match': {
'query': query,
'fields': ['name', 'summary', 'content'],
}
}, size=5
)
To access additional pages of results, the from_
parameter is used, which indicates from where in the complete list of results to start (since from
is a reserved keyword in Python, from_
is used).
The next example retrieves a second page of 5 results:
results = es.search(
query={
'multi_match': {
'query': query,
'fields': ['name', 'summary', 'content'],
}
}, size=5, from_=5
)
Let's incorporate size
and from_
into the handle_search()
endpoint in app.py:
@app.post('/')
def handle_search():
query = request.form.get('query', '')
from_ = request.form.get('from_', type=int, default=0)
results = es.search(
query={
'multi_match': {
'query': query,
'fields': ['name', 'summary', 'content'],
}
}, size=5, from_=from_
)
return render_template('index.html', results=results['hits']['hits'],
query=query, from_=from_,
total=results['hits']['total']['value'])
Here the page size is now hardcoded to 5 (feel free to use any other number that you like). The from_
argument is assumed to be given as an additional field in the submitted form, but this field is considered optional, defaulting to 0 when not present.
The search form that is available in index.html does not have a from_
field, so regular searches will always start from the first result. The template displays information about the range of results that shown, and what the total is. Here is how this is done using template expressions:
<div class="col-sm-auto my-auto">
Showing results {{ from_ + 1 }}-{{ from_ + results|length }} out of {{ total }}.
</div>
The template also includes logic to display pagination buttons to move forwards or backwards in the list of results. Here is the implementation of the "Previous results" button:
{% if from_ > 0 %}
<div class="col-sm-auto my-auto">
<a href="javascript:history.back(1)" class="btn btn-primary">← Previous page</a>
</div>
{% endif %}
As you can see, the "Previous page" button is only rendered to the page when from_
is greater than zero. The implementation of this button uses the browser's history API to go back one page.
The "Next page" button has a much more interesting implementation:
{% if from_ + results|length < total %}
<div class="col-sm-auto my-auto">
<form method="POST">
<input type="hidden" name="query" value="{{ query }}">
<input type="hidden" name="from_" value="{{ from_ + results|length }}">
<button type="submit" class="btn btn-primary">Next page →</button>
</form>
</div>
{% endif %}
This button isn't actually a standalone button, but a complete form that has two hidden fields in addition to the button. The form is similar to the main search form, but includes the optional from_
field, adjusted to point to the next page of results. When this button is clicked, the Flask application will receive a search request from this alternate form, which uses the same text query but a non-zero from_
value.
With this small and clever implementation of pagination you will be able to navigate through multiple pages of results.