CS1999 Buggy Racing tech notes

Technical notes for working on your CS1999 Buggy Editor project

Handle the POST request


Now you have the form POSTing the data to the webserver you have to handle it with Python.

You know where this is being processed: the route (action) and method was explicitly specified by the <form> tag.

So follow the same pattern that is already in place for the qty_wheels.

Here's the existing code. Not all of it is relevant, but that's part of the skill of learning to read code.

elif request.method == "POST":
    message = ""
    qty_wheels = request.form["qty_wheels"]
    try:
        with sql.connect(DATABASE_FILE) as db_connection:
            cur = db_connection.cursor()
            cur.execute(
                "UPDATE buggies set qty_wheels=? WHERE id=?",
                (qty_wheels, DEFAULT_BUGGY_ID)
            )
            db_connection.commit()
    except sql.OperationalError as e:
        message = f"Error in update operation: {e}"
        db_connection.rollback()
    else:
        message = "Record successfully saved"
    finally:
        db_connection.close()
    return render_template("updated.html", msg=message)

First, understand the existing code

You need to look at that while thinking about how it's handling the data you do know works: qty_wheels.

The Python is initialising a variable called qty_wheels to contain the value of request.form['qty_wheels']. That's how the data from the <form> tag is being presented through the request — in fact, the Flask library is doing the work there and it's Flask that has made request.form for you.

The next place that's doing something with qty_wheels is cur.execute("UPDATE buggies...) If you have a poke around in that code you should see how cur is related to con... and con is the name given to sql.connect(DATABASE_FILE).

So this is where the buggy is bring written to the database. That should be what you expected because that's what this POST request from the form was supposed to be doing anyway.

An important point here if you're new to programming is that you can make some sense of this even though you probably wouldn't have been able to write it all from scratch.

Python does try

The try, except and finally are all Python's mechanism for trying to do something, coping if it goes wrong, and then tidying up at the end regardless of whether or not things went well.

Why might it go wrong? Because writing to the database is an operation that is passing control over to another service (in this case SQLite) and a whole new world of problems can exist: Python is actually using another language — SQL — to communicate with it, and there might be errors in that. Or the connection to the database might not work.

Second, investigate the msg code

There's a variable in this code called msg that you should try to figure out.

You can see from the except: line that "error in update operation" is being put into variable msg.

However, if the update went OK, you can see msg gets a success message instead.

Where's msg being used? It's being passed into the template updated.html.

So the next thing is to quickly look in that and see what the template does with it. Can you see how msg is used in that template?

Why is render_template() being called at all? The answer is that although the browser sent data to the server to be added to the database, that is still an HTTP request, so it needs a response... and that response is the HTML page it will show. So updated.html is sent back to the browser and the browser will display it — thereby reporting the success or failure of the attempted operation.

Send a message back to the browser

Before you add the new code: do this test.

Now you know that msg is a string which is shown on the page that is sent back to (and hence displayed by) the browser, you can use that to show debug information too. If you comment out the success message and add an extra line like this, and then submit your form, what do you see in the browser?

# msg = "Record successfully saved"
msg = f"qty_wheels={qty_wheels}"

You should see qty_wheels=4 (or whatever you put in the form). That information is doing a round trip: the browser is sending the qty_wheels number in as a (named) datum in the form, the Python is extracting it, putting it in a message, and passing it back — via the template — to the browser.

Now grab the flag colour and check you've got it

So now you can make the variable:

flag_color = request.form['flag_color']

...and if you put that into the msg string, can you confirm that you are collecting it correctly by passing it back in the message through the updated template?

Note what has happened here: you've made a variable called flag_color and used it to extract the item from the form that was also called flag_color. You could call your variable anything your like, e.g.:

colour_of_my_flag = request.form['flag_color']

..but do not do that here because being consistent about the names here is the sensible thing to do.

You are explicitly seeing the data moving from one system (the HTTP request, where it was named by the browser's <form>) to another (the Python app). You can think of this as the data passing a boundary as it is handed from one subsystem to another.

Inspect the command to the database

If you've checked that flag_color contains the right thing (i.e. you've checked that it's working and your Python has extracted it from the form correctly) you can add it to the database. This is fiddly so be careful with the syntax:

This is the existing code. It's probably a good idea to break it over several lines because it's already quite long (Python is very fussy about indentation at the start of lines, but inside a pair of ( and ) it is happy about you breaking onto extra lines):

cur.execute(
  "UPDATE buggies set qty_wheels=? WHERE id=?", 
  (qty_wheels, DEFAULT_BUGGY_ID)
)

The first qty_wheels there is not the variable name: it is the name of the column in the database. By no coincidence it's the same name as the variable, but they are nonetheless different things.

This is another boundary between subsystems: Python is handing the data over to the database (in this case, it's SQLite).

By using the same name for the same datum on its way through the system you are keeping things understandable. In more complex systems this is not possible but in this case — because you have control over the HTML, Python, and database, you can and should stick with this convention.

Note that the UPDATE command is SQL. It's updating because the default editor comes with a database (if you initialised it according to the instructions!) that contains a single buggy: that database record is being updated each time the form is submitted.

Modify command to the database

The cur.execute() call is from Python's SQLite module. The cur.execute() call has two parameters:

  1. the SQL statement you want to execute
  2. a list of variables whose values you want that statement to use

In this case, each of the ? in the statement will be replaced by the value of the variable in the matching position in the list. For this reason it is critical that you put the right number of variables in that list in the right order.

Here's the new code: look carefully to see how it's been updated with flag_color:

cur.execute(
  "UPDATE buggies set qty_wheels=?, flag_color=? WHERE id=?", 
  (qty_wheels, flag_color, DEFAULT_BUGGY_ID)
)

Check that: three ?s in the string containing the SQL statement, and three items in the list in ().

The DEFAULT_BUGGY_ID is being used here so SQLite knows which record in the database to update. To start with there is only one (can you find in the Python what value it has?) but later — 3-MULTI — you might change this.

Review of what you just did

If you get this right, then when you send data from the browser the request may have contained this form data:

qty_wheels=6
flag_color=blue

then your Python should contain two variables with these names and values:

qty_wheels = request.form['qty_wheels']  # => '6'
flag_color = request.form['flag_color']  # => 'blue'

And then the SQL statement that cur.execute() constructs and executes will be:

UPDATE buggies set qty_wheels='6', flag_color='blue' WHERE id=1;

(If you are an experienced programmer, something to note: all the data coming from the form is effectively a string... and SQLite is not quite as rigorous as other SQL databases about what goes into its Integer columns.)

If that worked, you need to look into the database: we guided you with flag_color here because it was already in the database. But how did it get in there?