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 <24 inside 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
  • request lets you read form data that the browser sends

  • redirect sends 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="/">&larr; 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 named title - the names match the name="..." 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="/">&larr; 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

  1. Save app.py (Ctrl+S)
  2. If the app is running, click inside the terminal and press Ctrl+C to stop it
  3. Click the play button (top-right triangle) to restart the app
  4. Go to /rate in your browser, fill in all four fields, and click Submit Rating
  5. You should land on /ratings and 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:

  1. Add a new <div> with an <input> to the form (between Type and Rating is a natural spot). Use name="creator" and label it "Director / Artist"
  2. In the POST handler, read it: creator = request.form.get("creator", "")
  3. Add "creator": creator to the dictionary you append
  4. Show it on the rating card: add a line like <p class="text-sm text-violet-300">By {item['creator']}</p> below the type
  5. Add a "creator" key to your existing entries in my_ratings so they don't break

Part 4: Save to GitHub

Save your work using the Source Control tab:

  1. Save all files: Ctrl+S
  2. Click the Source Control icon on the left sidebar
  3. Type a message like: make rating form functional with POST
  4. 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 /edit route that lets you change a rating you already submitted
  • Filter by type - add buttons or links on /ratings to 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

  1. Open the AI chat panel:

    • Codespaces: Click the chat icon in the left sidebar, or press Ctrl+I
    • Cursor: Press Ctrl+L
  2. 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.

  1. 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 the if 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 in request.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