> Source URL: /users/charlie-m/week-5.path
---
week: 5
date: 2026-04-08
---

# 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.

- [ ] My TODO.md matches today's focus

---

## 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](../../resources/variables.resource.md) - how to store text and numbers
- [Print Statements](../../resources/print-statements.resource.md) - how to display output, use f-strings, and format cards

- [ ] I wrote `practice.py` and it runs without errors
- [ ] My output looks like a formatted leaderboard
- [ ] I committed and synced my work

---

## 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:

```python
from flask import Flask
```

Change it to:

```python
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

- [ ] I updated the import line

### Step 2: Change the form to submit

Find the `<form>` tag in your `/rate` route. It currently looks like:

```html
<form class="mt-8 space-y-4">
```

Change it to:

```html
<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:

```html
<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:

```html
<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>
```

- [ ] I changed the form to `method="post" action="/rate"`
- [ ] I changed the button to `type="submit"`

### 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:

```python
@app.route("/rate", methods=["GET", "POST"])
```

Then update the `rate()` function to handle both cases. Replace the entire function with:

```python
@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

- [ ] I replaced the `rate()` function with the version that handles POST
- [ ] I understand what `request.form.get()` does

### 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:

```python
@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`.

- [ ] My ratings page builds cards from the `my_ratings` list

### 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.

- [ ] The form submits and redirects to `/ratings`
- [ ] New ratings appear on the ratings page
- [ ] I can add multiple ratings in a row

### 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

- [ ] I added the Director/Artist field and it shows on the cards

---

## 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**

- [ ] I committed and synced my 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.

3. If something breaks, paste the error:

> I got this error: [paste error]. What's wrong?

For more prompts, see the [Prompting Cheat Sheet](../../resources/prompting-cheat-sheet.guide.md).

### Update your TODO.md

Write your chosen feature. Be specific:
- Good: "Add a delete button to each rating card"
- Not good: "improve my app"

- [ ] I picked a feature and wrote it in `TODO.md`
- [ ] I tried using the AI chat to start building it
- [ ] I committed and synced my work

---

## 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

- [Flask](../../resources/flask.resource.md) - how Flask routes and templates work
- [Tailwind CSS](../../resources/tailwind.resource.md) - styling with utility classes
- [GitHub Codespaces Guide](../../resources/github-codespaces.guide.md) - how to open and use Codespaces
- [Cursor Guide](../../resources/cursor.resource.md) - how to open and use Cursor
- [GitHub Basics](../../resources/github-basics.guide.md) - how to commit and push your code
- [Prompting Cheat Sheet](../../resources/prompting-cheat-sheet.guide.md) - good prompts to use with the AI agent


---

## Backlinks

The following sources link to this document:

- [Week 5: Make the Rating Form Work](/students.path.llm.md)
- [Week 5: Make the Rating Form Work](/users/charlie-m/index.path.llm.md)
