Week 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 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 body.
  • [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 POST request.
  • [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.
  • [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 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.
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 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.
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 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 application.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 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 quote, register, and 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.
  • [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.