Variable value in search box is lost when displaying records with pagination using Flask

Viewed 259

Problem description: I have a list of swimmer names stored in Sqlite db at the swimmer table. The list is displayed to the user when clicking on home menu.

enter image description here

When the swimmers option is selected from the menu the list of swimmers is displayed using pagination. You can browse the pages without problem using either the previous page or next page button; also you can even select a specific page and the records show correctly to the user.

enter image description here

On the page that displays the list of swimmers with pagination, at the bottom of the page is a search box and a button to activate the search by name.

enter image description here

For example, when entering the letter "A" in capital letters, the @app.route('/search_swimmers/') searches for all the records that include the letter "A" in the name field and returns this list with pagination. Up to here everything works perfect.

enter image description here

Results of the search for names that include the capital letter "A" in the name field:

enter image description here

When the search results are displayed on the screen. The Name field in the search box no longer includes the capital letter "A" which was the criteria entered for the search. However, all the records found are correct, because they satisfy the search criteria. The example shows a total of nine pages. For purposes of explaining the problem, a maximum number of three records per page has been adjusted. So far everything is correct in what the app is expected to do.

THE PROBLEM: When you want to navigate in the pages, the route @app.route('/search_swimmers/') has lost the value of the name_search variable. As a result, 0 records found are shown.

enter image description here

How could you prevent the value of variable name_search from not being lost?

Attached are the relevant code sections for your review:

app.py:

from flask import Flask 
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime  
from flask import request, redirect

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'  
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 
db = SQLAlchemy(app) 


class Swimmer(db.Model):
    id          = db.Column(db.Integer,     primary_key=True)
    name        = db.Column(db.String(80),  nullable=False)
    created_at  = db.Column(db.DateTime,    nullable=False, default=datetime.utcnow)

    def __repr__(self):
        return '<Swimmer %r>' % self.name


@app.route('/swimmers/<int:page_num>')
def swimmers(page_num):
    swimmers_per_page = 3
    swimmers = Swimmer.query.paginate(per_page=swimmers_per_page, 
                                    page=page_num, error_out=False)

    return render_template('swimmers.html',swimmers=swimmers)

@app.route('/search_swimmers/<int:page_num>')
def search_swimmers(page_num):

    '''
    Search
    '''

    swimmers_per_page = 3
    name_search = str(request.args.get('name'))


    swimmers = Swimmer.query.filter(
                    Swimmer.name.contains(name_search)
              ).order_by(
                    Swimmer.name
                  ).paginate(
                        per_page = swimmers_per_page, 
                        page = page_num,
                        error_out=False
                    )

    return render_template(
            'search_swimmers.html', 
            swimmers = swimmers,
            name_search = name_search
       )

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        name = request.form['name']
        new_stuff = Swimmer(name=name)

        try:
            db.session.add(new_stuff)
            db.session.commit()
            return redirect('/')
        except IntegrityError as e:
            db.session.rollback()
            return "There was a problem creating a swimmer."

    else:
        swimmers = Swimmer.query.order_by(Swimmer.created_at).all()
        return render_template('index.html', swimmers=swimmers)



if __name__ == '__main__':
    app.run(debug=True)

swimmers.html:

{% extends 'base.html' %}

{% block content %}
<div class="container">
  <h3>
    <span class="d-block p-2 bg-primary text-white">
      Swimmers list
    </span>
  </h3>
  <ul class="pagination">
    {% if swimmers.has_prev %}
      <li class="page-item"> <a class="page-link" href="{{ url_for('swimmers', page_num =   swimmers.prev_num ) }}">Previous page</a></li>
    {% else %}
      <li class="page-item"><a class="page-link btn disabled" href="#">Previous page</a></li>
    {% endif %}


    {% if swimmers.has_next %}
      <li class="page-item"> <a class="page-link" href="{{ url_for('swimmers', page_num = swimmers.next_num) }}">Next page</a></li>
    {% else %}
      <li class="page-item"><a class="page-link btn disabled" href="#">Next page</a></li>
    {% endif %}
  </ul>
  <p class="text-primary"> Page {{ swimmers.page }} of {{ swimmers.pages }}</p>

  {% for page in swimmers.iter_pages(left_edge=3, 
                                  right_edge=3, 
                                  left_current=3, 
                                  right_current=3
                                  ) 
  %}
  {% if page %}
    <a href="{{ url_for('swimmers',page_num=page) }}">{{page}}</a>
  {% else %}
  ... 
  {% endif %}
  {% endfor %}


  {% for swimmer in swimmers.items %}
  <div class="alert alert-primary" role="alert">
    {{ swimmer.name }}
  </div>
  {% endfor %}

  <p class="text-primary"> Page {{ swimmers.page }} of {{ swimmers.pages }}</p>
  {% for page in swimmers.iter_pages(left_edge=3, 
                                  right_edge=3, 
                                  left_current=3, 
                                  right_current=3
                                  ) 
  %}
  {% if page %}
    <a href="{{ url_for('swimmers',page_num=page) }}">{{page}}</a>
  {% else %}
  ... 
  {% endif %}


  {% endfor %}
  <div class="container">
    <form action="{{ url_for('search_swimmers',page_num=1) }}" method="get" class="form-inline my-2 my-lg-0">
      <input class="form-control mr-sm-2" type="text" name="name" placeholder="Name ... " oninvalid="this.setCustomValidity('Please Enter a valid Name')" aria-label="Search" required>
      <button class="btn btn-primary my-2 my-sm-0" type="submit"><svg class="bi bi-search" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" d="M10.442 10.442a1 1 0 011.415 0l3.85 3.85a1 1 0 01-1.414 1.415l-3.85-3.85a1 1 0 010-1.415z" clip-rule="evenodd"/>
        <path fill-rule="evenodd" d="M6.5 12a5.5 5.5 0 100-11 5.5 5.5 0 000 11zM13 6.5a6.5 6.5 0 11-13 0 6.5 6.5 0 0113 0z" clip-rule="evenodd"/>
      </svg></button>
    </form>
  </div>



