Luke's Project Guide

Project: Study Buddy Tracker Category: Web Development (Flask) Last updated: April 18

Note: This guide reflects the latest state of your project repo. It may not match the most up-to-date version if you've worked since.

Where You Are

You're ahead. Your Flask app already has:

  • A dashboard that groups assignments (overdue / due soon / this week / later)
  • An assignments list
  • A new-assignment form with validation in helpers.py
  • A real SQLite layer with schema init
  • A mark-complete route (POST /assignments/<id>/complete)

What's left: a button on the dashboard that calls the mark-complete route, edit and delete routes, a show/hide completed toggle, and a route audit.

Project Structure

Your project already has a clean split — let's name it:

  • Business logic — you handwrite this. helpers.py (validation, date parsing, grouping logic) and the SQL in db.py / schema.sql. This is what makes your tracker useful: how assignments are validated and how they're grouped by urgency.
  • Library / view code — agent-assisted is fine. Flask routes in app.py, HTML templates, Bootstrap classes. These are the same for any CRUD Flask app.

Current layout:

final-project-Pattlu3/
├── app.py                  ← Flask routes — agent-assisted OK
├── db.py                   ← DB connection — library code
├── helpers.py              ← business logic — handwrite (yours to own)
├── schema.sql              ← SQL — yours (product decision)
├── pyproject.toml
├── templates/              ← HTML — agent-assisted OK
└── instance/               ← generated DB (gitignored)

Why the split? From Lecture 1: The MVP — on demo day, the question is "how does your tracker decide what's 'due soon'?" That's in helpers.py (group_assignments). The Flask routing that plumbs data from DB to page is not the interesting part.

helpers.py should not import flask. It's pure logic — you can test it without spinning up a server.

Phase 1: Route Audit

No code — research phase. Walk your app like a user would. Find the gaps.

Objective

Before adding anything new, walk every URL in your app. Know what works, what's half-built, and what's missing.

Instructions

    • / (dashboard)
    • /assignments (list)
    • /assignments/new (GET — form)
    • /assignments/new (POST — submit)
    • /assignments/<id>/complete (POST — triggered how?)

Sample Notes

GET /                              → ✅ dashboard renders with groups
GET /assignments                   → ✅ list shows uncompleted
GET /assignments/new               → ✅ form renders, empty
POST /assignments/new              → ✅ saves, redirects to list
POST /assignments/<id>/complete    → ⚠️ route exists, no button triggers it
GET /assignments/<id>/edit         → ❌ doesn't exist
POST /assignments/<id>/delete      → ❌ doesn't exist

The audit sets your phase-by-phase work order for the rest of the week.

Optional — get help from your agent:

Here are my current Flask routes. Help me spot any missing pieces
to hit my MVP (add / list / mark complete / edit / delete). Just
list what's missing, no code yet.

Phase 2: Wire the Mark-Complete Button

Agent-assisted is fine here. A tiny HTML form inside a list row is pure template markup.

Objective

Your mark-complete route exists but nothing in the UI triggers it. Add a button that POSTs to it.

Instructions

Hints

Inline form inside a list row:

{% for a in assignments %}
  <li>
    {{ a.title }} — due {{ a.due_date }}
    <form action="/assignments/{{ a.id }}/complete" method="post" style="display: inline">
      <button type="submit">✓ Done</button>
    </form>
  </li>
{% endfor %}

Why a form, not a link? State-changing actions should be POST, not GET. Links are GET. A tiny form is the plain-HTML way to POST without JavaScript.

Optional — get help from your agent:

Show me the cleanest way to add a "Mark Done" button next to each
assignment in assignments_list.html. My route is
POST /assignments/<int:assignment_id>/complete. Don't change app.py.

Phase 3: Edit Route

Mixed phase. The route itself is Flask code (agent-assisted OK). The validation call reuses YOUR validate_assignment_form from helpers.py — that's your business logic doing the real work.

