Reading Tag Data from Code
Learning Objectives
- You can explain when a route should use
fetchall()and when it should usefetchone(). - You can create a route and a page for showing one selected tag.
- You can modify a simple read query and predict how the visible tag page changes.
The previous chapter added the /tags page that lists all tags. This chapter adds a second read-only page that shows the newest tag.
Fetching Many Rows or One Row
When thinking of a page that shows data from the database, a useful question is: which kind of query result does the page need?
If the page should show a list, the route needs several rows. If the page should show one item, the route needs one row. The psycopg library has two methods for those cases:
fetchall()returns all rows produced by the query as a list of rows,- and
fetchone()returns one row from the query result, orNoneif there are no rows.
For example, the /tags page needs several rows, so it uses fetchall():
@app.get("/tags")def list_tags(request: Request): with get_connection() as conn: tags = conn.execute( """ SELECT id, name FROM tags ORDER BY created_at DESC, id DESC """ ).fetchall()
return render(request, "tags.html", tags=tags)If the question is “Which tags exist right now?”, this route answers it by selecting several rows and returning them as a list. If the route used fetchone() instead, only one row would be returned, and the page could not show the whole list.
The
psycopgdocumentation on cursor methods is a useful reference if you want to see these fetch methods in more detail.
Choosing fetchall
0 / 5 points
A route should show every tag that the query returns on the /tags page. Which finishing call is the best fit?
Finding the Newest Tag
Let us next build functionality for a page that shows the newest tag. The question is: which tag was created most recently?
To answer that, the query needs to order the tags by their creation time, with the newest tag first. That way, the newest tag is the first row in the query result. To get only that row, the query can use LIMIT 1, which keeps only the first row. Then, the route can use fetchone() to get that one row.
Add a new route to app/main.py for /tags/newest that implements that logic. The route should pass the tag to a new template called newest_tag.html, which shows the tag name:
@app.get("/tags/newest")def newest_tag(request: Request): with get_connection() as conn: tag = conn.execute( """ SELECT id, name FROM tags ORDER BY created_at DESC, id DESC LIMIT 1 """ ).fetchone()
return render(request, "newest_tag.html", tag=tag)Read the route from top to bottom:
@app.get("/tags/newest")says which request the function handles,ORDER BY created_at DESCmoves the newest rows to the top,id DESCis a useful tie-breaker if two rows have the same timestamp,LIMIT 1keeps only the first row,- and
fetchone()says the route expects one row rather than a whole list.
That combination is a common pattern for one-row pages:
- first decide which row should come first,
- then keep one row,
- then fetch one row.
One-row newest tag query
0 / 5 points
Which combination best matches a route whose job is to show only the newest tag?
A Template for One Row
The route above calls render(request, "newest_tag.html", tag=tag). This means that the template should expect one variable named tag, not a list named tags.
Create a new template file called newest_tag.html in the app/templates/ directory:
<!doctype html><html lang="en"> <head> <meta charset="utf-8" /> <title>Study Tracker</title> </head> <body> <h1>Study Tracker</h1> <h2>Newest Tag</h2>
{% if tag %} <p> <strong>{{ tag.name }}</strong> </p> {% else %} <p>No tags found.</p> {% endif %} </body></html>The page shows the tag name in bold if there is a tag, and shows a fallback message if there are no tags. The {% if tag %} check is important because if the tags table is empty, the query returns no rows and fetchone() returns None. The check makes sure the template handles that case safely and does not try to access tag.name when there is no tag.
This page is simpler than tags.html because it does not loop over several rows. It either shows one tag or, if the table is empty, a fallback message.
This also shows another practical difference between the two read pages:
tags.htmlneeds a list and a loop,newest_tag.htmlneeds one row and no loop.
Later in the part, we will extract the shared HTML boilerplate (
<html>,<head>, the top heading) into one common base template so that individual pages only contain their own content. Until then, each page keeps its own full HTML shell.
Check Your Understanding
- Why is
fetchall()a good fit for/tagsbutfetchone()a good fit for/tags/newest? - Which part of the
/tags/newestquery makes sure only one row is returned? - Why is the
{% if tag %}check useful innewest_tag.html?
Programming Exercise
This chapter’s programming exercise continues from the previous chapter’s finished /tags page. It asks you to add /tags/newest and its template, and then adjust the /tags list page so it shows the same rows in alphabetical order.
Showing the newest tag
0 / 15 points
Continuing on the project from the previous chapter, add a new page to /tags/newest that shows the name of the newest tag from the database. If there is no tag in the database, the page should show the text No tags found..
Then, update the existing /tags page so that it shows the tags in alphabetical order instead of sorting the tags by creation time.
Once ready, zip the project and return it. The root of the zip must contain:
requirements.txt- the folder
app - the folder
migrations
The grader checks the following:
- The application starts successfully from a clean database.
- The
/tagspage shows the seeded tags in alphabetical order. - The
/tags/newestpage shows the newest tag. - The pages respond correctly when a new tag row is inserted into the database.