- 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 called
application.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 can
cd into our folder and run
flask run, which will find our
application.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 like
index.html can use the template with
extends "layout.html", and only have the content block for the
- [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 an
application.py file. Inside, we’ll create routes for
/ to list tasks and
/add to add a new task. We’ll create a
templates folder with a
layout.html before, a
tasks.html showing a list of items, and a
add.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. Our
add() function can then either display the form for a
GET request, or create a new task for a
- [42:30] We can create a global variable,
todos, to store a list of task names that we can display later. In our
add() function, if we get a
POST 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 our
todos list variable with
for todo in todos, and create a
<li> element with the contents set to each item.
onkeyup event for our
task 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.
- 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 global
session variable, and read, set, or update a
todos key within it. Flask will take care of making sure that the global
session 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 run
sqlite3 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 our
lecture.db file. In our
/ route, we can run a
SELECT query to get the rows from our
registrants 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 a
register.html file with a form that has the inputs we need, and ensure that the form submits to our
register route with the
POST method. Then, in our
register route, we can check for a
POST 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 the
db.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 an
apology.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.
- 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 file
finance.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 (our
- [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 a
POST method, as from the login form), or displays the login form for a
GET 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 have
logout, which just clears the session, and we have
sell routes left to implement.
- [15:10] We’ll implement:
register so we can register for a new account
quote so we can get a price quote for a stock
buy to buy some shares of a stock
index to show the stocks in our account
sell to sell some shares of a stock
history 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 use
sqlite3 finance.db to run queries that add columns or tables that we might want to use to store additional data to support our routes.
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.