Filtering and Searching
Learning Objectives
- You can distinguish a URL path from its query parameters.
- You can follow a title filter that leaves the source records unchanged.
- You can connect a GET form to a route that displays search results.
A list can display a selection rather than every record. Continue the web-app project from Chapter 5. This chapter adds select_tasks and replaces the list route and template; the detail page stays in place.
Add the selection function, replace the list route, and update the template before refreshing: the route needs the function and the template needs its context values.
Query Parameters
In /tasks?q=article, /tasks is the path and q=article is a query parameter. The same list route handles the request, with a different argument value.
For a route registered at @app.get("/tasks"), a parameter declared as q: str = "" takes its value from the query string: its name does not appear inside braces in the path. request: Request still receives the current request object. For /tasks?q=ARTICLE, q receives "ARTICLE". An omitted parameter uses its Python default "".
A Search Address
0 / 5 points
A browser requests:
http://127.0.0.1:8000/tasks?q=loopsThe matching GET route accepts a q query parameter as text.
Which pair gives the request’s path and the string received as q?
A selection operation builds a new result without removing records from the source collection. A later search can still find records that an earlier search left out. Read requests also leave the JSON file unchanged.
Two Task Lists
0 / 5 points
A route creates a separate list of records to display:
records = [ {"id": "T01", "title": "Read the article", "category": "reading"}, {"id": "T02", "title": "Practise loops", "category": "practice"}, {"id": "T03", "title": "Review notes", "category": "reading"},]selected = [records[0], records[2]]After the code runs, which pair gives the identifiers in selected and in records, in each list’s order?
Selecting Tasks by Title
The supplied filter below returns records whose titles contain the query. Add it to main.py, beside find_task and above the routes:
def select_tasks(records, q=""): query = q.lower() selected = [] for record in records: if query in record["title"].lower(): selected.append(record) return selectedlower() produces lowercase text for comparison without changing the stored title. The query "ARTICLE" therefore matches "Read the article". An empty string occurs in every string, so the default query accepts every title. The result contains complete records, including the identifiers needed for detail links.
Connect Selection to the List Route
Replace the existing list route in main.py with this complete version:
@app.get("/tasks")def show_tasks(request: Request, q: str = ""): records = read_records(DATA_FILE) context = { "tasks": select_tasks(records, q), "q": q, } return templates.TemplateResponse( request=request, name="tasks.html", context=context )The route passes q to select_tasks and gives its returned records to the template under tasks. The context also passes q to fill the search input below.
A GET Search Form
A search form saves visitors from editing addresses manually. Its method="get" sends the named input as a query parameter to the path in action. Replace templates/tasks.html with this complete document. Its list keeps the detail links:
<!doctype html><html lang="en"> <head> <meta charset="utf-8"> <title>Study tasks</title> </head> <body> <h1>Study tasks</h1> <form id="task-search" method="get" action="/tasks"> <label for="search-title">Title contains</label> <input id="search-title" name="q" value="{{ q }}"> <button type="submit">Search</button> </form> {% if tasks %} <ul id="task-list"> {% for task in tasks %} <li> <a href="/tasks/{{ task['id'] }}">{{ task["title"] }}</a> ({{ task["category"] }}) </li> {% endfor %} </ul> {% else %} <p>No tasks.</p> {% endif %} </body></html>The input named q supplies the route’s q parameter. id connects the input to its label. The quoted value displays the current query so visitors can edit it for another search.
Enter ARTICLE and submit the form. The address becomes /tasks?q=ARTICLE, the page displays Read the article, and the input still shows ARTICLE. Clear the input and search again to display all tasks.
If a search behaves unexpectedly, use Chapter 2’s print technique to inspect q and context["tasks"] inside show_tasks. Check that the input’s name agrees with the route parameter.
The next exercise supplies the title filter. Connect it to the GET route and the search field.
Connect the Search Page
0 / 25 points
Complete the marked GET route in main.py. The title filter and search form are supplied.
For GET /tasks?q=..., read the current records and call select_tasks(records, q). Pass the selected records under tasks and the search text under q to tasks.html. Searching leaves the JSON file unchanged.
You have practised listing records, following their stable links, and searching without changing them. The next unit uses forms to request changes through supplied application operations.
Check Your Understanding
- Why can
/tasks?q=articleand/tasks?q=loopsuse the same route function? - How does
select_tasksmakeARTICLEmatchRead the articlewithout changing the stored title? - What should the search page display after a visitor clears the search field and submits the form? Why?
- A search input has
id="search-title"andname="query", but the route expectsq. Why would the submitted text fail to reachq, and which input attribute needs to agree with the route parameter?