BRIAN: All right.
So far, we've been able to use Flask to build web applications.
But so far, the web applications we've been creating
are serving the same files to every user.
But a lot of web applications that you might
have interacted with in social media websites or other websites
let you log in.
You log in once-- and then every time you visit the website again,
it somehow remembers who you are.
You're not prompted to re-log in every time.
It knows who you are so it can to display your news feed
or information that's relevant to you when you visit that web page.
How is it that websites are able to do that?
Well, it turns out that many websites use
a technology called cookies, where cookies are just
data that a website can ask your web browser to store on your computer
such that it's stored in your computer.
And the next time that your web browser visits that same website,
your web browser can show the cookie to the website
so that the website knows who you are again.
It's an identifying piece of information such that after you log in once,
some cookie is stored inside your web browser.
And the next time you visit that same website,
you can present that cookie to the web server
so the web server knows who you are and knows what information to then display
to you.
This allows for the notion of what we're going to call sessions--
some data about your current interaction with a website that is specific to you.
And so let's take a look at how we can add sessions to our web application
now.
So what we're looking at here in CS50 IDE
is the same task management application that we created earlier
when we were creating our first web applications using Flask,
where we would create an application that displayed a list of the things
that you need to do.
And then give you a page where you could add a new task to that list of things
that you need to do.
Let's take a look at how this application works, just to recall.
So here's our page with all our tasks.
And if we create a new task, just call it foo, and press Submit.
I'll create another task, call it bar, and Submit.
All right, I have two tasks now.
But the interesting thing about this is that currently this data is just
stored as a global variable inside of our Flask application,
which means that it's shared among all of the people that might conceivably
be using this application.
If another person were to open up this page, which
I can simulate by opening this up in an incognito window, for example,
taking the same URL, pasting it in, the other user
is also going to see that they have two tasks, foo and bar.
And if this other user here on the right creates
a new task called baz and submits it, then the user on the left
will also see the task baz if they are to refresh that page.
And that's probably not what we want for some sort of application that
allows users to maintain their own lists of tasks, for example.
Ideally, I don't want the list of tasks to be a global variable.
I wanted data to be data that is local to someone's session.
That when I log in, I should see my tasks.
And when I'm interacting with the page, I
should be able to add to my list of tasks.
But that shouldn't affect someone else who has their own list of tasks
that they're maintaining on the same web application.
So let's now see if we can implement this by introducing sessions
into our web application.
There are a couple of setup things that I'll need to do.
From Flask, I'll need to import session, lowercase s.
And I'm also going to use a library called Flask Session, where
I'm going to import uppercase S Session, which will just give me access
to some additional features.
After here, there's some configuration steps that I need to do.
No need to worry too much about the details about this.
But in short, what I'm going to say is that session permanent
is going to be false.
I don't want sessions to be permanent.
And then I'm going to add a session type equals file system
to mean the location where I want to store all of the data pertaining
to sessions is going to be any file system of the web server
that I'm running this application from, which in this case is CS50 IDE.
And then the final configuration step is just
to say capital S session, in parentheses, app,
to mean that I would like to enable sessions for this particular Flask web
application.
So what does this mean?
What this means is that I'm now going to have access to a Python dictionary.
Recalling that a data dictionary is just a collection of key value pairs
called session where I can access information
that is going to be local to the user's current interaction with the web page.
And it's not going to be dependent upon what other users have also
added to their own lists of tasks.
What that means is that I can get rid of this list, which
I'm going to call todos.
And instead, inside of tasks, we're going
to use a key within the session dictionary.
So what I'm going to say inside of tasks is this first line.
I'm going to check, is there already a key called--
we'll call it todos inside of the session.
And in particular, if todos is not already in the session,
well, then I want to add it to the session.
So then I want to say session todos, it's just going to be the empty list.
So what are these two lines doing?
Well, these two lines are saying, for the current user,
do they already have a key called todos inside the current user's session
dictionary.
If they don't, then we're going to create a new key inside of the session
dictionary called todos them and set it equal to the empty list.
Meaning initially, the user has no tasks that are currently inside
of their list of tasks.
All right, let's move on.
Now in tasks.html, rather than passing in todos
as just a variable todos, which was a global variable that we no longer have,
now we're going to say the list of things to do is going to be
session, square bracket, todos.
In other words, for the current user session, the current user's interaction
with this website, let's get at their list of things to do.
So that's the function that's going to display the current list of tasks.
But now let's go down here to the add function.
What needs to change here?
Well, we've gotten the current task, the thing to do,
based on whatever was passed into that form.
Request.form.get task means get whatever the user typed in to that task field.
And now what we'd like to do is add it to the current users list of things
that they need to do.
So here we have todos.append todo, where before todos was
a global variable, accessible by all users who
were using this web application.
But now what I'd like to do is make this local to the current user.
And so the way I can do that is, instead of saying todos,
I'll say session todos, to mean, take the current user session,
access their list of things to do, and add to that list
this new thing that I need to do.
So instead of using a global variable, I'm
now using part of the session dictionary in order
to include and store data that is relevant to the current user only.
Let's try this out.
I'll run the web application by running Flask Run.
And now if we take a look at the web application, loading it in both pages
for two different users.
Now if user number 1 creates a new task called foo and then creates
a new task called bar, they still see their own tasks.
But if user 2, if I refresh the page, they don't see those same tasks.
User 2 can now create a new task, baz, for example.
And now they have their own separate list of things that they need to do.
Because this data is no longer being stored as a global variable.
It's instead being stored in each user's individual session.
And presumably Flask is asking the web browsers here
to store some sort of cookie, some unique identifying information
such that the next time you go to this page, that's by reloading the page,
Flask still knows who the user is and can display that user's list of tasks
back to them when they try and view this particular route on the web
application.
All right, so taking advantage of sessions,
we now have the ability to store information
that just pertains to the current user.
But as we start to think about users interacting with our web page,
you might imagine that our web applications are going
to need to store more and more data.
We're going to need to store data about the users,
any information relevant to those users.
And that's going to start to become a lot of information to store.
And there are many ways you could store data.
You could just always store data in the sessions.
But the sessions aren't permanent.
And so eventually you might lose that information.
You could store data inside of a file.
But then you need to know how to open and read that file
and write to that file all the time.
But this is a great opportunity to take advantage of something we've already
taken a look at, which are databases, using a language like SQL that
lets us interact with a SQLite database, for example, in order
to have tables of data, to insert rows into that table of data,
to update those rows, and to select rows out of that table such
that we can write a web application that connects to a database
and that uses the data inside of that database
to display information to people that are using the web application.
And so that's what we'll do next.
Create a new web application that we'll just say
is going to allow people to register for something,
be it registering for a mailing list or registering for a club
or registering for something else.
You can think of this as an application that's going to store information.
And the information we're going to need to store
is data about all of the registrants for the event or for the club
or whatever it is that people are registering for.
And what sort of information might we want to store?
Well, we probably want to store for each registrant their name
and maybe their email address as well.
And so let's give this a try.
I'll go back into CS50 IDE.
And now, let's go ahead and create a new directory for a new application
that we're going to create.
We'll call the application register.
And I'll create a new file.
And this new file that I'm going to create is going to be a database file.
I'll call it lecture.db.
So just create an empty SQLite database.
Now I'll go ahead and cd into register.
And now in order to access this database, lecture.db,
I'll run SQLite3 lecture.db.
And so this now takes me into SQLite where
I can run SQL queries on this particular database.
And so what am I going to do?
Well, the first thing I need to do inside this database is create a table.
So I'll go ahead and run some syntax.
So I'll say create table.
We'll call the table registrants.
And what are the columns that this table needs to have?
Well, presumably, like many tables, it should have an ID column
that will uniquely identify users.
So the ID column will be an integer.
And we'll say that it will be the primary key.
It will be unique.
It'll be the way that we identify every single registrant inside of this table.
And let's also add now a field for their name.
Their name is presumably going to have some sort of type.
And recall that we can use varchar as the name
of a type of a column that store some variable number of characters.
And we'll say we'll store up to 255 characters, which
feels like a pretty reasonable limit for a name.
And then we'll also store an email address column, which
we'll also specify as varchar 255.
And so we've now created a table that has three columns--
an ID column, which is an integer, a name column, which
is a variable number of characters, and an email column, which is also
a variable number of characters.
And here in the SQLite terminal, we can actually try and select this data.
I can say, select star from registrants.
And of course nothing happens because right now there
is no data inside of this registrants table.
But let's add some data.
I'll insert into registrants a name and an email address.
And what values do I want to insert?
Well, let's use the name of Alice, for example,
and an email address of alice@example.com.
So I've run a SQL query to insert into the registrants table a new row.
I've inserted a row with a name and an email
with the values of Alice and alice@example.com.
And now if I go back and select star from registrants,
selecting all of the data from that table, we'll say,
we have Alice who's automatically given an ID of 1, who has
an email address of alice@example.com.
So now that we've created this database file,
lecture.db, which is a SQLite database inside of which
is a table called registrants, let's now create a web application that's
able to actually use that database to read and write data to it
in order to display those registrants and update the list of registrants
if someone new registers using our web application.
So this is going to be a Flask web application.
So the first thing we'll do is create a new file.
And I'll call it application.py.
And again, this is where the Flask application is going to be stored.
From Flask, import Flask-- same as before.
This time, though, I'm also going to add from CS50.
Let's import SQL because I want to use CS50's SQL library, because I
want my Flask application to now have the ability
to interact with the database that I'm about to create.
So beneath that we'll say app equals Flask name, same as before.
And now I want to give my web application access to this database,
lecture.db.
So I'm going to say db, for database, equals, and then SQL, and then SQLite,
:///, three slashes, specifying that this is going to be a SQLite database.
And then what is the name of the database file?
Well, the name of the database file is lecture.db.
And now db is going to give me access to lecture.db.
And it's going to allow me to run SQL queries,
be they selects or inserts or updates, that then are able to get at data
and modify data inside of the lecture.db database.
Next, let's create a route.
I'll create app.route/.
We'll call the route index.
And what I'd like for the route to do is just display to me all of the people
that are currently registered, based on the data inside of my database.
How might I go about doing that?
Well, in order to get the data, I'm going
to need to run a query on the SQL database.
To do that, I can run db.execute.
And then in parentheses and quotation marks,
the name of the query that I now want to run.
In this case, it'll be select star from registrants,
because I'd like to select all of the data from the registrants table.
When I select all that data, the data is going to come back to me.
And I'm going to want to store that data inside of a variable, which
we'll call rows, in this case.
So I'm making a query on the database, running select start from registrants,
which is a SQL query, taking the result, and saving it inside
of this Python variable called rows.
What do I do now?
Well, now I'd like to take this variable rows and pass it into a template
so that I can render the information about who is currently registered.
Let's try that.
So I'll say return render template.
We'll call the template index.html, which doesn't yet exist,
but we'll create it in just a moment.
And say that index.html is going to have access to a variable called rows, which
will be equal to rows, the result of whatever came back from the SQL query
that we ran on line 10.
I'll also need to import render template in order to make this work.
But let's now create a new folder called templates inside of which we'll
create a new file called index.html.
And index.html will be the template that we're going to render.
So DOCTYPE HTML, HTML, head.
We'll go ahead and give this a title and call it registrants.
And inside the body of the page now, I'll
make a big heading at the top that just says registrants.
But now what I'd like is a list of all of the people
that are currently registered according to my database.
How am I going to do that?
Well, recall that UL can be used to create an unordered list.
And inside of that UL, I'd like to loop over
all of the rows that got passed into my index.html template.
So I'll say, for row in rows, and then endfor.
Recalling that this syntax, a curly brace percent sign,
is how I can add a loop into my template.
Say loop over all of the rows that got passed into index.html.
And for each one of those rows, what HTML do I want to add?
Well, I want to add an LI, a list item, that's going to show up in this list.
And I'd like to here plug in the name and the email address
of every person who's registered.
And so every row that comes back to me is
going to be in the format of a Python dictionary,
where the keys are going to be the names of all of the columns--
ID, name, and email.
And I can access the values that those keys
to get at the person's name and the person's email address, for example.
And so rows with all of the rows that came back from my registrants table,
each individual row is just one row that comes back from my registrants table.
And if I want the name that corresponds with that row,
well, I'm going to print out as by using double curly braces to substitute
a value here into the template--
row, and then in square brackets, name.
Say take the current row, get at the person's name, and plug in that value
here.
I may also want to include their email address, which I'll
do in parentheses for good measure.
And then I'll add another set of curly braces
and go ahead and substitute in row, square bracket, email as well.
So now I'm showing a list item for every row in my table,
where I'm saying the row name, and then the row email.
Let's now try to run this web application.
I'll go down into the terminal and run Flask Run to run the application.
And now if I try and load the page, I now see registrants.
And now I see a list--
Alice, and then alice@example.com as the email address
of the person who's registered.
And so if we ever were to update this database,
changing the data inside the database, this list would also update.
And we can see that if we go back here.
I'll go ahead and type in SQLite3 lecture.db to go back to the database.
And let me insert into registrants a new name and email.
This time, what values am I going to insert?
We'll add Bob and then bob@example.com.
And now if I exit out of the database and run Flask Run,
and I reload this web page, well, now I have two registrants.
I have Alice and I have Bob.
So now we have the ability to design a web application that's
able to read data from a database.
We have a SQLite database.
And our route now is reading data from that database
and displaying the information in the database in the form of an HTML list
here.
Let's now add to this and actually build into our web application
the ability to register as well, the ability
to add data to the registrants table instead of just reading data from it.
Let's give that a try.
Inside of index.html, we'll go ahead and add a link
at the bottom that will give me a way to register.
It will be a link that'll take me to /register, for example,
which is a route that doesn't yet exist, but we'll create it.
And the link will just say register.
What should this route do?
Here's app.route/register.
We'll create a function called register.
And for now, let's just go ahead and render a template, register.html.
Let's now create register.html.
So I'll create a new file, call it register.html,
which for now is going to include the same information that was currently
in index.html.
And I could improve the design of this somewhat by factoring out information
into a layout.html file, the way I have previously as well.
So definitely something to consider too.
But here for now, what I really want to do
is inside of the body I'll call the title register.
And here I'd like to create a form, a form that has an input, whose
type is text, and whose name is name.
I want the person to type in their name.
And so that they know that, I'll add a placeholder that says name.
And I'll also have an input whose type is text and whose name is email.
And just so the user knows that, I'll add a placeholder for the input field
that says email address.
And then I'll have an input whose type is
submit, which will give the user the ability
to submit this form after they've typed in their name
and typed in their email address.
What would I like this form to do?
Well, the form's action, when you submit the form,
we'll go ahead and also go to the /register route.
But again, I'm submitting data.
And so oftentimes when I'm doing this, I want to add a request method of post
to say that I would like to submit data via post to my /register route.
So what does that mean we need to do with our /register route here inside
of application.py?
Well, first and foremost, it means we need
to accept multiple requests methods.
We need to not just allow for get requests,
but also allow for post requests to be made to /register as well.
If the request method is get, then we're going to return register.html.
And I'll go ahead an import request up here as well so
that we have access to the request.
Otherwise, though, else, if the request method is post,
that means the user has tried to submit this form.
So what am I going to do?
Well, I'll say name equals request.form.get name,
and email equals request.form.get email.
In other words, if the user submits this form via post,
then I want to look at the information that was submitted inside of that form
and access the user's name and the user's email address,
storing them in these values, name and email.
Then what would I like to do with that information?
Well, I'm going to run a query that is going
to insert this data into the database to indicate that this user is now
registered.
But what does that query look like?
Well, it'll be db.execute.
And then what's the query for inserting?
It'll be insert into registrants.
What columns am I going to add?
Well, I'm going to add a name and an email, and then values.
And then what I'd like to do is provide a name and an email here.
Now you could try and do some sort of string concatenation
to try and plug in the value of the variable name
and the value the variable email.
But you have to be careful about that because you potentially open yourself
up to SQL injection attacks if you're literally
including strings inside of the query.
Whoever called it a SQL injection attack is
where if name or email happens to include some SQL-looking syntax, then
you might end up executing some dangerous SQL code
inside of your database.
So just to be safe, we're going to use placeholders here by saying :name
and :email to say we're going to plug in some name here,
and we're going to plug in some value of an email here.
And what values are we going to plug in?
Well, we're going to plug in a value for name, which is just going
to be equal to name, the variable name.
And for the value email, we're going to plug
in the value of the variable email.
So again here, we're just defining these placeholders called name and email,
where we're saying the value of the name placeholder,
we should substitute with whatever the variable name is equal to.
And same thing for email.
In this case they just happen to have the same names.
So I've inserted into the registrants table a new pair of name and email.
And the last thing I'll do is I'll go ahead and redirect the user back
to the /route, that default route that determines what should be shown,
which is all of the lists of all the current registrants.
Up at the top, to make this work, I'll go ahead and import redirect as well,
since I want to be able to redirect the user after this happens.
Let's give this web application a try.
I'll run Flask Run.
And now I'll reload the page.
And when the page reloads, I now see that Alice and Bob are still
registered.
They're still inside the database.
But I now see this additional link Register at the bottom.
When I click the Register link, I'm taken to the /register route on this
web application where I'm now presented with a form,
somewhere where I can type in a name and an email address, and then submit.
And the placeholders that I included as attributes of these input fields
are now showing up to me so that I know whether I
should type in my name or the email address into which of these two fields.
So we'll go ahead and type in Charlie and then charlie@example.com.
And I'll click Submit.
And OK, I get redirected back to the Registrants page.
And now Charlie is a registrant as well.
And I can confirm this if I go back, close out
of the application, and type SQLite lecture.db
and select star from registrants.
I now have three registrants.
You can't see Charlie's full email address.
But we do see that we have Alice, Bob, and Charlie all inside
of this registrants table.
Let's put a few finishing touches on this web application.
First and foremost, as we mentioned before,
there was some overlap between index.html and register.html.
So I'll first create a new file called layout.html,
which will contain all of the HTML that's
in common between the two HTML files.
So I'll take register.html, paste it in here for now.
And really the only thing that's going to differ between the two pages
is the contents of block body.
So I'll create a body block we're now inside of index.html,
instead of all of this, I can just say we're going to extend layout.html.
And then inside of block body is going to be the list
of all the current registrants.
And then I'll end the block after that.
So, simplifying the code just a little bit.
And I'll do the same thing in register.html where instead of all this
I'll just say we're going to extend layout.html.
And inside of block body, we have register up at the top,
and then the form where the user can actually register here too.
So this shouldn't actually change the way the application behaves, but just
cleans up the code a little bit.
In case we want to make modifications to the layout,
it will affect all of the different HTML pages.
But let's now actually do some changing of the logic of our application.
Because one thing we haven't yet accounted
for is the possibility that maybe the user didn't type in their name
correctly or didn't type in an email correctly.
And one of these two fields is blank, for example.
What might we do then?
Well, let's create an apology.html page, which will just
be an error page where we apologize to the user
if we can't perform the requested action for some reason.
So I'll go ahead in templates and create a new file, which
I'll call apology.html, inside of which we'll go ahead and extend layout.html
again.
And then inside of block body, I'll display
in each one that just says sorry.
And then maybe we have some error message of something that happens.
So inside of a div I'll display the value of a variable called
message, which I haven't yet defined.
But we'll get there in a moment.
And then at the bottom, maybe a link that takes me back home that just says
go back.
So this is an error message page, something
that we're going to display if something goes wrong in the web application,
for example.
And when might something go wrong?
Well, inside of application.py we might find that maybe the user
didn't type in a name.
So I can say, all right, if not name--
in other words, if no name was provided--
then what I want to do is return, render template, apology.html,
passing in a message of, you must provide a name.
And likewise, we can also add another condition that says, if not email--
if the user doesn't type in an email address--
then we should also return the template, apology.html,
meaning some error took place, with a message of you
must provide an email address.
Let's give this a try now.
I'll go ahead and run Flask Run.
I'll load the web page.
And I see my list of registrants.
And now let me try to register.
But let me leave both the name and the email address fields blank.
I'll press Submit.
And we get a sorry message that says, you must provide a name.
Let's go back.
We're taking back to the home page.
I'll try register again.
Let me type in a name.
I'll type in Dave.
Press Submit.
Sorry, you must provide an email address.
All right, let's go back.
Register.
I'll type in Dave.
I'll type in dave@example.com.
I'll press Submit.
And now Dave was successfully registered as well.
And I have this list of all four registrants
that are currently inside of my SQLite database
that I am reading from in order to display them all as individual list
items inside of my HTML template.
So by including SQL queries inside of our web applications,
we now have the ability to write Flask applications that
interact with a database.
That are able to store data inside of a database
and read data from that database in order
to display that information dynamically to users who are visiting our page.
And this really opens the door for a wide variety of possible applications
that we can design.
Any application that has multiple routes,
that store in some sort of data, and that's
using that data to be presented in templates, you can create just
by combining these ideas, using HTML for the templates,
using Flask as the web server that's listening for requests
in particular routes, and responding with information,
and by using a database like a SQLite database
in order to store information that your web application needs access to.
This, with web programming.