> Source URL: /users/charlie-m/week-4.path
---
week: 4
date: 2026-04-01
---

# Charlie: Week 4: Start Building Your Rating App

You are in good shape on earlier weeks: `favs.py`, `v0-prompt.md`, `vibe-code-report.md`, `warmup.py`, and `TODO.md` are in your repo. This week you start your real app in **Flask** so you own every line of code.

Your v0 prototype (React/Next.js) is a great reference: https://v0.app/chat/movie-and-album-ratings-gHlvwCzKPL5?ref=WBY3CM

**Important:** v0 can look very polished. Your first Flask version will be simpler on purpose. You are rebuilding the **core idea** (home, rate, see ratings) with Python. Styling uses **Tailwind CSS** via CDN -- see [Tailwind CSS](../../resources/tailwind.resource.md).

---

## Check In: What Are You Working On?

Open `TODO.md`. Does it match what you are doing today? If not, add a rough line like "Build my Media Rater Flask app." You will refine this at the end.

- [ ] My TODO line roughly matches today's focus

---

## Part 1: Essential Project Catch-up

### Fill in **Known Limitations** in `vibe-code-report.md`

Scroll to `## Known Limitations` and add 2-3 honest bullets -- for example:

- "Ratings do not save yet -- refresh clears them"
- "No edit screen for old ratings"
- "No poster images hooked up in Flask yet"

- [ ] Known Limitations is filled in

**Optional:** Your `warmup.py` may be missing a closing `)` on the `print` line. If the file does not run, fix that when you have time -- it is **not** required for your Flask path this week.

---

## Part 2: Build Your Flask App

Flask maps URLs to Python functions. Each returns HTML as a string. Use Tailwind classes instead of giant `<style>` blocks.

### Step 1: Create `app.py`

**New File** -- `app.py`. Paste:

```python
from flask import Flask

app = Flask(__name__)


@app.route("/")
def home():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>Media Rater</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-3xl font-bold text-amber-300">Media Rater</h1>
            <p class="mt-2 text-violet-300">By Charlie M.</p>
            <p class="mt-6 text-violet-100">Rate movies and albums. Keep notes. Track everything.</p>
            <div class="mt-8 flex flex-wrap gap-3">
                <a class="rounded-lg bg-amber-400 px-5 py-2 font-semibold text-violet-950 hover:bg-amber-300" href="/rate">Rate Something</a>
                <a class="rounded-lg border border-amber-400 px-5 py-2 font-semibold text-amber-200 hover:bg-amber-400/10" href="/ratings">My Ratings</a>
            </div>
        </div>
    </body>
    </html>
    """


@app.route("/rate")
def rate():
    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">
                <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="button">Submit Rating (UI only for now)</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>
    """


@app.route("/ratings")
def ratings():
    return """
    <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">
                <div class="rounded-xl border border-violet-700 bg-violet-900/80 p-4">
                    <h3 class="text-lg font-semibold text-amber-300">Forrest Gump</h3>
                    <p class="text-xs uppercase tracking-wide text-fuchsia-300">Movie</p>
                    <p class="mt-2 text-2xl font-bold text-amber-300">8.5 / 10</p>
                    <p class="mt-2 italic text-violet-200">"Classic. Tom Hanks is goated."</p>
                </div>
                <div class="rounded-xl border border-violet-700 bg-violet-900/80 p-4">
                    <h3 class="text-lg font-semibold text-amber-300">After Hours</h3>
                    <p class="text-xs uppercase tracking-wide text-fuchsia-300">Album</p>
                    <p class="mt-2 text-2xl font-bold text-amber-300">9 / 10</p>
                    <p class="mt-2 italic text-violet-200">"The Weeknd at his best."</p>
                </div>
                <div class="rounded-xl border border-violet-700 bg-violet-900/80 p-4">
                    <h3 class="text-lg font-semibold text-amber-300">Get Out</h3>
                    <p class="text-xs uppercase tracking-wide text-fuchsia-300">Movie</p>
                    <p class="mt-2 text-2xl font-bold text-amber-300">7.5 / 10</p>
                    <p class="mt-2 italic text-violet-200">"Creepy but good."</p>
                </div>
            </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 __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=5000)
```

The form is **UI only** for now (button type `button`) so you are not stuck on saving data until you are ready.

- [ ] I created `app.py` and pasted the starter code

### Step 2: Install Flask

```bash
pip install flask
```

- [ ] Flask is installed

### Step 3: Run the app

Save `app.py`, **play button**, open it in the browser (**Codespaces:** use the port popup or **Ports** tab for port 5000 -- **Cursor / local:** `http://localhost:5000`).

- [ ] Home, `/rate`, and `/ratings` work

### Step 4: Make it yours

Replace the three example cards with **your** movies and albums.

- [ ] I customized the example ratings

### Challenge: Use a Python list

Add this **below** `app = Flask(__name__)`:

```python
my_ratings = [
    {"title": "Forrest Gump", "type": "Movie", "rating": 8.5,
     "notes": "Classic. Tom Hanks is goated."},
    {"title": "After Hours", "type": "Album", "rating": 9,
     "notes": "The Weeknd at his best."},
]
```

Replace `ratings()` with a version that **loops** over `my_ratings` and builds HTML cards. Use an **f-string** `return f""" ... """` and build a string `cards` in a `for` loop. Use Tailwind classes like `rounded-xl border border-violet-700 bg-violet-900/80 p-4` for each card.

Tip: inside `f""" ... """`, use `{{` and `}}` if you ever need literal curly braces -- for Tailwind you mostly do not need braces in the HTML.

- [ ] I moved my ratings into `my_ratings` and the page still looks good

---

## Part 3: Save to GitHub

Message example: `add media rater flask app with tailwind`

- [ ] I committed and synced

---

## Set Your Goal for Next Week

Open `TODO.md` again and set **one** specific goal. Examples:

- "Save new ratings into my_ratings using a form POST"
- "Add search or filter on `/ratings`"
- "Add poster image URLs to each card"

- [ ] I updated `TODO.md` with a specific goal for next week
- [ ] I saved, committed, and synced

---

## Troubleshooting

### "No module named flask"

`pip install flask` or `pip3 install flask`.

### Page looks plain

Missing Tailwind `<script>` in `<head>`. Restart Flask, hard-refresh.

### Route "Not Found"

Keep routes **above** `if __name__ == "__main__":`.

---

## Resources

- [Flask](../../resources/flask.resource.md)
- [Tailwind CSS](../../resources/tailwind.resource.md)
- [GitHub Codespaces Guide](../../resources/github-codespaces.guide.md)
- [Cursor Guide](../../resources/cursor.resource.md)
- [GitHub Basics](../../resources/github-basics.guide.md)
- [Prompting Cheat Sheet](../../resources/prompting-cheat-sheet.guide.md)
- [Project Folder Guide](../../resources/gh-project-folder.guide.md)

---

## Get Help From Your AI Agent

> I'm building a **Media Rater** Flask app for Demo Day. This week I'm working on: **[e.g. list loop / Tailwind cards / an error]**.
>
> ```
> [paste app.py or one function]
> ```
>
> Stuck because: **[describe]. Paste terminal errors.**
>
> Please coach me -- no full-file rewrite. Small steps first.


---

## Backlinks

The following sources link to this document:

- [Week 4: Start Building Your Rating App](/users/charlie-m/index.path.llm.md)
