Charlie: Week 5: Make the Rating Form Work
Your app is in solid shape. You've got a home page, a rating form with fields for title, type, rating, and notes, and a ratings page that displays cards from your my_ratings list. Three routes, clean Tailwind styling, real data structure. That's ahead of the pack.
The missing piece: the form doesn't actually do anything yet. The submit button says "UI only" and clicking it goes nowhere. This week you fix that - the form will POST data to your app, Python will read it, append it to your list, and redirect you to the ratings page where your new entry shows up immediately.
Your v0 prototype for reference: https://v0.app/chat/movie-and-album-ratings-gHlvwCzKPL5?ref=WBY3CM
Note: Ratings you add will disappear when the app restarts. That's normal for now - you're storing data in a Python list in memory. Saving to a file or database comes later.
Check In: What Are You Working On?
Open TODO.md and update it with a rough plan for today. Something like:
Make the rate form submit data and show it on the ratings page.
Part 1: Essential Catch-up
You're caught up. Nice work.
Part 2: Python Practice
Create a new file called practice.py. Try this before working on your app.
Challenge: Build a "Movie Ranking Report" that stores ratings for 3-4 movies, calculates the average rating, finds the highest-rated movie, and prints a formatted leaderboard.
Your program should:
- Store at least 3 movie titles as variables (e.g.
movie_1 = "Forrest Gump") - Store a numeric rating (1-10) for each movie
- Calculate the average rating across all movies
- Figure out which movie has the highest rating (you can do this manually - just compare and store the winner)
- Print a formatted leaderboard showing rank, title, and rating, plus a summary line with the average
Example output (yours will have different data):
╔══════════════════════════════════════════╗
Movie Ranking Report
──────────────────────────────────────────
#1 The Dark Knight 9.5 / 10
#2 Forrest Gump 8.5 / 10
#3 Get Out 7.5 / 10
──────────────────────────────────────────
Average Rating: 8.5 / 10
Top Pick: The Dark Knight
╚══════════════════════════════════════════╝
Hints:
- Store each movie and rating as separate variables:
movie_1 = "The Dark Knight",rating_1 = 9.5 - Calculate the average:
average = (rating_1 + rating_2 + rating_3) / 3 - Use f-strings with padding to line things up:
print(f" #1 {movie_1:<24}{rating_1} / 10") - The
<24inside the f-string means "left-align and pad to 24 characters" - it keeps your columns neat
Need a refresher?
-
Variables - how to store text and numbers
-
Print Statements - how to display output, use f-strings, and format cards
Part 3: Make the Rating Form Submit Data
Right now your rate form has type="button" - it looks like a form but doesn't send anything. You're going to change three things: make the form submit, write Python to receive the data, and redirect to the ratings page.
Step 1: Update the imports
Open app.py. Find the import line at the top:
from flask import Flask
Change it to:
from flask import Flask, request, redirect
-
requestlets you read form data that the browser sends -
redirectsends the user to a different page after submitting
Step 2: Change the form to submit
Find the <form> tag in your /rate route. It currently looks like:
<form class="mt-8 space-y-4">
Change it to:
<form class="mt-8 space-y-4" method="post" action="/rate">
method="post"tells the browser to send the form data to the server (instead of just sitting there)action="/rate"tells it which route to send the data to
Then find the submit button:
<button class="rounded-lg bg-amber-400 px-6 py-2 font-semibold text-violet-950 hover:bg-amber-300" type="button">Submit Rating (UI only for now)</button>
Change type="button" to type="submit" and update the text:
<button class="rounded-lg bg-amber-400 px-6 py-2 font-semibold text-violet-950 hover:bg-amber-300" type="submit">Submit Rating</button>
Step 3: Add a POST route
Your current /rate route only handles GET requests (loading the page). You need to add handling for POST requests (receiving form data).
Find your @app.route("/rate") decorator and change it to:
@app.route("/rate", methods=["GET", "POST"])
Then update the rate() function to handle both cases. Replace the entire function with:
@app.route("/rate", methods=["GET", "POST"])
def rate():
if request.method == "POST":
title = request.form.get("title", "")
media_type = request.form.get("media_type", "Movie")
rating = request.form.get("rating", "5")
notes = request.form.get("notes", "")
my_ratings.append({
"title": title,
"type": media_type,
"rating": float(rating),
"notes": notes,
})
return redirect("/ratings")
return """
<html>
<head>
<meta charset="utf-8">
<title>Rate Something</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-violet-950 text-violet-50">
<div class="mx-auto max-w-2xl px-6 py-12">
<h1 class="text-2xl font-bold text-amber-300">Rate Something</h1>
<form class="mt-8 space-y-4" method="post" action="/rate">
<div>
<label class="block text-sm font-medium text-amber-200" for="title">Title</label>
<input class="mt-1 w-full rounded-lg border border-violet-700 bg-violet-900 px-3 py-2 text-white placeholder-violet-400 focus:border-amber-400 focus:outline-none" type="text" id="title" name="title" placeholder="e.g. Forrest Gump, After Hours">
</div>
<div>
<label class="block text-sm font-medium text-amber-200" for="media_type">Type</label>
<select class="mt-1 w-full rounded-lg border border-violet-700 bg-violet-900 px-3 py-2 text-white focus:border-amber-400 focus:outline-none" id="media_type" name="media_type">
<option value="Movie">Movie</option>
<option value="Album">Album</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-amber-200" for="rating">Rating (1-10)</label>
<input class="mt-1 w-full rounded-lg border border-violet-700 bg-violet-900 px-3 py-2 text-white focus:border-amber-400 focus:outline-none" type="number" id="rating" name="rating" min="1" max="10" step="0.5">
</div>
<div>
<label class="block text-sm font-medium text-amber-200" for="notes">Notes</label>
<textarea class="mt-1 min-h-[100px] w-full rounded-lg border border-violet-700 bg-violet-900 px-3 py-2 text-white placeholder-violet-400 focus:border-amber-400 focus:outline-none" id="notes" name="notes" placeholder="What did you think?"></textarea>
</div>
<button class="rounded-lg bg-amber-400 px-6 py-2 font-semibold text-violet-950 hover:bg-amber-300" type="submit">Submit Rating</button>
</form>
<p class="mt-10"><a class="text-amber-300 underline hover:text-amber-200" href="/">← Back to Home</a></p>
</div>
</body>
</html>
"""
Here's what the POST handling does:
-
request.method == "POST"checks if the user submitted the form (vs. just loading the page) -
request.form.get("title", "")reads the value from the form field namedtitle- the names match thename="..."attributes in your HTML inputs -
float(rating)converts the rating from a string to a number so it displays correctly -
my_ratings.append({...})adds a new dictionary to your list - same structure as the entries already there -
redirect("/ratings")sends the user to the ratings page so they can see their new entry
Step 4: Update the ratings page to use a loop
If your /ratings route still has hardcoded HTML cards, replace it with a version that loops over my_ratings. This way new entries automatically show up.
Replace your entire ratings() function with:
@app.route("/ratings")
def ratings():
cards = ""
for item in my_ratings:
cards += f"""
<div class="rounded-xl border border-violet-700 bg-violet-900/80 p-4">
<h3 class="text-lg font-semibold text-amber-300">{item['title']}</h3>
<p class="text-xs uppercase tracking-wide text-fuchsia-300">{item['type']}</p>
<p class="mt-2 text-2xl font-bold text-amber-300">{item['rating']} / 10</p>
<p class="mt-2 italic text-violet-200">"{item['notes']}"</p>
</div>
"""
return f"""
<html>
<head>
<meta charset="utf-8">
<title>My Ratings</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-violet-950 text-violet-50">
<div class="mx-auto max-w-2xl px-6 py-12">
<h1 class="text-2xl font-bold text-amber-300">My Ratings</h1>
<div class="mt-8 space-y-4">
{cards}
</div>
<p class="mt-10"><a class="text-amber-300 underline hover:text-amber-200" href="/">← Back to Home</a></p>
</div>
</body>
</html>
"""
If you already converted to a loop last week, make sure it's using f"""...""" and pulling from my_ratings.
Step 5: Test it
- Save
app.py(Ctrl+S) - If the app is running, click inside the terminal and press Ctrl+C to stop it
- Click the play button (top-right triangle) to restart the app
- Go to
/ratein your browser, fill in all four fields, and click Submit Rating - You should land on
/ratingsand see your new entry alongside the starter data
Try adding 2-3 more ratings. They should all appear on the ratings page.
Challenge: Add a Director/Artist field
Your TODO mentioned wanting a director/artist field. Add it:
- Add a new
<div>with an<input>to the form (between Type and Rating is a natural spot). Usename="creator"and label it "Director / Artist" - In the POST handler, read it:
creator = request.form.get("creator", "") - Add
"creator": creatorto the dictionary you append - Show it on the rating card: add a line like
<p class="text-sm text-violet-300">By {item['creator']}</p>below the type - Add a
"creator"key to your existing entries inmy_ratingsso they don't break
Part 4: Save to GitHub
Save your work using the Source Control tab:
- Save all files: Ctrl+S
- Click the Source Control icon on the left sidebar
- Type a message like:
make rating form functional with POST - Click Commit, then Sync Changes
Set Your Goal: Add a New Feature
Open TODO.md and pick one feature to work on before next week:
- Edit existing ratings - add an
/editroute that lets you change a rating you already submitted - Filter by type - add buttons or links on
/ratingsto show only movies or only albums - Delete a rating - add a delete button on each card that removes it from the list
- Add a search bar - let users search ratings by title
How to use the AI chat to help you build it
-
Open the AI chat panel:
- Codespaces: Click the chat icon in the left sidebar, or press Ctrl+I
- Cursor: Press Ctrl+L
-
Use this prompt template:
I'm building a media rating app in Flask where users can rate movies and albums. My ratings are stored in a Python list of dictionaries. This week I got the form submission working. Here's my current code: [paste your
app.py] I want to add: [describe the feature you picked] Can you help me understand how to approach this? Don't just give me the answer - walk me through the steps.
- If something breaks, paste the error:
I got this error: [paste error]. What's wrong?
For more prompts, see the Prompting Cheat Sheet.
Update your TODO.md
Write your chosen feature. Be specific:
-
Good: "Add a delete button to each rating card"
-
Not good: "improve my app"
Troubleshooting
405 Method Not Allowed
Your route doesn't accept POST requests. Make sure the decorator says @app.route("/rate", methods=["GET", "POST"]) - the methods list must include "POST".
Form submits but nothing shows up on the ratings page
- Check that
my_ratings.append({...})is inside theif request.method == "POST":block - Make sure the ratings page loops over
my_ratings(not hardcoded HTML) - Check the
name="..."attributes in your form match what you use inrequest.form.get()
Data disappears when I restart the app
This is expected. Your data lives in a Python list in memory - when the app stops, the list resets to whatever is hardcoded at the top of app.py. Saving to a file or database is a future feature.
The rating shows up as a string like "8.5" instead of a number
Make sure you're converting it: float(rating) before appending. If someone leaves the field blank, float("") will crash - you can default it: rating = request.form.get("rating", "5").
KeyError on the ratings page
If you added new fields (like creator) to the form but your existing entries in my_ratings at the top of the file don't have that key, the loop will crash. Add the missing key to every dictionary in my_ratings.
Something else isn't working
Ask the AI agent:
I'm building a media rating app in Flask. I just added form submission with POST. I'm getting this error: [paste error]. What's wrong?
Or ask your instructor.
Resources
- Flask - how Flask routes and templates work
- Tailwind CSS - styling with utility classes
- GitHub Codespaces Guide - how to open and use Codespaces
- Cursor Guide - how to open and use Cursor
- GitHub Basics - how to commit and push your code
- Prompting Cheat Sheet - good prompts to use with the AI agent