</div>

{% endblock content %}

search_swimmers.html:

{% extends 'base.html' %}

{% block content %}
<div class="container">
  <h3>
    <span class="d-block p-2 bg-primary text-white">
      Search Swimmers Results
    </span>
  </h3>


  <p class="text-primary">Total pages: {{ swimmers.pages }}</p>
  {% for page in swimmers.iter_pages(left_edge=3, 
                                  right_edge=3, 
                                  left_current=3, 
                                  right_current=3
                                  ) 
  %}
  {% if page %}
    <a href="{{ url_for('search_swimmers',page_num=page) }}">{{page}}</a>
  {% else %}
  ... 
  {% endif %}
  {% endfor %}


  {% for swimmer in swimmers.items %}
  <div class="alert alert-primary" role="alert">
    {{ swimmer.name }}
  </div>
  {% endfor %}

  <p class="text-primary">Total pages: {{ swimmers.pages }}</p>
  {% for page in swimmers.iter_pages(left_edge=3, 
                                  right_edge=3, 
                                  left_current=3, 
                                  right_current=3
                                  ) 
  %}
  {% if page %}
    <a href="{{ url_for('search_swimmers',page_num=page) }}">{{page}}</a>
  {% else %}
  ... 
  {% endif %}
  {% endfor %}


  <div class="container">
    <form action="{{ url_for('search_swimmers',page_num=1) }}" method="get" class="form-inline my-2 my-lg-0">
      <input class="form-control mr-sm-2" type="text" name="name" placeholder="Name ... " aria-label="Search">
      <button class="btn btn-primary my-2 my-sm-0" type="submit"><svg class="bi bi-search" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
          <path fill-rule="evenodd" d="M10.442 10.442a1 1 0 011.415 0l3.85 3.85a1 1 0 01-1.414 1.415l-3.85-3.85a1 1 0 010-1.415z" clip-rule="evenodd"/>
          <path fill-rule="evenodd" d="M6.5 12a5.5 5.5 0 100-11 5.5 5.5 0 000 11zM13 6.5a6.5 6.5 0 11-13 0 6.5 6.5 0 0113 0z" clip-rule="evenodd"/>
        </svg></button>
    </form>
  </div>

</div>

{% endblock content %}

Any guide is highly appreciated. Thank you!

1 Answers

I believe the right solution for this is to put the search term in the URL.
The below modifications work in my testing with URLs like:
http://127.0.0.1:5000/search_swimmers/3/test

I modified your search_swimmers() route like this:

@app.route('/search_swimmers/<int:page_num>/')
@app.route('/search_swimmers/<int:page_num>/<search_term>')
def search_swimmers(page_num=None,search_term=None):
    swimmers_per_page = 3
    if not search_term:
        name_search = str(request.args.get('name'))
    else:
        name_search = search_term 

    swimmers = Swimmer.query.filter(
                    Swimmer.name.contains(name_search)
              ).order_by(
                    Swimmer.name
                  ).paginate(
                        per_page = swimmers_per_page, 
                        page = page_num,
                        error_out=False
                    )

    return render_template(
            'search_swimmers.html', 
            swimmers = swimmers,
            name_search = name_search, 
       )

And your search_swimmers.html like this:

{% extends 'base.html' %}

{% block content %}
<div class="container">
  <h3>
    <span class="d-block p-2 bg-primary text-white">
      Search Swimmers Results
    </span>
  </h3>


  <p class="text-primary">Total pages: {{ swimmers.pages }}</p>
  {% for page in swimmers.iter_pages(left_edge=3, 
                                  right_edge=3, 
                                  left_current=3, 
                                  right_current=3
                                  ) 
  %}
  {% if page %}
    <a href="{{ url_for('search_swimmers',page_num=page, search_term=name_search) }}">{{page}}</a>
  {% else %}

  {% endif %}
  {% endfor %}


  {% for swimmer in swimmers.items %}
  <div class="alert alert-primary" role="alert">
    {{ swimmer.name }}
  </div>
  {% endfor %}

  <p class="text-primary">Total pages: {{ swimmers.pages }}</p>
  {% for page in swimmers.iter_pages(left_edge=3, 
                                  right_edge=3, 
                                  left_current=3, 
                                  right_current=3
                                  ) 
  %}
      {% if page %}
        <a href="{{ url_for('search_swimmers',page_num=page, search_term=name_search) }}">{{page}}</a>
      {% else %}

      {% endif %}
  {% endfor %}


  <div class="container">
    <form action="{{ url_for('search_swimmers',page_num=1) }}" method="get" class="form-inline my-2 my-lg-0">
      <input class="form-control mr-sm-2" type="text" name="name" value="{{ search_term }}" aria-label="Search">
      <button class="btn btn-primary my-2 my-sm-0" type="submit"><svg class="bi bi-search" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
          <path fill-rule="evenodd" d="M10.442 10.442a1 1 0 011.415 0l3.85 3.85a1 1 0 01-1.414 1.415l-3.85-3.85a1 1 0 010-1.415z" clip-rule="evenodd"/>
          <path fill-rule="evenodd" d="M6.5 12a5.5 5.5 0 100-11 5.5 5.5 0 000 11zM13 6.5a6.5 6.5 0 11-13 0 6.5 6.5 0 0113 0z" clip-rule="evenodd"/>
        </svg></button>
    </form>
  </div>

</div>

{% endblock content %}
Related