Week 9
- Class
- Source Code
- Problem Set 9
Flask
- So far, we’ve learned how to write webpages that are saved as a file and returned by an HTTP server. But we can also have web servers, or applications, that generate content dynamically before returning it as a response.
- [1:00] We’ll use a framework in Python called Flask, which allows us to write a web server with many features. We’ll create a new folder in our IDE, called
hello/
, and create a new file calledapplication.py
. By reading the documentation and experimenting, we can write our first Flask application which returns something for the/
route. And in our terminal, we cancd
into our folder and runflask run
, which will find ourapplication.py
file and run it. We’ll open the URL, and see our returned string. - [4:10] We’ll add another route,
/goodbye
, and a function that returns different content. We can return any content we want in our routes. - [6:00] It turns out that Flask allows us to use template files, or files with HTML that are like format strings, with some parts that are the same every time, and some parts that will contain variables that we can substitute in. The
render_template
function in the Flask library will allow us to use templates and plug in variables like ``. - [10:35] We can generate a random number, for example, and display it each time our page is loaded. We can use
control + c
to stop our server, and then restart it, to make sure any changes we make are reloaded. And once we load our page in the browser, we can view its source to make sure that Flask substituted our variable as we expected. - [13:25] We can add conditions to our templates, with
if ...
, so depending on the value of our variables, we can return different content entirely. - [16:25] We can even write a form that our server can accept, with another route that the form can submit to. Then, in that route, our server can receive and use the form data. We write a form that has a name input, and write a route function that gets the input with
request.args.get()
, and returns a template with the input substituted in. - [21:30] We see an Internal Server Error, and in our terminal we see the error that
request
is not defined, and it turns out that we need to import it from Flask. We try again, and see that the GET parameters in the URL changes based on what we submit in the form. - [24:00] We can add additional logic in our route to handle the case where
name
is empty, and return a different template. - [26:00] It turns out that we can have templates for our templates, since many of our pages might have similar HTML code around its content. We’ll create
layout.html
, and add a special block inside the<body>
tag. Then, our other files likeindex.html
can use the template withextends "layout.html"
, and only have the content block for thebody
. - [30:35] And we can add additional blocks, like for content we would want to have inside a
<style>
tag in the page. - [32:20] We’ll start writing a new application by creating a new folder called
tasks
, and creating anapplication.py
file. Inside, we’ll create routes for/
to list tasks and/add
to add a new task. We’ll create atemplates
folder with alayout.html
before, atasks.html
showing a list of items, and aadd.html
that includes a simple form. We’ll have our routes render each of these templates, and set our form to use a new method,POST
, to send the form’s data back to the/add
route. Ouradd()
function can then either display the form for aGET
request, or create a new task for aPOST
request. - [42:30] We can create a global variable,
todos
, to store a list of task names that we can display later. In ouradd()
function, if we get aPOST
request with some data, we’ll add the new task name to our list on the server, and redirect back to the default route, which will show a list. - [44:15] And in our
tasks.html
template, we can loop over ourtodos
list variable withfor todo in todos
, and create a<li>
element with the contents set to each item. - [48:00] We can also make sure that the task name is not empty, by adding some JavaScript code that only enables the submit button if the input field’s value is not empty. Otherwise, we disable the submit button. We do this by adding an event handler to listen to the
onkeyup
event for ourtask
input, which is triggered by the browser every time the user presses a key and releases it. - [52:40] But our task list goes away when we stop and start our web server, since we initialize our
todos
variable to an empty list each time. Next, we’ll use a database with SQL to store and modify data.
Databases
- So far, we’ve learned how to write a server that can respond with webpages that are the same for every user. But there are websites where we can log in, and it will show us information specific to us.
- Recall that cookies are small files that websites ask our browser to store on our computer, with some kind of identifier that our browser shows the website the next time we go there, so the website knows who we are. This allows our server to have sessions, or data for users’ interactions with a website, specific to each of them.
- [1:20] We’ll look at the task list application we made last time. Since our task list was stored in a global variable in our server application, everyone who visits our page will see the same list.
- [2:40] To solve this, we can use sessions from Flask, by importing and initializing their implementation. By doing so, our
tasks()
function can look in the globalsession
variable, and read, set, or update atodos
key within it. Flask will take care of making sure that the globalsession
variable is actually specific to the user who made that request, by storing and checking some cookies. - [7:30] If we want to store more complex data, it would make more sense to use a database instead of session objects. So we’ll create a new application to store registration information, like names and emails.
- [9:25] We’ll make a new empty file,
lecture.db
, and runsqlite3 lecture.db
to create a table and set column names and types for the data we think we’ll need. - [11:00] In
sqlite3
, we can run queries to select or insert into the table to check that everything works. In our new Flask application, we’ll import the SQL library from CS50 so we can work with our database more easily, and establish a connection to ourlecture.db
file. In our/
route, we can run aSELECT
query to get the rows from ourregistrants
table, and pass them into our template. Our template will in turn iterate over each row, and generate an<li>
item with the values of each column in each row. - [17:35] Once we have our index route, we can add more rows to our table with the
sqlite3
prompt, and see our server return the new data. - [18:05] We can add a new route to our application that will insert new data, too. In our
register()
function, we can return aregister.html
file with a form that has the inputs we need, and ensure that the form submits to ourregister
route with thePOST
method. Then, in ourregister
route, we can check for aPOST
request, insert the data from the request into our table, and redirect to the main route. In our SQL query, we’ll be careful to substitute our variables safely with thedb.execute
function, instead of combining the strings ourselves, to avoid SQL injection attacks. - [23:05] We’ll try out our application, and everything seems to be working as we expect. To improve the design of our server’s code, we’ll factor out some common template code into
layout.html
, and create anapology.html
page where we’ll tell the user an error message if something in their form is blank. - [28:40] Now we can write Flask applications to read and store data in a database, saving our data efficiently for the long term.
Finance
- We’ll take the concepts we’ve seen to create CS50 Finance, a virtual stock trading website with an account for users to register for, the ability to get quotes for shares of stocks and to virtually buy or sell them. We’ll also have a history page for each account to see what we’ve done in the past.
- [2:45] We look at the distribution code for CS50 Finance, or the code that we’ll all start off with. We have an
application.py
file that our Flask app will run, with various configuration options, a connection to a database filefinance.db
, and routes for . This follows the MVC, Model-View-Controller, pattern, which generally separates the concerns of data and how that’s stored (our database), the views that display some amount of data (our templates), and controllers that control the logic of what is displayed when (ourapplication.py
routes). - [4:45] Since we’re using a third-party API, or Application Programming Interface, some code that someone else wrote designed for us to use, we’ll also need an API key to get stock information.
- [5:30] Notice that our routes also have a
@login_required
decorator, or extra attribute in Python to indicate that the function should behave differently. Flask allows us to automatically redirect users to a login page, and we have the login functionality implemented in our distribution code too. The/login
route checks whether a matching user and password exists in our database (for aPOST
method, as from the login form), or displays the login form for aGET
method. And in our database, instead of storing the user’s raw password, which is more insecure since hackers might use them against other websites, we store the hash of their password which is sufficient for verification, but difficult from which to recover the original password. - [14:30] After the
login
route we havelogout
, which just clears the session, and we havequote
,register
, andsell
routes left to implement. - [15:10] We’ll implement:
register
so we can register for a new accountquote
so we can get a price quote for a stockbuy
to buy some shares of a stockindex
to show the stocks in our accountsell
to sell some shares of a stockhistory
to show transactions in the past- and a personal feature of our choice
- [15:55] We talk about the requirements for each of these routes, and how they might be implemented with conditions based on the request’s method, and either display forms or perform some action after validating the request.
- [20:50] We have an existing
finance.db
database, and we can usesqlite3 finance.db
to run queries that add columns or tables that we might want to use to store additional data to support our routes. - [23:00]
index
will query our database for a user’s stocks and their cash balance, along with using an API to get the current price of each and displaying all this data with a template.sell
, too, should have validation and update our data in the database. - [25:25] Finally, we might need another table (in our database) to support our
history
page, and display the data for each user’s transactions in a table (in our template). - [26:25] And we’ll need to add a personal touch, whether that’s allowing users to change their password, add cash, or additional features.
Conclusion
- In this track, we learned about how computers communicate over an internet, structured web pages with HTML and styled them with CSS, and added some interactivity with JavaScript. Then we learned how to write a web server application with Flask, that can dynamically generate web pages and use a database to read and write data.