Web Input, Uploads, Databases, and Testing

You can read this note directly if you know basic Flask routes and HTML forms. This note connects user input to validation, processing, storage, retrieval, and displayed output.

Beginner Mental Model

A complete small web application often follows this path:

HTML form
-> Flask request
-> Python validation and processing
-> SQLite storage or retrieval
-> Jinja template
-> browser display

For an uploaded image, the file itself is normally saved in a folder while useful metadata, such as its title and filename, is stored in the database.

When something fails, identify the first broken layer rather than changing every part at once.

Form to Flask Flow

Caption: Form values travel to a Flask route in a request; Flask processes them and returns rendered HTML for the browser to display.

HTML:

<form method="post" action="/add">
  <label for="title">Title</label>
  <input id="title" name="title" type="text">
  <button type="submit">Add</button>
</form>

Flask:

from flask import Flask, request, render_template
 
app = Flask(__name__)
 
 
@app.route("/add", methods=["POST"])
def add():
    title = request.form["title"].strip()
 
    if title == "":
        return render_template(
            "form.html",
            error="Enter a title."
        )
 
    return render_template("result.html", title=title)

The server must validate input even if the HTML form also uses client-side restrictions. A user can bypass or alter browser-side checks.

Input Validation

Useful checks include:

InputPossible check
required textnot empty after removing surrounding spaces
scoreinteger and within an allowed range
filenamenon-empty and acceptable extension
categoryone of the allowed values
identifierexpected format and length

Validation should happen before database insertion or file saving where possible.

Example:

score_text = request.form["score"]
 
try:
    score = int(score_text)
except ValueError:
    return render_template("form.html", error="Enter a whole number.")
 
if not 0 <= score <= 100:
    return render_template("form.html", error="Score must be from 0 to 100.")

Image Upload Flow

Caption: A complete upload flow validates the input, saves the file, stores its metadata in SQL, retrieves the record, and displays the result.

HTML:

<form method="post"
      action="/upload"
      enctype="multipart/form-data">
  <label for="photo">Image</label>
  <input id="photo" type="file" name="photo">
  <button type="submit">Upload image</button>
</form>

Flask route:

from pathlib import Path
from flask import Flask, request
 
app = Flask(__name__)
 
UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)
 
 
@app.route("/upload", methods=["POST"])
def upload():
    photo = request.files["photo"]
 
    if photo.filename == "":
        return "No file selected", 400
 
    save_path = UPLOAD_DIR / photo.filename
    photo.save(save_path)
 
    return "Uploaded"

Beginner trace:

StepWhat happens
1browser sends the file using multipart/form-data
2Flask reads it from request.files["photo"]
3Python checks whether the submission is acceptable
4server saves the file
5database stores metadata if required
6a template displays the result

For real systems, filenames must be handled carefully because they come from users. The exam-core point is to validate input and avoid blindly trusting submitted data.

SQLite Table

Example table for uploaded-image metadata:

CREATE TABLE IF NOT EXISTS Photo (
    photo_id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    filename TEXT NOT NULL
);

Python setup:

import sqlite3
 
 
def open_database():
    connection = sqlite3.connect("gallery.db")
    connection.row_factory = sqlite3.Row
    return connection

Create the table:

def create_table(connection):
    connection.execute("""
        CREATE TABLE IF NOT EXISTS Photo (
            photo_id INTEGER PRIMARY KEY,
            title TEXT NOT NULL,
            filename TEXT NOT NULL
        )
    """)
    connection.commit()

Parameterised SQL

Insert values using parameters:

def add_photo(connection, title, filename):
    connection.execute(
        "INSERT INTO Photo (title, filename) VALUES (?, ?)",
        (title, filename)
    )
    connection.commit()

Do not build SQL by concatenating form input:

# Avoid this
sql = "INSERT INTO Photo (title) VALUES ('" + title + "')"

Parameters keep data values separate from SQL structure and reduce SQL injection risk.

Retrieval

def get_photos(connection):
    return connection.execute(
        """
        SELECT photo_id, title, filename
        FROM Photo
        ORDER BY title
        """
    ).fetchall()

A route can pass the rows to a template:

@app.route("/photos")
def photos():
    with open_database() as connection:
        rows = get_photos(connection)
 
    return render_template("photos.html", photos=rows)

Displaying Formatted Output

<table>
  <tr>
    <th>Title</th>
    <th>File</th>
  </tr>
 
  {% for photo in photos %}
    <tr>
      <td>{{ photo["title"] }}</td>
      <td>{{ photo["filename"] }}</td>
    </tr>
  {% endfor %}
</table>

The route variable and template variable must agree:

render_template("photos.html", photos=rows)
{% for photo in photos %}

If uploaded images are to be displayed, Flask must also provide a route or suitable static location from which the browser can request each saved file.

One Complete Processing Sequence

1. user enters title and chooses an image
2. browser submits text fields and file
3. Flask reads request.form and request.files
4. Python validates both inputs
5. image is saved to an upload folder
6. title and filename are inserted into SQLite
7. transaction is committed
8. rows are selected from SQLite
9. Jinja renders a table or image gallery
10. browser displays the response

This sequence separates three different things:

  • file content stored in the file system;
  • metadata stored in the database;
  • HTML presentation generated by the template.

Local Server Testing

The syllabus expects testing on a local server.

Run the application and visit its local address, commonly:

http://127.0.0.1:5000

Test cases should cover more than the happy path.

Test categoryExampleExpected result
normalvalid title and imagerecord saved and displayed
extremeshortest allowed titleaccepted if within rule
invalidunsupported file typeclear rejection message
emptyno title or no fileform redisplayed with guidance
persistencerestart app after insertstored database row remains
routevisit expected URLcorrect page and status
methodsubmit form using intended methodroute accepts request
displayretrieve several rowscomplete table shown

Debugging Map

SymptomLikely place to check
404 responserequested URL and @app.route path
405 responseform method and route methods
form value missinginput name and request.form[...] key
upload missingenctype and request.files
file not savedupload path and save() call
row not storedSQL, parameters, and commit()
table blankquery result and template variable
image brokenfile-serving route or generated image URL
CSS missingstatic/ folder and stylesheet URL

A useful debugging question is:

What evidence proves that this layer produced the expected value?

Common Mistakes

  • Forgetting enctype="multipart/form-data" for uploads.
  • Reading uploaded files from request.form.
  • Trusting a user-supplied filename without checks.
  • Saving a file but not recording its filename when metadata is required.
  • Recording a filename but never saving the file.
  • Forgetting to commit an INSERT or UPDATE.
  • Concatenating form data into SQL.
  • Passing one template variable name but looping over another.
  • Testing only valid input.

Paper 1 and Paper 2 Emphasis

Paper 1-style reasoning: trace the movement of data, identify missing form attributes, explain parameterised SQL, and propose normal, extreme, invalid, and empty tests.

Paper 2-style practical work: complete form handling, validate values, save an upload, insert and retrieve SQLite rows, render a table, and verify the application locally.

Check Your Understanding

  1. Where does Flask find ordinary text fields and uploaded files?
  2. Why should validation occur on the server?
  3. Why use SQL parameters?
  4. What is the difference between saving an image and storing its metadata?
  5. Give one normal and one invalid local-server test.

Answers:

  1. Text fields are in request.form; uploaded files are in request.files.
  2. Browser-side checks can be bypassed or changed.
  3. They separate values from SQL structure and reduce injection risk.
  4. The image bytes are saved as a file; descriptive fields such as title and filename can be stored as database rows.
  5. Normal: valid title and image are stored and displayed. Invalid: unsupported file type is rejected with a clear message.