Objective

Users can add but not edit. Add a flow: GET shows form pre-filled, POST updates the row.

Instructions

    • GET /assignments/<int:assignment_id>/edit — fetch row, render form pre-filled
    • POST /assignments/<int:assignment_id>/edit — validate, UPDATE row, redirect

Hints

GET handler:

@app.get("/assignments/<int:assignment_id>/edit")
def assignments_edit_form(assignment_id: int):
    db = get_db()
    row = db.execute(
        "SELECT id, title, due_date FROM assignments WHERE id = ?",
        (assignment_id,),
    ).fetchone()
    if row is None:
        return "Not found", 404
    return render_template(
        "assignment_form.html",
        title="Edit Assignment",
        form=dict(row),
        errors={},
        assignment_id=assignment_id,
    )

POST handler:

@app.post("/assignments/<int:assignment_id>/edit")
def assignments_update(assignment_id: int):
    form = dict(request.form)
    cleaned, errors = validate_assignment_form(form)   # ← reuses YOUR logic
    if errors:
        return render_template("assignment_form.html",
                               title="Edit Assignment",
                               form=form, errors=errors,
                               assignment_id=assignment_id), 400
    db = get_db()
    db.execute(
        "UPDATE assignments SET title = ?, due_date = ? WHERE id = ?",
        (cleaned["title"], cleaned["due_date"], assignment_id),
    )
    db.commit()
    return redirect(url_for("assignments_list"))

Make the form action conditional in the template:

<form method="post"
      action="{% if assignment_id %}/assignments/{{ assignment_id }}/edit{% else %}/assignments/new{% endif %}">

Optional — get help from your agent:

Read my assignment_form.html and app.py. Help me adapt the form
to work for both "new" and "edit" based on whether assignment_id
is passed in. Show me the diff only.

Phase 4: Delete Route

Agent-assisted is fine here. Single SQL DELETE + one button. Near-identical to the mark-complete pattern.

Objective

One POST route, no GET (prevents accidental deletion via a link).

Instructions

Hints

The route:

@app.post("/assignments/<int:assignment_id>/delete")
def assignments_delete(assignment_id: int):
    db = get_db()
    db.execute("DELETE FROM assignments WHERE id = ?", (assignment_id,))
    db.commit()
    return redirect(url_for("assignments_list"))

Delete button with confirmation:

<form action="/assignments/{{ a.id }}/delete" method="post" style="display: inline"
      onsubmit="return confirm('Delete this assignment?')">
  <button type="submit">🗑</button>
</form>

Optional — get help from your agent:

Skip — mirrors the mark-complete pattern you already have.

Phase 5: Show/Hide Completed Toggle

Mixed phase. The SQL conditional is business logic (you're deciding what the list contains). The URL-param plumbing is library code.

Objective

Your list currently filters WHERE completed = 0. Let the user flip to showing completed.

Instructions

Hints

Route with the toggle:

@app.get("/assignments")
def assignments_list():
    show_completed = request.args.get("show_completed") == "1"
    db = get_db()
    if show_completed:
        sql = "SELECT id, title, due_date, completed FROM assignments ORDER BY due_date ASC"
    else:
        sql = "SELECT id, title, due_date, completed FROM assignments WHERE completed = 0 ORDER BY due_date ASC"
    rows = db.execute(sql).fetchall()
    return render_template("assignments_list.html",
                           title="Assignments",
                           assignments=[dict(r) for r in rows],
                           show_completed=show_completed)

Toggle link in the template:

{% if show_completed %}
  <a href="/assignments">Hide completed</a>
{% else %}
  <a href="/assignments?show_completed=1">Show completed</a>
{% endif %}

Optional — get help from your agent:

My assignments_list page filters out completed items. Walk me
through adding a URL-param-based toggle. I've sketched the route —
check my logic before I commit.

Checkpoint 2 Readiness

By Thursday April 23 at 3pm:

Helpful Resources