Python TrainerQuick start
If any file the app needs already exists, the command lists the files and changes nothing. Every app is created with a
Apps you can create
The tables and columns follow the skill test papers. Run Neil's versionsThese four are standalone apps (they do not use Books (ISBN)
Events Registration
Employee Attendance
Clinic Consultations
ICT Congress Registration
How
|
| Action | SQL | Route in app.py |
Method |
|---|---|---|---|
| Create | INSERT |
/add |
POST |
| Read | SELECT |
/ |
GET |
| Update | UPDATE |
/edit/<id> |
POST |
| Delete | DELETE |
/delete/<id> |
POST |
get_db()opens the SQLite connection andclose_db()closes it after each request.init_db()creates thebookstable the first time the app runs.- Always use
?placeholders in SQL. Never build SQL with string formatting. - Call
db.commit()after everyINSERT,UPDATE, orDELETE, or the change is not saved.
How the SQLite functions work
These are the Python sqlite3 calls used in every route.
| Function | What it does |
|---|---|
sqlite3.connect("app.db") |
Opens the database file (creates it if missing) |
db.execute(sql, values) |
Runs one SQL statement. values fill the ? marks in order |
db.commit() |
Saves INSERT, UPDATE, and DELETE changes to the file |
.fetchall() |
Returns all rows of a SELECT as a list |
.fetchone() |
Returns the first row of a SELECT, or None if nothing matched |
cursor.rowcount |
Number of rows the last UPDATE or DELETE changed |
cursor.lastrowid |
The id of the row the last INSERT created |
db.row_factory = sqlite3.Row |
Lets you use row["title"] or row.title in templates instead of row[1] |
Example: UPDATE
def update_book(book_id, title, author, year):
db = get_db()
cur = db.execute(
"UPDATE books SET title = ?, author = ?, year = ? WHERE id = ?",
(title, author, year, book_id),
)
db.commit()
return cur.rowcount # 1 if the book was found, 0 if the id does not exist
How to read it:
UPDATE booksnames the table.SET title = ?, author = ?, year = ?lists the columns to change.WHERE id = ?picks which row to change. Never leave outWHERE, or every row in the table is changed.- The tuple
(title, author, year, book_id)must be in the same order as the?marks. db.commit()saves the change. Without it, the update is lost.
Use it inside a route:
@app.route("/edit/<int:book_id>", methods=["POST"])
def edit(book_id):
changed = update_book(
book_id,
request.form["title"].strip(),
request.form["author"].strip(),
request.form.get("year") or None,
)
if changed == 0:
return "Book not found", 404
return redirect(url_for("index"))
How the Edit button flows through the app:
- Clicking Edit opens
/?edit=3. index()runsSELECT * FROM books WHERE id = ?and sends that row to the page asbook.- The form fills in with the row's current values and its
actionpoints to/edit/3. - Clicking Update sends a POST to
/edit/3, which callsupdate_book. - The route redirects to
/, and the list shows the new values.
To change only one column:
db.execute("UPDATE books SET year = ? WHERE id = ?", (1999, 3))
db.commit()
Example: INSERT, SELECT, DELETE as functions
def add_book(title, author, year):
db = get_db()
cur = db.execute("INSERT INTO books (title, author, year) VALUES (?, ?, ?)",
(title, author, year))
db.commit()
return cur.lastrowid # id of the new row
def get_books():
return get_db().execute("SELECT * FROM books ORDER BY id DESC").fetchall()
def get_book(book_id):
return get_db().execute("SELECT * FROM books WHERE id = ?", (book_id,)).fetchone()
def delete_book(book_id):
db = get_db()
cur = db.execute("DELETE FROM books WHERE id = ?", (book_id,))
db.commit()
return cur.rowcount
A single value still needs a tuple, so write (book_id,) with the comma.
How the page is linked to the route
render_template("index.html", books=books, book=book) loads templates/index.html and passes the values after it into the HTML. Inside the HTML, {{ b.title }} prints a value and {% for b in books %} loops over a list.
The folder must be named exactly templates and sit next to app.py.
How to route a new function
A route is a Python function with @app.route(...) above it. Follow these steps every time.
1. Add the function in app.py
Put it above the if __name__ == "__main__": line.
@app.route("/about")
def about():
return render_template("about.html")
2. Create the matching file in templates/
Create templates/about.html:
<h1>About</h1>
<a href="{{ url_for('index') }}">Back</a>
3. Link to it from any page
Use the function name inside url_for, not the URL:
<a href="{{ url_for('about') }}">About</a>
Open http://127.0.0.1:5000/about. With debug=True the server reloads when you save.
Route that reads one record
A URL part in <angle brackets> becomes a function argument. int: makes it a number.
@app.route("/book/<int:book_id>")
def book_detail(book_id):
book = get_db().execute("SELECT * FROM books WHERE id = ?", (book_id,)).fetchone()
if book is None:
return "Book not found", 404
return render_template("book.html", book=book)
<a href="{{ url_for('book_detail', book_id=b.id) }}">View</a>
Route that saves a form (POST)
Use methods=["POST"] for anything that changes data, then redirect so a refresh does not resubmit the form.
@app.route("/add", methods=["POST"])
def add():
db = get_db()
db.execute("INSERT INTO books (title, author) VALUES (?, ?)",
(request.form["title"], request.form["author"]))
db.commit()
return redirect(url_for("index"))
<form method="post" action="{{ url_for('add') }}">
<input name="title">
<input name="author">
<button>Save</button>
</form>
The name="..." of each input must match the key in request.form["..."].
Route with a search box (query string)
@app.route("/search")
def search():
q = request.args.get("q", "")
books = get_db().execute(
"SELECT * FROM books WHERE title LIKE ?", (f"%{q}%",)
).fetchall()
return render_template("index.html", books=books, book=None)
<form action="{{ url_for('search') }}">
<input name="q" placeholder="Search title">
<button>Search</button>
</form>
Using a different table
To CRUD something else (students, events, products):
- Change the
CREATE TABLEstatement ininit_db(). - Change the column names in each
INSERT,UPDATE, andSELECT. - Change the
<input name="...">fields and table columns intemplates/index.html.
If you change the columns of a table that already exists, delete app.db so it is created again.
Common errors
| Error | Fix |
|---|---|
python is not recognized |
Install Python from python.org and tick Add to PATH, or use py app.py |
No module named flask |
Activate the venv (venv\Scripts\activate), then pip install flask |
TemplateNotFound |
The folder must be named templates, next to app.py |
Method Not Allowed |
The form's method does not match the route's methods=[...] |
BuildError from url_for |
The name must match a function name in app.py exactly |
| Port already in use | Use app.run(debug=True, port=5001) |
| Data not saved | Add db.commit() after the write |
Notes
debug=Trueis for development only. Turn it off before deploying.- Delete is POST-only so a link or crawler cannot delete records.