[MUSIC PLAYING]
DAVID J. MALAN: All right, this is CS50, and this is week nine,
the week where we synthesize the past several weeks
and the past several languages, including HTML, CSS, and JavaScript
most recently, plus some SQL, plus some Python.
Indeed, the goal for this week is to synthesize all of those materials
into a topic that's broadly described as web programming.
Now to be fair, last week we did introduce a bit of JavaScript,
and via JavaScript can you indeed program,
but it was entirely client side as we used JavaScript last week.
This week we'll reintroduce a server side component
and indeed end by tying together the browser, the so-called client,
and the web server, the backend of any web application.
So to get there, let's consider where we came from.
Last week in order to serve up any of your static web pages,
static in the sense that once you write them, they don't change
fundamentally, they don't particularly take user input
unless you add a little bit of JavaScript,
we use just this simple program called http-server.
Now there are other equivalents of this command on Macs and Windows
and Linux and other platforms, but for our purposes,
http-server the program literally just runs a web server.
And a web server is just a piece of software
that's constantly listening on port 80 or port 443 in terms of TCP recall,
and it's just listening for connections.
And any time your browser connects it looks at the URL
that you visited and maybe some parameters that you
may have provided via that URL, or more generally via a form,
and it then serves up a web page, optionally with some kind of output.
But of course everything last week really was just static.
It was just HTML files, CSS files, and maybe
some JavaScript files or JavaScript within.
But within the requests that your browsers
were sending to that web server, were recall HTTP headers like this.
And so among the things that program http-server was doing,
and among the things any web server was doing,
was analyzing the contents of that virtual envelope, top to bottom,
left to right, trying to understand what is it your browser or your friend
or your family member if you shared your you
URL with them was trying to request.
And if the first line of those HTTP headers was just get slash,
that just means give me the default web page.
And by convention, recall that typically means specifically give me the file
called index dot HTML.
That might have been explicit if you literally typed index
dot HTML into the browser's URL bar, that would even more explicitly tell
the server to give you that file.
But recall we also played with google.com
and specifically its search functionality.
And recall that we manually last week constructed
a URL that looked like this, and then we replicated it
with an actual HTML form, the result of which when submitting it brought us
to a URL of the form HTTPS colon slash slash, www.google.com slash search,
question mark q equals cats.
And last week http-server is fairly simplistic.
It only served static content.
So it was pretty much ignoring things like these URL parameters.
But if you actually want to write a backend web server,
a web application like google.com itself, you
need to be able to analyze the contents of this virtual envelope,
figure out what path the user wants, or route, so to speak.
Slash search would be the route here in question.
And then you have to figure out what the exact HTTP parameters are.
Q, in this case, whose value is "cats."
So someone somewhere has to write code that parses that string, figuring out
that you want slash search and then, OK, let's ignore the question mark.
Let's declare a variable called q, and let's give it a value of "cats."
Like somewhere in the picture there needs to be code that does that.
And today we introduce you to that very code.
And we're going to introduce you to a library called Flask.
And technically speaking, Flask is also called a framework.
A framework is a way of doing things.
It's a way of organizing your code, it's a way
of writing your code that more specifically is
just how you should use this library.
Now why does Flask exist?
And there are alternatives.
Well, Flask exists just to honestly simplify a lot of tasks
that for you and me would just get pretty boring pretty quickly.
Imagine how annoying it would be if you wanted
to implement some new website or some new mobile application for that matter,
and you had to write code that analyzed text like this every time
just to figure out what the user's input is.
Wouldn't it be nice if you can just call a function, the equivalent
of get string or get int, and let some other function written
by someone else look at that text, figure out what the parameters are
and what their values are, and just hand them to you in nice easy to use
variables.
So that is one of the things that Flask does for you.
It analyzes the insides of these virtual envelopes,
figures out what the user input is, figures out
what the route is, like slash search or slash index dot HTML
that the user wants, and makes it more easily
available to you, the programmer.
And it's a framework now in the sense that typically when
you create, as we will today, a Flask based web application,
you're going to typically organize your files and folders in this way.
And this is what I mean by framework.
Frameworks are not just libraries where they have functions that you can call.
They typically have documentation that says, to use these functions,
you should additionally organize your files and folders in this way.
And perhaps the simplest Flask application would have these files:
application dot py, which is where really all of our effort
is going to go in writing code, in this case in Python,
requirements dot text, which is just a simple text file that enumerates,
top to bottom, one per line, what are the other libraries that you
want to use in your application, static, which
is going to be a folder that literally contains
static files like your gifs, your jpegs, your pings, your CSS
files, your JavaScript files, any of the files you wrote this past week,
now they're going to go into this static folder, and lastly templates.
And templates is going to be where more of your HTML goes.
And we'll see what the distinction is there versus last week.
So in short if you want to make a web application,
not a website, a web application that takes user input,
produces user output, maybe talks to a database,
maybe sends emails, maybe does any number of other things,
programmatically you have a web application
and will use this framework here called Flask.
And there are alternatives.
In the world of Python there is a framework called Django,
in the world of PHP there are frameworks like Symfony and Laravel and the like.
In Java, in C sharp, and other languages there are similar frameworks.
So this is just representative of the types of frameworks that are out there.
But it's perhaps helpful to know before we dive into some actual code
that these frameworks tend to implement certain design patterns.
A design pattern is, again, just a fancy way of describing ways
that humans write code.
Suffice it to say over the past many decades,
a lot of humans working initially independently
kept solving the same problems again and again,
and they realized, wow, I'm noticing patterns
in how I'm solving a problem for this project
and for this project and another.
And once human programmers noticed these patterns,
they might formalize them by writing a book,
by writing a blog post or the like, and then
give them a name, a design pattern that they recommend that other people adopt.
Why?
Just because it helps you organize your code instead
of putting all your code in one massive file,
well maybe if we put some code here, some code here,
we can collaborate more effectively with others
and just keep ourselves sane when maintaining bigger and bigger projects.
So Flask implements what's generally known
as an MVC design pattern or paradigm.
MVC just refers to an acronym representing model, view,
and controller.
And these are sort of technical terms of art that at the end of the day
we'll see are relatively simple.
Controller is going to be where you write most of your Python code.
It is the file or files that control your web application.
So in the past, any time we've written a C program or a Python program,
you have in effect been writing controller code.
We just never slapped that label on it.
There's also going to be today what we'll call a view, the V in MVC.
And view just refers to all of the stuff that a human views, the HTML, the CSS,
more generally the user interface.
Anything involving the user is going to be described
as part of your application's view.
But again, any of the code that you, the programmer, write,
that's part of your controller, so to speak.
And then lastly is the so-called model, the M in MVC.
And this generally refers to what technique, what service, what software
you're using for your data.
So maybe that's a SQL database, maybe that's a CSV file.
The model refers to the data that your application is using.
So again, none of these terms are introducing
things we couldn't have done last week or in weeks prior.
All we're doing by introducing MVC as an acronym or a design pattern
is just to slap a label on the approach that Flask,
and in turn many programmers, whether you're
using Python or other languages used when designing web based applications.
There are alternatives but this is among the most popular and perhaps
among the simplest.
So much like we've done with any language,
let's take a look at the simplest possible web
application you might write using this library or framework called Flask.
It turns out if you fill a file called application dot py
with these lines of code, you have just made your very first web application.
Now it's not going to do much of interest, as we'll see,
but this is the minimal amount of code pretty much that you need in order
to write a web application that is a program that once you start
it is going to just be constantly listening and listening and listening
for TCP requests on port 80 or 443 to come in from people's browsers,
your own included.
And then now you have the ability to write
code that responds to those users.
So what do I mean by this?
Well, let's go ahead and see this in action
and start writing some of our own very first programs.
I'm going to switch over to CS50 IDE, which we'll continue using here.
I'm not going to run http-server anymore.
Instead we're going to use Flask which will take the place of that server.
And I'm going to go ahead and let's create a very simple file first.
I'm going to go ahead and, actually let's do this.
I'm going to first create a directory.
So I'm going to create a directory called Hello,
and I'm going to cd into Hello.
And as always, if you open your file browser
you'll see the same thing at top left, but in general I'll
focus on the command line.
And now that I'm in the Hello directory, let me go ahead and create a file.
And I'll save this in Hello, whoops, I'll save this in Hello.
And let me go ahead and call this, let's say,
application dot py, and again, saving it in the Hello directory.
All right if I type ls, I'll see it there and it's currently empty.
So let me go ahead and pretty much copy some of that code from a moment ago.
Let me go ahead and from the Flask library import something
called Flask, capital F in this case, which is just their convention.
And let me go ahead and also import something called render.
Actually let's not even do that yet.
Let's keep this minimalist at first.
In order to turn this file into a full-fledged Flask application that
is my own server, I'm going to go ahead and define a variable called
app by convention.
I'm going to call this Flask function, and I'm
a little weirdly going to pass an underscore, underscore, name,
underscore, underscore.
Now we've only seen that special variable once in the past.
And that, recall, was when I said, well, you should either
call main at the bottom of your Python file to call your main function,
or you can do this annoying thing where you say, if name equals,
equals main then call main.
So no matter why we used that in the past, this special variable,
underscore, underscore, name, underscore, underscore essentially
refers to the name of the current file.
So this is a line of code that says, Flask, turn the current file
into an application, that is a web application that
will listen for browsers' requests.
Now in my file I have to tell Flask what are my routes.
A route is simply a URL.
So slash, or slash index dot HTML, or slash
search, or any number of other paths that you see in today's URLs
on websites.
And to do this in Flask I say app dot route, quote unquote slash.
Now, this is something we've not seen in Python before,
but this is actually a Python feature.
Any time you see an at sign at the beginning of a function like this,
it's what's called a Python decorator.
And for our purposes today, this is just a special way
of applying one function to another.
But let me leave it at that for now and just
say that the convention when using Flask is that at first you
define your route like your slash default route,
then you define a function.
And the function can be called whatever you want,
but by convention you should probably call
it something appropriate for the actual route in question.
And again, humans tend to call the default route your index.
So I'm going to go ahead and call this function index.
And below that, let me just go ahead and do something silly like Hello, world,
literally returning that string.
And let's go ahead and see what exactly happens here after doing that.
Let me go ahead now and in my Hello directory if I type ls,
notice that again, I only have one file, application dot py.
In order to start my server today, because it's a Flask application,
I do not want to use http-server, I instead
want to run the command Flask run.
So Flask is, yes, a library.
But it also comes with a program when you install it
on your Mac or PC or the IDE that lets you start a Flask application as well.
And you'll see some cryptic output here.
But just like http-server, you'll see the URL
of your currently running application.
And if I go ahead and click this URL and open it in another tab, voila,
I see my very first dynamic web application.
It's only printing Hello, world.
And in fact, if I go to Chrome's view page source feature,
notice that it's not even full-fledged HTML.
It's literally text, but that's because I didn't
bother returning any actual HTML yet.
So let's do that.
Let's actually do this more properly and not just return
some arbitrary string of text.
Let me stop the server and let me focus now on doing this.
By default the route called slash I've claimed
means that you should return a file called index dot HTML.
Well let's assume for the moment it exists.
How do I go ahead and return it?
Technically I'm going to go ahead and render
a template called index dot HTML.
And in order to use this function render template,
I need to import it from the Flask library as well.
So Flask again is a library, comes with a lot of functions.
The first one of which is called Flask itself.
That's what activates this as a web application.
Render template is another function whose purpose in life
is to go find a file called index dot HTML, grab its contents,
so that you can then return it.
So it's similar in spirit to using open and read in Python a few weeks ago.
But it's going to give us some other fancy features as well.
But let me go now into my command line, type
ls to remind us that we only have application dot py.
I don't want to create index dot HTML in the same directory.
Flask, recall, has certain organizational recommendations
like this here whereby I should actually put index dot
HTML and all of my HTML files today onward in my folder called templates.
So let's do that.
Let me go ahead and make a directory called templates with make dir enter.
If I type ls now you'll see that I have a templates directory.
Now let me go ahead and create a new actual file called index dot HTML.
Let me store that in my templates, directory and now let me go ahead
and something pretty familiar.
We've done this many times at this point whereby we just
whip up a quick HTML page.
I'll give my doc type of HTML up here.
Let me have my HTML tag.
My language will be English by default. Down here I'm
going to have the head of my page.
Inside the head I'll have a title.
I'm going to call this Hello.
Down here I'll have my body.
Inside the body I'm just going to say Hello, world instead.
So still no progress really from last week.
But let me now go back to application dot py,
remind us that we've just now returned the function call,
render template of index dot HTML, and again, this function, render template,
is going to go open that file, grab all of the bytes inside of it
and return them ultimately via this line.
So now let me go ahead and run Flask run again, let me go ahead
and click on my IDE's URL, voila, same thing.
But now if I open up Chrome's view source feature,
notice now that I've returned a full-fledged web page.
So again, not really fundamentally that interesting,
but I've taken this baby step now toward generating any HTML that I want.
So let's make this more interesting.
I've claimed all along that there is this way of course with web programming
to take user input via the URL.
So how can we go about doing that?
Well, me go ahead and propose this.
Let me go ahead here and write an additional line of code
that does something like this.
Let me declare a variable called, actually let's do this.
After render template, it turns out you can pass in 0 or more named arguments.
And the names of these arguments are entirely up to you,
and we haven't seen that thus far.
Thus far in the past, any time we've used someone else's function,
you had to check the documentation or the lecture notes
to figure out what were the available arguments you
could pass into this function.
But that's not true with render template.
It's a little more powerful.
So if I want to pass a user's name from my application dot py file,
which, recall we're going to start calling my controller,
I can say, give me a variable called name.
What do I want the value of this person's name to be?
Well really what I want it to be is the equivalent
of whatever is after that question mark, right?
The only way, fundamentally, to get user input from a URL, we've seen,
is whatever is after the question mark.
And I don't want q, and I don't want cats,
I want name equals David or name equals Brian.
So how can I do that?
Well, let me go ahead and say this: name equals
request dot args dot get, quote unquote name,
and let me Additionally add request to the top of this file.
So what am I doing?
When I import this other request variable from the Flask library,
this gives me access to the HTTP request and with it any parameters
that might have been in the URL.
And what Flask does for me is it parses that URL.
It figures out what is q, what is cats, what is name, what is David.
Whatever is after the question mark, Flask parses it for me
and hands it back to me as variables, and I can get access to those variables
by calling request dot args for arguments,
dot get, and then the name of the parameter
that I want to get from the URL.
So now if I save this, let me go back to index dot HTML,
and here is why this file today onward is now called a template.
A template, just like in the human world,
is kind of a framework into which you can plug other values.
It's a template that you can base your own work on.
It's like a blueprint that you can base a building on.
So templates typically have special syntax
via which you can plug in some values.
And I'm afraid the syntax is slightly new versus past things we've seen,
but if you use two curly braces, as though one weren't
bad enough in C and in other contexts, two curly braces, left and right,
you can tell Flask's render template function to plug
in right there the value of any variable that you
have passed into render template as one of these arguments in the function
call.
So let me go ahead now and run Flask run again to restart my web server.
Let me go ahead and open this URL, and it looks pretty stupid right now.
Hello, None.
But recall that None is a special value in Python.
It means something has no value.
So you know what I could do?
It turns out that, let me go back over here, if I go back to my URL,
and let me zoom out so you can see it, let me go ahead and add a slash,
question mark, and not q equals cats because that's irrelevant now,
but how about name equals David.
And let me zoom in on that.
All I've added is the slash question mark name equals David.
Let me go ahead and hit Enter, and voila, now I
have a web page that says Hello David.
And indeed if I view my page source, notice the HTML
has been dynamically generated.
There is no file on my IDE that says Hello comma David,
rather it's been dynamically plugged in.
And notice this can change.
If I go ahead and change the name to Brian, enter,
his page changes, and of course if I view the page source now
it's as though I had a file called index dot HTML
that literally had Brian's name in it.
But no, there's just that placeholder within my template instead.
And I can clean this up.
Notice it looked pretty stupid if there was no value for name,
but it turns out the request dot args, dot
get function takes a second optional argument.
If I don't know if there's going to be of value
and I want to give it a default argument,
my second argument to the get function can actually be the default value.
So if I go ahead now, and let me go ahead rerun Flask.
And let me go ahead now and reload with no name argument.
Now you see a default value of Hello comma world, but if I go back up there
and put my name back in, now it doesn't need the default.
I see what the human actually typed in.
So what's going on here, well, if you consider
what google.com is doing when you type in cats and hit Enter,
that word q equals cats is being passed to google.com in the URL,
Google is running some program, maybe it's Python with Flask,
maybe it's some other language that's analyzing the URL,
grabbing q equals cats, and then they're searching their database essentially
for a keyword of cats, and then they're dynamically
generating the HTML that shows you all of those pictures and search
results of cats.
There is no web page, there's no HTML file on Google servers
that constantly has a big list of cats.
Like there's certainly no human maintaining a really big HTML
file just filled with image tags and cats all day long.
Suffice it to say that's all dynamically generated.
And even with this trivial example, we see now
perhaps that we're scratching the surface of that very capability.
All right, let me pause here and see if there are any questions or confusion,
because it's a lot all at once given that with any framework
you typically have to learn the conventions first
and then you can start to be productive.
BRIAN: Yeah, so in your, when you called the render template function,
someone asked why don't you need to say it's in templates slash index dot HTML,
and why did you just say index dot HTML?
DAVID J. MALAN: Good question.
This is one of those "just because" answers.
The render template function has been implemented in such a way
that it assumes that your files are in the templates directory.
You can technically override that by reconfiguring the application,
but the default and indeed the convention
is to just put those files in templates.
Good question.
Santiago?
SANTIAGO: Why when you declare the app, app dot route,
you have to include the forward slash as an argument to that?
DAVID J. MALAN: Yeah, so why do you have to include the forward slash?
That is the way, in Flask, of telling Flask, use
the following function for this route.
And slash is perhaps the simplest, certainly most basic route
that you could define.
It's sort of the absence of any other words.
So you have to put slash there because otherwise the server won't
know what to do if you visit something dot com, slash, and that's it.
We can change this notice.
I can change this to anything I want.
Let me stop my server, and let me change this to slash secret, as though I'm
creating a secret URL on my website.
Let me go ahead and rerun Flask run.
Let me open up this URL, and notice what happens.
Nothing happens.
Not found, 404 when I visit slash.
And Chrome is just being annoying.
It's hiding the slash just for simplicity these days,
but the slash is there even though you're not seeing it as the human.
But if I change this and go to slash secret, then we see that page again.
So that app at app dot route function just
lets you define what route should be associated with the following function.
Other questions?
Yeah, over to Sophia.
SOPHIA: The model, is it a part of the HTML file now?
Like the name, I guess as the data, is it encoded with an HTML file?
DAVID J. MALAN: Yeah, at the moment I would
argue we don't really have a model yet.
Like there's no database, there's no CSV file,
so right now we're just playing with C, controller and V, view.
And that's totally fine.
It's only when we have a full-fledged application like we soon will today
that really the M comes into play.
But again, this is sort of, reasonable people might disagree.
They're just conventions, they're not hard, fast rules.
All right, so this is a little silly, that in order
to be greeted by my website you have to have the wherewithal
to know that you have to type your own name into the URL bar, right?
No one does that.
That's just not how the web typically works.
We instead find ourselves as humans filling out forms.
So let's take this one step further and actually improve things in such a way
that we actually have a form instead.
So let me go ahead and do this.
I'm going to go ahead and create another file.
Let me go ahead and do this.
Let me go into my templates directory where I currently
only have index dot HTML, and let me just copy this file,
index dot HTML, into another one right now called greet dot HTML.
And that is to say I want my greet file, ultimately, let me go ahead
and open this in my editor, greet ultimately
is going to do the job that index was a moment ago.
And I'm going to change the behavior of index
dot HTML to instead have an actual form.
So I'm going to go ahead and delete this and I'm
going to do a form action equals, a route
called slash greet, which doesn't exist yet, but I wager it soon will.
I'll use get initially, and then inside of this form
let me go ahead and give myself an input whose name is going to be literally
name, because I want the human's name.
So the red name is the tag's name.
Name in green at the moment is the name of the human in question,
although a little confusingly.
And then let me go ahead and say the type of this field will be text.
And then let me go ahead and give myself an input type, equals submit,
so I have a submit button, and the value of that
will be whatever the default is.
So now let me go back to application dot py,
let me go back to my Hello directory.
Make sure you don't run Flask run in your templates directory.
Make sure you only ever run it where the application dot py file is.
Let me run this file.
Let me go ahead and open it now, and voila.
My slash route, the default route notice,
has changed to be this HTML form.
And if I look at the view source of that in Chrome,
you'll see exactly what I just typed.
So there's nothing dynamic about the form.
That is indeed hard coded.
There's no placeholders there.
But notice that this form has been designed in advance by me to go
to the slash greet route using get.
Well how do I implement that?
Well let me actually go into my application
now, because you'll notice if I type in my name, David, and click Submit,
not found.
Because notice that slash greet does not exist.
That is not a defined route in my application.
So let me go back to my server, and let me go ahead down below,
and let's just do something similar.
App dot route, quote unquote, slash greet.
So let's give myself a second route.
Let me go ahead and define the function that
should be called when a user visits slash greet.
And again, you can call it anything you want, but let's keep ourselves,
let's keep things simple and just call it the same thing
as the route, though again, that's not a requirement.
And then in my greet function, well what do I want to do?
Let me go ahead and say, return "to do."
I haven't done it yet.
But again, as in C and in Python, take baby steps,
and just make sure the basics of your code are working so far.
Let me rerun Flask run.
Let me open my URL.
And now let me try filling out this same form again.
And actually I'm a little annoyed that the autocomplete is popping up,
but we can turn that off later.
Let me go ahead and click Submit, and voila.
Now the greet route does exist.
Notice that slash greet is fully functional.
It's not a not found anymore, but it's of course not doing anything useful,
hence the "to do."
Well that's OK.
Let me go back here and stop the server.
And let me instead have greet render a template called greet dot HTML.
But recall that greet dot HTML is where I started.
It is the file now that has this placeholder.
So I think what I can do is just move the code that I was using earlier
for index which no longer needs it, because index
dot HTML has only a hardcoded static form, let me go ahead
and just move the name parameter to my greet route instead.
Let me go ahead and restart my server.
Let me go ahead and open my URL.
Let me go ahead and type my name in and hit submit, and voila.
So now we have two fully functional routes, slash and slash greet,
the first of which just so happens to only display
the static form, the second of which actually happens to do something
more interesting and greet the user.
And I mentioned a moment ago, I was kind of annoyed by the user interface
here, the fact that it's remembering who typed their name in before,
I just, rubbing me the wrong way, leaks a little bit of privacy.
So let me actually go into my index and recall
that there's other attributes in HTML like autocomplete equals off.
I can also do autofocus to give that textfield the blinking cursor
by default. And now let me go ahead and restart
my server after making this change.
Let me go ahead and reload.
And you know what?
We can do a little better.
Let's also add one of those placeholder, quote unquote name,
just so that it's a little more clear to my visitors
that, oh, you want me to type my name, David, here.
Autocomplete is now off.
I click Submit, voila.
We now have a working, if simple, web application.
All right, so what were the additions there?
Same exact application as before, but we added a second route
and a second template so that one form could submit data to the other route
instead.
So let me pause here to see if there's any questions or confusion before we
continue to iterate on this.
Questions or confusion.
SPEAKER 1: I'm wondering how that came equals double quote name,
is that equivalent to the name that you later specified?
DAVID J. MALAN: It is.
And I'll demonstrate this by changing that now.
I called it name just because we're indeed talking about humans' names,
but I could call this anything I want.
So for instance in my form I'm collecting this as the person's name.
But I could change it to first underscore name, for instance,
if I only care about their first name.
Let me change the placeholder to make clear
that I only want their first name.
Let me now go to my application dot py.
Let me get the first underscore name HTTP parameter, and then
in my template, greet dot HTML, let me change this to first underscore name.
And actually I need to make one more change.
I need to change this to first underscore name as well.
So it's a little annoying that you have to repeat yourself all over the place,
but they mean different things.
In the context of application dot py, this is a parameter
that I'm passing into my template.
In the context of this get function, this is the HTTP parameter
that I'm grabbing from the URL.
And in the context of my template, this refers
to the first of those, which is the argument I'm
passing into render template.
And if I reload the server, and let me go ahead and reload the form here.
Let me type in only my first name.
Click submit.
A whole bunch of things changed, not in terms of output
but in terms of functionality.
And you'll see that now I'm using first underscore name instead of name itself.
Other questions or confusion about routes or parameters
in the URL, templates or otherwise?
No?
All right, well let me ask a question then of us.
Let me go ahead and scroll this down just a bit to make room.
Here, again, is my index dot HTML file.
It contains a form.
Here now is my greet dot HTML file, which contains just,
hello comma so and so.
What looks poorly designed about this?
I'm going to go ahead and revert just to my shorter names just so a little more
fits onto the screen at once.
But what looks poorly designed about this?
I claim that it's correct.
We seem to have a working web application.
But what is poorly designed arguably?
BRIAN: Let's hear from Peter.
SPEAKER 2: Well I guess you're getting it,
so the URL is exposing the personal data of the input, whatever
they might have put in, so I guess we'd want to use post instead to hide it.
DAVID J. MALAN: Good catch.
So all this time I have very deliberately but a little worrisomely
been leaking information in the URL in the sense
that now it's probably going to be saved by my browser, right?
Often when you're typing something to your URL bar,
you can see what you've searched for before,
what websites you've been to before, and that's a good user interface
feature in that it just helps you type your keystrokes because you can just
hit Tab and hit Enter to finish your thought quickly.
But it's a little invasive if you don't want people knowing where you went
or what you searched for or if it's a lab computer
or your siblings are using it too.
There's a lot of reasons why you don't want your typed input ending up
in that URL bar.
And I proposed last week that we can avoid this by using,
what you proposed is post.
And indeed let's do this.
Let me go back to my index dot HTML.
Let me make a simple change in my HTML file, changing the method to post.
Post is going to be almost the same as get,
but instead of putting my parameters in the URL
like q equals cats or name equals David in the URL,
it's going to instead metaphorically put it lower, deeper
inside the virtual envelope.
So it's still being sent from the browser to server,
but it's not going to get remembered by the browser in the URL bar.
But if I do this I need to change my controller.
I need my Python code to look in a somewhat different location
to get at that.
And it's a pretty simple change.
Instead I have to do this.
I have to tell Flask that the route, slash greet,
is actually going to support a different set of methods.
Rather than supporting get, which is the default,
I have to pass in the somewhat cryptic argument called methods.
And then I have to pass in literally a Python list of the methods
I want this list to support.
By default if you don't use this argument, all
of these routes, the first and the second,
have essentially this default value, methods equals,
quote unquote get in a list, so a list of size one.
It would be a little annoying if you had to type that all over the place,
so the default just allows you to just type nothing at all.
But if you want to support post, you do have
to override this and change the get default to post explicitly.
And you have to get the parameters from a different variable.
Instead of using request dot args which refers to the arguments in the URL,
you have to change it to request dot form.
These are horribly named.
In Flask, these global variables, request dot args and request dot form
refer to get and post respectively.
Better names might have been request dot get and request dot post,
but this is what we have.
So request dot args is for get requests in the URL.
Request dot form is for post requests where the same info is buried deeper
in the virtual envelope.
So after those changes, let me go ahead and make,
sorry not make, wrong language, Flask run.
All right let me go ahead and open my URL, and voila.
I'm going to type in David, click Submit, and now notice the magic.
Now the route is still slash greet, but no one
who uses my laptop later is going to know what my name was
or what my Google search was or what my credit card number was
or anything else that I might have typed into a form here like my name.
But the output is still fully functional.
So this indeed, to be clear, is post is what
you would use whenever you're collecting anything remotely personal
like people's email addresses, perhaps, or their credit
card, or their passwords, certainly, and other values,
but you can otherwise use get as we've been using thus far.
Well let me ask a follow up question, because I
think we can still do better than this.
There's something fundamentally about the design of index dot HTML
and greet dot HTML that feels a little suboptimal.
And I dare say several of you noticed this same problem in this past week
when you were just creating a few HTML files on your own.
What was a little tedious or annoying or messy or poorly designed
as best you could tell from creating these several HTML
files for your own personal home page?
BRIAN: The chat is suggesting you've repeated some HTML between the two HTML
files.
DAVID J. MALAN: Yeah, my god, I mean honestly by the second file,
third file, fourth file, you're probably just copying and pasting
your previous files and then making minor changes,
and that's all you could do.
With HTML and CSS alone and even JavaScript,
you can't share HTML across multiple pages.
Your only option this past week when serving static content
was to copy and paste that content redundantly.
And look at this.
In my index dot HTML file, everything I've just highlighted from line 1 to 7
happens to be exactly the same as everything from line 1 to 7
in greet dot HTML.
So here's another feature you get with Flask or any web programming framework.
These aren't specific to Flask, per se.
You get the ability to factor out common content.
And so how do we go about doing this?
Well, let me go ahead and do this.
Let me go ahead and create a third file that by convention in Flask
is called layout dot HTML.
And to be safe, to be sure, I'm going to put this in my templates directory
because again it's an HTML file.
I'm going to go ahead and copy paste all of that same boilerplate, if you will,
all of the commonalities, and delete what was specific to greet dot HTML.
And I think what we're looking at, lines 1 through 10,
is now like a template for, if you will, the layout for my other two web pages.
So you know what, let me go ahead and do this.
Let me use some special syntax and say block body, and then, a little weirdly,
end block here.
So this is Flask specific syntax or technically
Jinja specific syntax, which is a language that Flask
is using which someone else wrote, this is special syntax in Flask that
essentially says put a placeholder here.
Put a placeholder here so I can plug in other HTML in just a bit.
So how do I use this now?
Well let me go to my index dot HTML file,
and let me get rid of all of the redundancy,
boiling down index dot HTML really into its essence.
The only thing that's special about index dot HTML was that form.
So if I want to use the same layout that I just created, let me go ahead
and say up here using that special syntax, extends layout dot HTML,
and then down here let me say, here is my body block, if you will.
Let me un-indent that a bit.
And then let me say down here end block.
So again, syntax is weird, but the ideas are pretty similar.
This is almost similar in spirit to our header files
in C, where you could factor out some commonalities
and just reuse them in multiple places.
Here it's a little fancier because you can kind of
have this blueprint, this layout, literally
that you can then plug different content into.
So this first line says, hey Flask, the following file, index dot HTML,
essentially inherits from, it extends my default layout.
Well what's that layout?
It's this.
Notice that this layout defines this placeholder, arbitrarily called body.
I could have called it x or y or z, but I'm going to call it body.
Why?
Because really it's like 100% of the contents of my body.
So seems like a good name.
And then in index dot HTML, I just now need to tell Flask,
here comes some code that you should plug into that placeholder.
And you can plug one or more lines of HTML code in here.
So let me go ahead and save this.
Let me go ahead now and go to greet, similarly delete all that redundancy,
and now let me go up here and say again, extends layout dot HTML,
and then down here let me say here is my body block.
Let me just un-indent this slightly, and then let me say end block
and save that file.
So now these HTML files are quickly looking weird.
Like there's barely any HTML in here, it's
really just text and weird Flask syntax, same thing in index dot HTML,
but again, this is one of the values of a web application.
Where you have a programming language like Python,
you can factor out those commonalities and really
start generating pages dynamically just like the Googles and Facebooks
and others of the world do every day.
So let me go ahead now and rerun Flask run
and cross my fingers as always because I made a whole bunch of changes here.
Let me open my URL, and voila, seems to be working OK so far.
Let me go ahead and, before I submit this, let me show you the page source.
Notice that it's there it's not quite as pretty printed,
like the indentation's a little off, but that's OK.
Your templates should be well styled.
Everything should be beautifully indented in your templates.
But if your templates are then rendered by Flask
and the whitespace isn't quite as pretty,
that doesn't matter because again, the browser doesn't care.
But indeed, I have a full-fledged complete web page.
Let me submit this form.
It seems to still work.
That's my slash greet route.
I'm seeing Hello, David, and if I view page source here,
notice another confirm form submission.
This is actually a safety feature.
You don't want websites tricking you into like checking out
twice with your credit card via post, so I'm
going to go ahead and click reload manually
which confirms that I want to resubmit this form because there's
nothing dangerous about it.
There is now my greet route.
So this probably would have saved you all a lot of headache or tedium
or copy paste, you just didn't have this tool in the tool kit last week.
You cannot do it with HTML and CSS or even JavaScript alone in the browser.
So this is where you have templates really shining.
Not only can you have templates use two curly braces
to plug in values of variables, you can use this curly brace percent sign
syntax and plug in actual contents of other files.
Whew.
Questions or confusion?
BRIAN: There was a clarification question.
Could you tell us what is [? Jinja? ?]
DAVID J. MALAN: Yes, so I skirted over that because I
was getting annoyed at just like how many new terms there
are today and frankly in the web programming world
that we're sort of stuck with.
So this is a good example of code reuse.
There's a lot of smart people out there, a lot
of people solving different problems.
It would be a little annoying and a little arrogant if all of us
tried to solve all of the world's computing problems.
And so the authors of Flask decided that for their template feature
they would not reinvent the wheel and come up with their own syntax.
They would use someone else's existing crazy syntax, if you will,
that uses the double curly braces and the curly brace
and the percent signs so that they're just combining
one person's language with another.
And this is so common.
And this is a good thing because technically you
can plug in a different language if you really are anti-Jinja for some reason,
you just hate the syntax, you can actually use something else.
And so this is actually a demonstration in computing
of really a component based design where you
can plug different pieces of software together and have
them still interoperate.
And we'll add to the course's website links to Jinja's documentation,
and we'll see in a little bit what more you can do with Jinja in the template.
So again, Jinja just refers to really the double curly brace syntax
and the curly brace percent sign syntax that appears in our templates files.
All right, one last feature when it comes to saying hello.
Thus far in application dot py, I've been
keeping things a little conceptually simple if complicated syntactically
by having two separate routes, one route, slash, for my form,
and then another route for my actual greet route that
actually displays the information.
But strictly speaking I can be a little clever
and combine these routes, if only because if you're
building a pretty sophisticated website that's got lots of routes,
it's kind of annoying if like every route needs two routes if one form has
to submit to another.
So let me just propose that we tighten up our implementation
and actually reuse our one route.
So let me go ahead and delete my greet route,
just because it's bothering me that I'm getting a little redundancy there,
and let me go ahead and say that my default route, my index,
will actually support both methods, get and post.
So I'm going to use the same route, not only to show the user the form,
but also to say hello to the user as well.
So I need to be able to support both get and post on the same route.
So how am I going to do this?
Well, let me go ahead and do this.
What I can do is if request dot method equals equals get,
then I can actually go ahead and execute this line of code here.
Else if request dot method equals equals post, then what I'm going to do
is this, return, render template, greet dot HTML,
and I'm going to pass in a name of request dot form
dot get, name, and then a default value of world.
So nothing new.
That's just the code that I deleted a moment ago.
But notice now I'm reusing the same route,
and I'm just checking logically in my controller code,
if you will, well if the method came in as get,
go ahead and just display the form.
If the method came in as post, go ahead and greet the user.
Now why is this going to work?
Well it turns out, whenever you visit a URL on the internet,
like HTTP colon slash slash, www.harvard.edu or yale.edu
or google.com, you have always been making get requests.
In fact, recall last week when we looked at those sample HTTP requests,
and the headers, everything we did last week
had the keyword get by default in the envelope.
So get does not have anything fundamentally to do with forms,
it's actually the default HTTP verb that's
used whenever you just visit any URL.
Any time you visit a URL on the web you are using get by default.
When you submit a form, you are potentially using get as I did first,
or you are using post as I did second.
So what's the takeaway here?
Well let me go ahead and make one change to my form.
I do want to submit via post.
I'm going to submit though no longer to slash greet
but just to my default route.
And if I did everything right here, let me go ahead and run Flask run,
let me open up my URL as before, and I'm at my slash route.
Notice if I view my page source, the action is going to be slash also,
so I'm sort of using the same route for two different pieces of functionality.
Let me type in my name, click Submit, and voila, amazingly, it worked.
Hello, David, but my route has not changed.
Now, why is this why is this useful?
It may or may not be.
It's just the capability you now have because if you can express yourself
programmatically in Python, you can distinguish between the two verbs
get and post that are inside of your incoming HTTP request, and if anything
this just means you don't need twice as many routes just to have one thing pass
to another, and it turns out there's also
other features that derive from just being able to reuse the same routes.
But more on that down the road.
All right, any questions then on saying hello, Flask, or these routes?
No?
All right.
Well just as a teaser here, here is a screenshot
of pretty much the very first web application I made back in the day.
When I was an undergraduate, I got involved in intramural sports,
not in an athletic sense but in a computer science sense.
I volunteered to actually build the very first website for Harvard's freshman
intramural sports program, otherwise known as Frosh IMs.
At the time we were using paper to write your names down on and email
addresses down on, your choices of sports,
we would walk across Harvard Yard, the grassy area in the middle of Harvard,
slide it under the door of one of the proctors or resident advisors,
and voila, we were registered for sports.
But there was no website.
There was an internet but there was no website yet.
So this was late 90s.
So I thought it would be fun to implement
that same idea of a piece of paper for registering
for sports in a web browser.
And now unfortunately I only knew C and maybe some C++ at the time and a couple
of other languages from other CS courses.
I knew nothing about web programming.
So I taught myself how to web program with the help of lots
of questions and answers from friends.
I happened to use a language called Perl, and the result at the end
was this frightening design here with lots of menu options
up top that use JavaScript and some images here
and some CSV files essentially on the backend that
stored all of the registration data.
I knew nothing about databases at the time.
But it was my first foray into web programming.
And what we thought we'd do after our first break
here is reimplement the idea of a registration system using HTML, CSS,
and Python and ultimately SQL to reimplement the same idea,
worrying even less today about the aesthetics.
But let's go ahead and take our first five minute break here.
And when we come back Frosh IMs.
All right, we are back.
So the goal at hand is to implement the most primitive of features
that I first implemented via this Frosh IMs
website years ago, that of allowing students to register for sports.
And to register for a sport, let's collect everyone's name and maybe
the sport for which they want to register.
So how are we going to do this?
Well, let me go ahead and copy just the layout from our previous hello examples
just because it's pretty good boilerplate.
And I'm actually going to make one change.
It turns out that it's very easy to make your website pretty mobile friendly
just by adding a couple of lines of code.
In particular in my head here I'm going to go ahead and add a tag called meta.
It's going to have a name of viewport.
Viewport refers to the rectangular region
that defines most of your web browsers' user interface.
The content of this meta tag is going to be initial scale
equals 1 and width equals device width.
And this last keyword is kind of the magic.
This incantation here essentially tells the browser
whatever the user's, whatever the width of the user's device
is, assume that that's the maximum width for my web page,
and it's going to scale my font sizes automatically
up or down based on whether you're using a laptop or desktop or iPhone
or Android phone or something else altogether.
There's other things you need to do to make your websites responsive,
so to speak, and it's helpful to use libraries like Bootstrap for that,
But this minimally helps at least address issues of font size.
So I'll start including that here as well.
Now let me go into my application dot py, which unfortunately for Frosh IMs
is empty.
And indeed to be clear, over the break I created, application dot py is empty,
a templates directory, and inside my templates directory
is just that layout dot HTML.
So we've got a little bit of work to do here together.
So let's go ahead and start building out this application.
Let me go ahead and import from Flask the Flask function itself
plus render template which I bet we're going to need,
and then also request which we're probably going to need as well.
Let me initialize the application with this Flask function using
underscore underscore name.
So again, that's literally how we began earlier.
And let's just go ahead and start defining our first route.
App dot route, quote unquote slash.
Let me define a function called index, but again I could call it anything,
but that's good convention, and just to get
me started I could really do this quickly and say return "to do,"
but I'm kind of confident now in my ability
to at least use render template.
So let's at least render a template called index dot HTML
that admittedly does not yet exist.
So let's make it exist.
Let me go ahead and create a new file called index dot HTML.
And again I could call this whatever I want,
but if this is going to be my default route,
I might as well call my default page the most common default
name, which is index dot HTML.
Let me store it in my Frosh IMs folder and my templates folder
so that it's in the proper place.
In here, let me go ahead and say that this extends my layout dot HTML.
And then down here let me say my body block will be whatever's in here,
and then my end block will close that.
In here, now I'm feeling a little uncomfortable
with how much code I've written so let's just put a "to do" there,
and let's make sure this whole thing works.
So let me go back into my directory, do Flask run, enter.
All right, go ahead and open up this browser tab, and it just says, "to do."
So I think I'm in a good place, and indeed
if I view page source I see a full fledged web page,
including that meta tag we just added, but with just a big to do here.
Now what do I want to do?
The biggest thing I want to do is actually
have students be able to register for a sport.
So let's go ahead and define ourselves an HTML form via which to do this.
So inside of my body block in my index dot HTML template.
Let's go ahead and first just call this the registration page with an H1 tag,
and then let me go ahead and give myself a form.
The action will be, I mean, the sky's the limit, let's keep it simple
and just do slash register rather than complicate things
by using the same route again.
Let's keep things a little private, so let's do a method of post
so that my roommates don't know what sports I'm registering for or not
registering for.
And then in here let's go ahead and ask the user for their name.
So let's go ahead and do an input, name equals name,
where the name of this input is going to be name in the human sense.
The type of that box will be text, and let's add some of the fanciness
as before.
Autocomplete equals off, or oof.
Autofocus, whoops, autofocus, and then over here a placeholder
so that it's self-describing of name.
All right.
And then let me go ahead and give myself a submit button,
input type equals submit, and let's add a value of register,
just to make the website a little more user friendly.
Let me go ahead and stop my server and restart it
so I can reload all of these changes, and let
me go over to this URL again, and voila, we have the beginnings of a form.
It's only asking for name, so it's not quite there.
So I need to ask for some sports as well.
Now there's a bunch of ways we can do this,
and even though you might not have seen them all yet in CS50,
you've certainly used them on the actual internet when you visiting websites.
So let's do a drop down menu initially.
Let me do what's called the select menu because you select an option, the name
of which is going to be "sport."
Select menus if you read the documentation
or follow an online tutorial, you'll see that they
must have children called options.
And children must have values.
So for instance, let's use a value of how
about "dodgeball," which is a common intramural sport.
And then inside of the option tag, you actually
have to say, much like a link, what the human should see.
So I'm just going to redundantly say dodgeball here too.
And let me go ahead and make that my life a little easier here.
Let me, "to do," "to do," just to speed things up.
Let me do a little bit of copy paste here.
And how about we change this one to flag football, flag football.
Over here let's do, say, soccer and soccer.
And again just like links there might be this duality, the same values,
but they don't have to be the same.
But for now, we'll keep it simple.
This is what the computer will see.
This is what the human will see.
And then down here we'll call this ultimate Frisbee and here
the same thing, ultimate Frisbee.
OK, so now I have a working drop down menu.
So let me go ahead and prove as much by restarting my server,
visiting my same URL, and voila.
The user interface is kind of ugly, definitely very minimalist,
but if I now type in my name David, notice that by default dodgeball
is selected because one of those options should be, but I can actually fix that.
Let me, I don't like how I'm forcing everyone
to think they have to do dodgeball.
Let me do an option here with an empty value for instance.
And I can even say something up here, for instance,
like let's just say what it is, sports, so, with no value.
So it's not an actual valid option.
Let me go ahead and open my URL now.
OK, so now I see sport in the dropdown too.
So David, sport, OK, it's a little weird that you can register for sports.
That's not really a thing.
So let me fix that.
And it turns out if you read the documentation for a select menu,
you can actually disable the option and also, let's say, select it by default.
So these are just additional attributes that you
would discover in the documentation for the option tag that
now let me make the user interface a little more robust just so that users
aren't registering for "sport."
Let me go ahead and do David, and now notice, sport is selected by default,
but it's grayed out.
It's not a valid value anymore.
So minor user interface improvement.
Dodgeball sounds amazing.
That was not an intramural sport in my day, but it apparently is these days.
So let me go ahead and register for this.
OK, but, of course, not found.
Well why is that?
Well if we look at the URL I just ended up at,
I've not implemented my slash register route yet.
All right, so let's do that.
To do that, I need to go back into application dot py, and let's go ahead
and do some registration trickery.
App dot routes slash register, I'm going to define
a function that I might as well call register,
but I could, again, call it anything I want.
And in this function let's keep it simple initially.
Let's just go ahead and return a template called success
and just presume that the registration was successful.
And in fact, let me go ahead and create a new file called
success dot HTML in the Frosh IMs templates directory,
and let me make sure that also extends layout dot HTML,
and that it has a block body, and, for good measure, end block.
And then down here, I'm going to keep this so simple for now.
You are registered, exclamation point.
Not really registering anyone for anything,
but I'm going to claim that they were.
So now if I rerun my application, and I go back to my form here,
and I now re-register David for dodgeball and click Register, huh.
Now it's a bug.
And let me pause here just to see if the pieces are fitting together.
Method not allowed, even though I'm at slash register.
Any thoughts on how to fix this?
Method not allowed?
And notice, you can even see in my terminal window
in red an error message, status code 405 which means method not allowed,
which is a rare one but I indeed screwed up.
Yeah, over to Santiago?
SANTIAGO: Yeah, I think that when you defined
the app dot route for read, or register, [INAUDIBLE]
you didn't specify a methods is post.
DAVID J. MALAN: Yeah, so by default my methods are all get,
but if I want to use post for privacy's sake,
recall that I just have to tell Flask that the methods I want you to support
for this route are this list of verbs or methods,
and that list is going to be of size one with just the keyword post in it.
So again, when you encounter these issues, which you absolutely will
when doing all of this for the first time,
don't worry so much about the specifics of the error message, but the ideas.
Like what's gone wrong?
Method.
Method not allowed.
What could that mean?
The only methods we've talked about are get and post,
so maybe it's something related there.
Well the route in question was slash register,
so maybe it's something related to my register route,
and see if those kinds of clues can lead you to a solution like Santiago
just proposed.
Let me go ahead and restart my server.
Let me go ahead and reload my form, type in David again,
I'm going to select dodgeball, register, voila, you are registered.
All right.
I'm not actually registered for anything,
but I feel like there's still a missed opportunity for error checking now.
In fact, let me go back to my form, not type David
and not select dodgeball and click Register.
Well this is kind of stupid.
Like its claim, it's saying I'm registered even if I only type my name,
no sport, you are registered.
So there's a missed opportunity here for some error checking.
So let's at least go in and add this.
Let's go ahead and do something like this.
How about if not request dot form dot get
name or request dot form dot get sports, or not that,
then let me go ahead and return render template failure dot HTML,
otherwise I'll return success dot HTML.
So let me try this now.
Let me rerun the server, Flask run.
Let me go ahead and reload my form, no changes to the form,
but now if I don't cooperate and I click Register,
OK, now really bad things are happening.
This is one of those 500 errors.
Let's see where I screwed up.
Internal server error and the subsequent paragraph are not very descriptive.
But if I go to my terminal window, here's my exception
a Jinja 2 exception, that's the templating language,
failure dot HTML is not found.
All right, well that's just because I made a stupid mistake,
so let me stop the server.
Let me go ahead and copy success dot HTML for now.
Let me go ahead and paste it in here.
And I could probably factor this out, but we're
going to keep it simple for now and call this failure dot HTML,
put it in Frosh IMs templates, save it.
Now let me rerun my server.
Now let me go back.
Let me try not typing in anything and just registering, OK, voila.
Now it's actually checking.
And if I go back here, name, OK, I'll not type in a sport,
it's still not checking.
But there's still this kind of hack here.
You know, you recall from last week that if you right click or control click
on anything in any web page, you can inspect it
in Chrome or your preferred browser, and that
lets you start poking around the HTML.
And watch this, maybe I'm a little bit upset
that there's no, let's say, tennis.
Tennis is not offered by the intramural sports program, but darn it
I want to register for such.
Well, again, using a browser, there's nothing stopping me from, for instance,
I don't know how to do this, let's change ultimate Frisbee to tennis,
and literally change the HTML on the page, zoom back out,
close the inspector, ha, ha, now tennis is a sport at Harvard.
So now watch what I can do.
Register for tennis, type in my name David, voila, registered for tennis.
Right?
I've hacked the website.
All right, so in some sense I kind of sort of
have hacked the website this time.
Now obviously I'm not doing anything with this information
on the server at the moment.
I'm just blindly saying registered or not registered.
But notice what I've just done.
In fact, let's really notice what I've done.
Let me go ahead and inspect the page again, let me go to my network tab,
and let me resubmit this form, and watch what happens here.
Let me go back here, let me type in David, let me do this quick hack again,
let me go ahead and right click on sport,
inspect, let me get rid of ultimate Frisbee and change this to tennis
again, and let me change this to tennis again, enter.
Now let me go to my network tab, select tennis from the dropdown,
and then down here, let me go ahead here and click Register.
Notice what's going on down here.
Just like last week, you can see a list of all of the HTTP requests.
So let's look at slash register.
Let me now zoom in on all of this and scroll down to the form data
which we didn't see last week but is there for post requests.
I have legitimately sent from my browser David and tennis to the server.
The onus is now on you, the programmer, to make sure
that you're not actually going to let me register for tennis if it is not
a supported sport.
So now there's more error checking necessary.
It's not sufficient to just trust that the user typed in a name and a sport.
You can't just check for the absence of either of those.
Now, we should really be smarter and make sure
that whatever sport the human's browser sent actually exists at Harvard.
And you could imagine this going very wrong very quickly,
if it's a bank account, like how much money do you want to deposit?
Well I'll just hack the HTML and change the amount of money
I'm depositing or withdrawing, right?
You should not be able to change a server's data
or database by just changing some HTML.
You should never, ever trust your users, or myself in this case.
So how do we defend against this?
Well there's a solution to this problem that actually gives us
some other features as well.
Let me go ahead and get rid of this silliness
where I've literally hardcoded all of these sports
in the HTML, which literally I resorted to copying and pasting.
That should have been the first clue that we can probably do better.
Let me instead go into application dot py, and let me go up here,
and let me just define a global variable called Sports.
I'll capitalize it as is the convention when defining constants.
And let me go ahead and say, the sports are going to be this Python list.
So I'm going to keep it pretty and put it on multiple lines using
square brackets because it's a list.
Here's where I'm going to say dodgeball as one sport,
flag football was our next, comma, soccer is our third, volleyball fourth,
and then lastly we'll leave in ultimate Frisbee as legitimate.
So those are my sports.
How do I now use that list of sports?
Well, I can just pass them into my template.
I can say something like this: Sports, or anything, x, or y,
or z, but sports is more descriptive, equals sports.
So this is to say, I'm going to create in my template
a variable called sports, the value of which
is going to be that same global variable.
And to be clear, Flask requires that you choose a name.
You can't just pass in sports because Flask just
won't know what to do with a single argument like that.
You have to give these things names.
And again, it tends to lead to this redundancy
where you're reusing the same words, but one is your variable on the right,
the other is the name you're using in your template on the left.
So now let's do this in index dot HTML.
Here's where templating gets more powerful.
In templates like Jinja, you don't just have curly braces left and right,
and you don't just have the blocks with the curly braces and percent signs,
you even have some simple programming constructs.
I can actually do something like this.
It turns out I can actually do something like this.
For sport in sports, end for, so it's a little stupid,
the syntax, that you literally say end and then
the name of whatever the preposition was a moment ago,
but now let me go ahead and generate an option whose value is
quote unquote the current sport, close, that and then in here
let me say sport again.
Done.
Now I have used a for loop in my template, the syntax for which
looks almost identical to Python, that's deliberate, the authors of Jinja
essentially borrowed Python's own syntax so it's not too much new information
overload.
And notice what I'm doing.
I'm using this Jinja syntax to iterate over that Sports variable.
It's a for loop, so I'm saying iteratively set sport
equal to the first sport, then the second, then the third, then
the fourth, then the fifth.
And then inside of this block here, this for loop,
I'm using the double curly braces to just plug in the sport there,
plug in the sport there.
And what's amazing about this feature of Flask
and in turn web frameworks in general is that now when
I run Flask run, and I open up my URL and go to this form, voila, it's there.
It's dynamically generated for me.
And if I look at my page's source in my browser,
notice that all of those options are indeed there.
But I didn't have to type them out manually.
I now have it in an array and I'm dynamically generating even more HTML.
And what's more powerful about that savings
is that now I can go into my application dot py, and let's
not just check for this value.
So if not request dot form dot get name, that was saying if there is no name,
and then this part here was saying if there is no sport,
why don't I more specifically say, or the sport
that the user input into the form is not in sports.
So here's where Python gets pretty elegant.
Now I'm checking two Boolean expressions.
Is it the case that there is not a request dot form dot get name,
or is it the case that the sport that was in the user's form
is not in the global variable, sports, then it should be failure.
So you can send me tennis, you can send me water polo,
you can send me any sport you want.
I am now going to reject it because I am validating your submission
against server side data.
And again, this is thematic.
Unfortunately you can never, ever trust user input.
You should always assume that some annoying
hacker out there or friend or sibling is going
to be trying to hack into or crash your programs.
But you can defend against that by programming defensively in this way.
So case in point now, if I go ahead and David, dodgeball, register, that works.
But if I do this little hack again where I go into my HTML,
I go down here, I change ultimate Frisbee, for instance,
to tennis, and also change it here to tennis,
zoom out, close that, choose tennis, type in my name, David, voila.
Wait a minute.
Damn it.
Why did that work?
Oh, wait.
OK.
I didn't restart the server.
It was still running the old code.
Now I open it up.
Now we have to do this again.
OK.
Now I'm going to go ahead and hack the site.
Let me go ahead and inspect this, expand the sports, let's go ahead
and change ultimate Frisbee to tennis.
Let's go ahead and change that to tennis.
Close the window, change the dropdown to tennis, and now, there we go.
OK.
All right.
I was confused for a moment, but again, just restart the server,
and it will reload the changes we actually made.
And thank you so much, Brian, for diving in there to save me.
I figured it out.
All right, any questions or confusion beyond my own now on what it is we've
just done by one, dynamically generating a list of user select menu options
and also validating on the server against that same list of data?
Any questions?
Or anything on your end Brian?
BRIAN: Nothing here.
DAVID J. MALAN: All right.
So let me just demonstrate real quickly these just different user interface
mechanisms.
And suffice it to say, we're not too focused
for our purpose on user interface design.
Using a library like Bootstrap can you make these forms look much prettier
just by adding a few more HTML tags and a few more CSS classes,
but let me go ahead and make one tweak here
whereby we can change the UI from being the select menu
to a bunch of different things.
I index dot HTML I can go ahead and define, inside of my for loop,
instead of using the Select menu an input, name equals sport,
type equals radio for radio button, which
are the mutually exclusive circles that you might tick on a box, on a website,
and then the value of this will be this particular sport.
And then over here, let me go ahead outside of the radio button
and actually display to the human that sport name.
Let me restart the server.
Let me reload my page, and voila, we no longer have a select menu,
but we have radio buttons, which achieve the same result,
but they're just laid out in a slightly different way.
And they're indeed mutually exclusive like an old school
radio in a car where you can only select one of them at a time,
but otherwise the site works completely the same.
So which to use?
It depends.
If you've got 100 different sports you probably
don't want 100 different radio buttons if only
because they take up so much space.
But if instead you might want to use a drop down menu instead.
All right, so what's the key thing we're still not doing?
We've got a pretty dynamic user interface,
but I'm just throwing away the information
once the user clicks Register.
So let's actually start remembering, just as I did back
in the day, who has actually registered so that we actually have
a proper working registration system.
So how can we go about doing this?
Well we've seen in our look at Python in the past
that we can store data inside of a Python program using a list,
using a dictionary, using a set.
There's a lot of data structures that Python gives us.
So let's start simple.
And let's actually do that.
Let's go ahead and store our registrants maybe in a dictionary.
So in application dot py, which is thus far now unchanged,
let me give myself one other global variable, registrants,
and let me initialize it to an empty dictionary.
And recall that we did this to implement a phone book where
I added my name and phone number and Brian's name and phone number
a couple of weeks ago.
We used a Python dictionary to store those key value pairs, name and phone
number.
Here let's do something similar.
Let's store the user's name and the sport
for which they're registered so that we then
have in the computer's memory a list of all of the valid registrations.
So how are we going to do this and what needs to change?
Well let me go ahead and change this back to the Select menu
just because it fits on the screen a little better instead of the radio
buttons.
And from here we will have again, the user interface that we saw earlier.
So let me go ahead and do Flask run, let me go ahead
and open this up, and voila, we have that same interface
as before, the goal of which now is when I click Register,
I don't want to just naively see you are registered or you are not register,
I want to actually register the user.
So I think we're going to need to do a bit of work
in application dot py, the file that's actually
processing the user's registration.
So instead of just validating the form by checking
if they had a name or a sport that was in sports,
let's do something a little different.
So let me go ahead and sort of start anew here,
and let me do something like this: name equals request dot form dot get name,
just so I have it in a variable, it'll be a little easier to use.
And then let's just say if not name, then let's go ahead and return render
template failure dot HTML.
All right, let's then check sport.
Sport equals request dot form dot get sport, storing it in a variable
just so I can do this a little more cleanly.
And then if not sport, go ahead and return render template, template,
failure dot HTML.
And then let's do one more check, but I'm just
being a little more pedantic this time.
If sport not in sports, then let's go ahead
and return render template failure dot HTML.
But you know what, this seems a little silly.
If all I'm doing, if I know as the programmer what the error is,
but I'm just telling the user you are not registered,
that's not particularly good design.
I'm not telling the user any information about what they did wrong.
So let's do this instead.
Instead of failure dot HTML, let me go ahead and create a different file
called error dot HTML.
And this will be called error dot HTML in my Frosh IMs directory
in my templates directory, and in here, let's go ahead
and actually put an error message, for instance, something like this message.
So there's nothing really there other than a placeholder for message,
but let's see what this will do for us.
Let's now go back to application dot py.
And instead of error dot HTML, let's be a little more specific
and pass in an error message like missing name.
And down here, let's change this to error dot HTML
and say a message of missing sport.
And down here, let's change this to errors
dot HTML, whoops, error, error in the error, message of invalid sport.
So there's a sport there, but it's just not in the list of valid sports,
so we should tell the user accordingly.
So now let's just try this real quick.
Let's try this real quick and input this data in correctly so we can see these.
Let me rerun the server, reload the form, let's type in nothing
and just try to register.
OK.
Missing name.
It's more useful than just "you are not registered."
All right, fine, let me give you my name, David.
Register.
Missing sport.
OK.
Now let me go ahead and give you a sport.
And now if I registered, I'd probably get some other error
because I haven't handled the successful outcome yet.
So let's finish this up.
Let's go back to my application dot py in my register route
and then ask ourselves, well what does it mean to register the user?
I want to remember that the user is registered for a given
sport in the computer's memory.
And the computer's memory, recall, I'm using
by way of this global variable called registrants.
And it's all caps just to make clear that it's a global variable,
but that's not required.
And then down here, why don't I do this, registrants, name equals sport.
So this is very similar to what we did with names and phone numbers, keys
and values, where I'm using name to index into the Python dictionary,
and I'm setting the value equal to the sport.
And then let's go ahead for now and say return, render template, whoops,
render template, success dot HTML.
So let's test this out.
Let's go over to Frosh IMs, whoops, let's learn from my mistakes.
Stop the server and reload.
Let me go back to the browser.
Let me go ahead and type in David, who will register for dodgeball, register.
Claims it's registered.
Let me do Brian will register for flag football.
Registered.
He is registered.
But I don't know who's registered or not.
I don't know.
But you know what, let's do a little sanity check here.
Let me just print out registrants, right?
When in doubt, print or print f is your friend.
So let's just do a little poking around here and run Flask run again.
This time let's go back to the form and register David, sport, dodgeball,
register, same behavior.
But down here, notice we have this.
And I see a couple of you are registering as well.
So I see Lucas has registered for dodgeball as well,
Brian Y. has registered for volleyball.
So OK, now Harsh has registered for volleyball.
So this is the problem with having an internet when we do these live demos,
but apparently it does in fact work.
So let's go ahead and make this a little cleaner
here, and instead of just printing registrants, let's go ahead
and do something like this.
Why don't I go ahead, and you know what, let's do this.
Let's go ahead and render a template called registrants dot HTML.
And I think to make this happen, I'm going to need another route,
but this one will be kind of interesting in that we've never
dynamically generated quite this kind of success message.
So let me go ahead and whip something up real quick here.
Let me go to a new file, registrants dot HTML, Frosh IMs, templates,
and then in that folder, templates, and in here we're going to do
extends layout dot HTML, down here, block body, end block,
and then down here, in here, I'm going to do, this going to take me
a moment here, H1, registrants.
Then below that I'm going to give myself an HTML table.
Inside of the table, I'm going to define what's
called a table head, which is not strictly required
but it helps distinguish the head from the body from the optional footer.
Inside of here I'm going to create a table row.
Inside of here I'm going to create three columns,
using TH, table heading, which you might not have seen before.
Typically makes things bold by default to make clear it's the heading.
And I'm going to put the person's name and then another heading of their sport
down here.
And then below the table head I'm going to do tbody, which is the table body,
another tag you might not have seen before but just represents, indeed,
the body.
And I'm going to generate dynamically a whole bunch of TRs.
So for name in registrants, let's go ahead
in this Jinja for loop in my template and output dynamically a table row.
Inside of that table row, some table data, specifically the person's name,
and then specifically in the other column
the person's sport, which I can access, as we'll see by way of a variable
called registrants by indexing into it via the name.
So registrants, in a moment, is going to be a dictionary
that I pass into my template.
I think that's it.
It's a little tedious to type it out.
But notice there's no data.
It's only HTML and templating.
If I go back here now and pass in registrants equals registrants,
just like with the sports variable I'm providing my registrants template
with a variable called registrants, but again I could call it x or y or z,
but registrants feels a little more appropriate,
setting it equal to the value of that global variable,
thereby giving my template access to that dictionary.
If I now save the file, rerun Flask, go ahead back here,
whoops, let me go ahead and do this once more.
Let me go ahead and rerun Flask.
Let me go ahead and select my URL.
Let me go ahead and register David for dodgeball and click Register.
Voila.
Harsh, you beat me to it.
So Harsh is registered for flag football.
David has registered for dodgeball.
It's not the prettiest user interface, but if I view the page source,
you'll see that we indeed have the table head, followed by the table body,
followed by Harsh for flag football, followed by David
for dodgeball, and then after that any subsequent registrants
who are registering at the same time.
So this is pretty cool, I dare say, in that now we
have the ability to save all of these registrants to a global variable.
But what's a downside here?
We've made progress.
I'm now showing you users who have registered,
so that means the RA or proctor who's running the sport
knows who's registered.
But there's a catch.
What could go wrong here?
What's bad about using a dictionary?
Sophia?
SOPHIA: I'm assuming that every time you reload
the server you're just going to reset, and you're not going save the data?
DAVID J. MALAN: Exactly.
If I'm just using a global variable, it'll
work forever so long as the server runs forever and the IDE stays online
and we don't lose power or any other number of reasons.
But as soon as the web server, excuse me, stops or is turned off
or something goes wrong, we're going to lose all of that data.
So this is why a couple of weeks ago with Python
we actually used CSV files or ultimately a SQLite database.
But let me make one change too.
Because what I don't like right now is that at the moment
the only way to view my registrants is to actually register yourself.
And maybe that's a feature if you want to require people register before they
see who else is registered, but it feels nice to have a different route,
maybe slash registrants, that anyone can visit
to see the actual list of registrants.
So let me go ahead and make one tweak here.
Let me go ahead down here and define another route, app dot route, slash,
whoops, registrants.
And then define registrants as my function
but I could call it anything I want.
And let me actually move this rendering of template here.
And you know what, let me be a little clever.
Let me actually redirect the user to slash registrants.
So let me go ahead and import one more function up here,
redirect, from the Flask library.
And this is a function that will literally do one of those HTTP 301s
or some other status code that will send a location to the browser that says,
uh-uh, go here instead.
And the reason for this is that it's just nice now
that inside of my register route, once you're registered,
I don't need to literally copy and paste this line in two different places
and actually have redundant code.
I can just say, redirect the user to slash registrants
which already exists now so that they can see themselves
and who else has registered.
So if I do this again, let me restart the server.
Let me go back to the form, type in David for dodgeball, register.
OK, so Harsh, you didn't make the cut this year, this time, but we see Break
and Moash and David now registered in that split second,
amazingly, for these sports.
But notice the URL, which is the key point.
Now we're at slash registrants.
And if I keep reloading I'll keep seeing all of the changes since then.
All right, but Sophia makes a really good point in that the data is all
going to be lost.
So I think we should clean this up and maybe do one final version here,
where we actually save the data in, for instance, a SQLite database
or maybe even email it out so that we have a backup copy as well.
So let me go ahead and propose this.
Let me go ahead and propose that we make a few changes here, a few changes,
borrowing some inspiration from our look at both SQL and Python.
So I'm going to leave most of this alone for now.
I'm going to go ahead and get rid of this variable.
We'll have to store the data elsewhere.
And instead, let's go ahead and use a database, a SQLite database.
So let me go ahead and declare a database as SQL.
SQLite, colon slash slash slash, Frosh IMs dot db.
Let me go ahead and import this.
This, recall, is from CS50's library, this SQL library.
And let me go ahead in my window for just a moment
here and copy the database from an example I brought with me just so
that we don't have to create the database as well
and sort of rehash things from our week on SQL.
Let me go ahead and grab the database here, and indeed, voila,
the database is out of the oven.
And we now have a local file in my directory called Frosh IMs dot db.
So let's go ahead and first look at this database, SQLite of Frosh IMs dot db
dot schema, enter.
Here is the database table that I created in advance just so we
wouldn't have to reinvent the wheel.
I'm creating a table called registrants with an ID column, with a name column,
and a sport column.
The primary key is going to be that ID.
The textfield should not be null and the sport should not be null.
But we've seen all of this syntax actually in past weeks.
So it's just a simple table to give me an ID for every registrant, a name,
and a sport.
But I'm going to do this in Python now.
So let me go ahead and go back into my, let me go back into my application dot
py, and let's go ahead and do this.
When I want to actually register the user,
let's validate them as before, making sure
that they've given us their name and their sport,
and now let's just use a little SQL.
Instead of saving it into what was a global variable let's
do db dot execute, insert into registrants a name and a sport,
I don't need the ID, it will auto increment for me
as the primary key, and two values, question mark and question mark.
Again, beware SQL injection attacks, and let me pass in name and sport.
And I think that's all I need.
I'm now going to use a little bit of SQL in my Python
in order to insert that name and sport into the database,
and after that, I think I'm fine.
But I do need to change registrants because this variable down here no
longer exists.
But that's OK, because recall from our look at SQL in Python,
I can do something like this: registrants equals db dot
execute, select star from registrants.
Indeed that simple query would seem to give me access
to all of the registrants thus far, and now I can just pass that in like that.
Again, it looks a little weird that the name of your variable in your template
is the same as the name of this variable.
But again, that's because on the right is the variable, on the left
is the name you want to use in your template,
but you could call it anything you want.
All right, here, as always, let me cross my fingers.
Flask run, all right, let me go ahead and open up my URL.
Let me go ahead and type in David, dodgeball, register, OK, interesting.
So Harsh and Naveen got in there real fast,
again, you're just kind of waiting with the Submit button.
But this is a mess.
Something is clearly wrong now, I'm seeing like weird dictionary syntax.
But that's because I haven't adjusted my template.
So let me go into registrants dot HTML and recall
that when I select with select star from registrants,
this is like getting back rows.
I just renamed it to registrants, but I'm really getting back rows.
And rows are just dictionary, each row is a dictionary
of columns and the values they're in.
So I think my syntax just needs to change a little bit in my registrants
dot HTML.
I should really think of this as for registrant in registrants,
and let me go ahead and output the registrant's name.
And down here let me output the registrant's sport.
So again, this is more similar to the time we spent on SQL and Python
than it is to HTML and CSS right now.
Now I'm iterating over all of the rows.
And if you prefer, let's change even the semantics.
Let's go back over here.
Let's do it just like we did a couple of weeks ago.
Let's just get back a bunch of rows, pass in those rows to my template,
and now iterate over those rows.
And each row has a name field, and each row has a sport field.
So that too would be equivalent.
But once you understand the difference, let's
call it what they really are instead of this more generic row,
so let me revert that.
Now let me go ahead and rerun the code.
Let me try to beat everyone else to the punch, type in David for dodgeball,
and, Oh my god, OK, lot of people.
OK, new and better Harsh, all right, so you're working really quickly
it should seem.
So now we see all of the same results.
The database is really hopping now.
I'm a little nervous about scrolling any further,
so I'm going to leave it there.
But we now have a full fledged web application honestly
that is now much more representative of real world websites.
We have our own database using SQL, we have our own templates dynamically
generating this table even though it's ugly at the moment,
but I could certainly beautify it with some CSS or some Bootstrap
in particular.
But we now have a fully functioning website.
What would be the icing on the cake?
You know what?
It's pretty cool, even though we take it for granted
when a website emails you a confirmation.
What if we add one final feature just like I actually did years ago
whereby you email the registrant or yourself to report
that someone has actually registered?
So how can we go about doing this?
Well, let me go back into my code here.
And let me add one final flourish.
I'm going to scroll up to the top here, and I'm
going to import another library.
From Flask mail import mail and message.
And again, you would only know to do these things from a class,
from the documentation, from a tutorial or the like.
I looked it up in advance to see how to do this.
And then I have to, according to the documentation,
there's a bunch of initialization steps required
when you want to write Python code to generate emails programmatically.
You've got to know your username, your password,
and a bunch of other settings.
So let me go ahead and configure those now.
App dot config, quote unquote, mail default sender is one.
And I'm going to go ahead and set that equal to os
dot get env of mail default sender.
So default sender refers to a default email address.
So for instance, if I wanted all these emails to come from me,
I could do something like that.
But instead I'm going to store them in variables elsewhere because I
pre-configured my ID in advance so that no one could see my username
and password.
So I'm just going to grab some of those values from the so-called environment.
You won't use this feature too often but we'll
show it to you in the next problem set.
It's a way of getting access to a variable that's
been defined elsewhere in your IDE or in your Linux system
or Mac or PC more generally.
All right, I need to define a few more of these.
Let me go ahead and just do some boilerplate,
even though this looks like a little copy paste, which it is,
it is in fact necessary because there's a lot of settings for a mail server.
So let me do them one at a time.
Mail password, I don't want you to see that either,
so I'm going to go ahead and grab that from my environment, mail password.
Let's see, I need a mail port, mail port, so that's going to be 587.
It turns out I'm going to use Gmail, and Gmail
lets you send email on TCP port 587, so I looked that up in the documentation
too.
Mail server in fact will be SMTP dot gmail.com.
I found that on Google's documentation.
Mail use TLS, which is a type of encryption,
let me go ahead and enable that as true.
And then lastly mail username, I don't want
you to know what username I'm using, but I'm
going to go ahead and grab this from my environment, username.
And this is actually deliberate.
Though I'm sort of saying it tongue in cheek,
it is bad, bad practice to type in usernames, passwords, or anything
remotely private or secure into your code.
Because imagine if you did type your own Gmail username or password
into this program just to try it out literally
right now at home and then you can submit50 or check50, guess what?
You're sending your username and password to us, the staff,
not to mention GitHub and the whole internet,
by including it in your source code.
So while I'm sort of making fun at what I'm doing here,
this storing of private information in these environment variables
is actually best practice because it means it's not in my code,
I'm not going to accidentally copy and paste it elsewhere.
All right, I need to import one other thing which will explain all of these
red X's.
I need to also import OS.
So it's kind of a bold claim to import a whole operating system,
but there's indeed a library that comes with Python
called OS which gives you access to things like these environment variables
and others.
Whoo.
OK.
That was a lot, and I need one last line of code,
a mail variable equal to passing my Flask
application to a function called mail.
All right.
I think all of those errors will soon go away,
I think it's just being a little slow on me.
Let's go ahead now and actually send mail.
Indeed the red errors are gone.
Let me scroll down, and before I redirect the user to slash registrants
but after registering them, let me go ahead and do this.
Let me define a message variable equal to capital message, which
is the feature I imported from that library,
and let me create a subject line of You are registered!
And let me go ahead and, huh.
Oh, I don't actually know who to send this to, so that's OK.
Let's go ahead and change something here.
Let's go ahead and have the user ask not for their name,
let's ask the user for their email now.
So I'm going to change my actual form to ask for their email instead.
And now let me go back over here, and let me go back in here
and set a second argument of recipients equals email.
And let me change this here.
Let me go ahead and get the user's email from the form.
If there is no email let's yell at them and say missing email.
Sport is unchanged.
Let's go ahead and insert into registrants.
You know what, let's not bother inserting into the database actually.
Let's keep this super simple.
Let's get rid of the SQL stuff.
Let's just have it send a confirmation email.
So it's even simpler.
We'll keep the focus entirely on email here.
So I've created the message with a subject line and the
to line, so to speak, via the recipients.
Now I'm going to go ahead and send mail dot send message.
Save.
Here's where I'm going to cross my fingers as always.
I'm going to go ahead now and run this program.
I'm going to restart, reload my form here.
It's indeed asking me for email.
Let me go ahead and type in, let's say John Harvard's address here.
John Harvard, like me, will register for dodgeball,
and let me go and click Register.
Now the website is about to say internal server error, which comes as a surprise
to me, but let me see why.
Oh I accidentally am still using the database.
So let me fix this.
Let me go ahead and, since we're not using the database anymore,
let me go ahead and revert to our simpler return render template
success dot HTML.
All right.
User error.
Let me go ahead and restart the server.
OK, bad gateway, whole other problem, here we go. jharvard@cs50.
Let me go ahead now and register for dodgeball.
Register.
Crossing my fingers.
I am registered, and indeed, let's go over to my other browser
here where I have Gmail open.
I am currently masquerading as John Harvard in Gmail here.
Let me go ahead and refresh my inbox, and oh my god, a message from the CS50
bot that I am indeed registered.
So now we have a programmatic way of sending emails.
I hit it twice, so I got two emails this time.
But indeed I've now dynamically generated emails as well.
A lot.
But the ideas really are just based on templates
and the idea of sort of putting our controller logic and application dot py
and all of the user aesthetics and our so-called views,
and now that we've had a database or in some sense an email backend,
now we also have the notion of a model where the data is actually being stored
or in this case, sent.
Any questions or confusion or clarifications
here on the use of SQLites or email or any of these Frosh IMs features?
All right.
So why is there a shopping cart with cookies on the stage?
Well for that let's take our final five minute break.
We'll come back, talk about something called sessions,
and build even more sophisticated web apps.
Back in five.
All right, we're back, and there's one feature that we haven't really
fundamentally touched on at all when it comes to web programming,
and it's one that you and I take for granted every day,
any time we log into some website, buy something online, or generally interact
with dynamic websites.
And that's a feature that's generally known as sessions.
There is a mechanism built into web applications, whether they're
implemented in Flask or in other frameworks or languages that
allow the web servers to remember a little something about you.
And this is super important to how the web today works,
because without it you wouldn't be able to log in to Gmail or to any other site
and have the website remember that you are logged in.
Consider, after all, when you log in to Gmail,
you type your username, your password, maybe your two-factor authentication
code, and then you see your inbox.
And from there, you're not prompted to log in again the next time you open
an email and then the next time you open an email,
you stay logged in for some number of minutes or hours or even days
on your computer.
Even if you close the browser tab, even if you shut down your computer,
when you come back to Gmail quite often, you're still logged in,
and it remembers in some sense that you have been logged in.
Well, how does this actually work?
Well underneath the hood, anytime you visit gmail.com,
your browser is sending a request like this for slash, the default
route of gmail.com, and hopefully you're going to either see your inbox,
or if you're not yet logged in, you're going to see that login screen.
Indeed, the response is probably going to come back
along the lines of 200 OK, content type of text HTML,
and that's when you see the login form or your inbox.
But recall from last week there's other HTTP headers
that can be tucked inside of that virtual envelope that
provide hints to the browser and server as to how they should interoperate.
That's indeed what HTTP is all about.
It's a protocol set of conventions that govern how browsers and servers should
interoperate.
And it turns out that sometimes this server returns what are called cookies.
And odds are you've heard of cookies.
Odds are you have some sense that cookies
are bad or dangerous but also necessary, and indeed, all of that is true.
Cookies are essentially pieces of data or even files
that are planted on your computer or your phone
by a server that help them remember that you've been there before.
In some sense, they help remember who you are, but even websites
that you're not logging into very often put cookies onto your phone,
or your laptop or desktop which essentially might
be a big random number that only you are given so that even if they don't know
that it's Maggie who's visited the site, or Ryan, or Nicole, or someone else,
they know that unique identifier has been seen before.
And they can therefore track your behavior.
So if you've heard the term tracking cookies, that's all it is.
It's some piece of data, a big random number that's
been installed on your browser that your browser then remembers.
And the mechanism is as simple as this: one of the HTTP headers
that comes from server to browser literally
is set dash cookie, colon, then the name of a cookie, for instance session,
and then value, and the value might be that big random number.
And because of HTTP, again, it's a protocol
that browsers and servers speak together,
the browsers are designed to send that cookie back to every subsequent page
you visit on gmail.com or whatever server you're on.
In other words, when you visit gmail.com then an hour from now,
even after closing your tabs, or even tomorrow after going to sleep,
your browser is not only sending these headers, if you
have been given a cookie from gmail.com before, your browser sends
a similar header but without the word set, it says cookie, colon,
session equals value.
So unbeknownst to you, your browser is constantly reminding the server
that you've been there before.
And this is similar to the real world when you're perhaps in,
when you go into a club or a bar or an amusement park
and you go through the gates, you typically
are allowed back in by having your hand stamped.
And this way they don't have to check your tickets again
or your ID or the like, they just stamp your hand
which is a little reminder that you have been there before.
And so in fact, here, I've got a little stamp here.
If I visit a website, it's like my hand is being stamped here,
and my browser, by design of HTTP is supposed
to, every time I visit another page, click another link,
represent my hand just like you're going back
into the bar or the club or the amusement park
to remind the person at the door, the bouncer, that you've been there before
and to let you through rather than prompting you to log in again.
So that's what a cookie is.
It is like this virtual hand stamp that's put on your computer,
but your computer is designed to present it again and again.
So if you've ever wondered how sometimes you go shopping on one website
and then all of a sudden, creepily you're on Facebook or something else,
and you're seeing ads for the very thing you didn't buy on this other website,
that all derives from cookies.
Long story short, there's advertising companies
that Facebook and Google and others partner with that
put little images on the website or JavaScript files or other such files,
and because those ads are on different websites the cookies are shared
across multiple websites in some sense.
So it's like you are constantly presenting your hand
stamp to all these different third party companies,
and this is how they're advertising to you or even tracking you at worst.
As an aside, when you use incognito mode or private mode on your browser,
all of your cookies are temporarily thrown away.
You get an empty cookie jar, so to speak,
so that you do appear to be logged out.
And in fact, even in class, I've used incognito mode
a few times because I want to start fresh and not
have any of my prior clicks or our prior demos messing up future demos.
But the reason I was doing that was just to get rid of all cookies
and therefore all states.
So on the one hand, cookies are an incredibly important and powerful
mechanism, but suffice it to say, and more on this down the road,
they can be misused, certainly when it comes to one's privacy or security.
So with that said, let's focus on the good part of cookies
and consider how a typical website remembers that you're logged in.
Let's take that very simple idea and make it clear
that it's all within your power now with code to do this.
So let's start as follows.
I've created a new directory called login
just to demonstrate a program that logs people in.
I've got my same layout as before, and I've added one more tag.
It tends to be best practice to tell the browser that you're
using essentially Unicode.
This way I can have emojis and other characters in my HTML.
So I've added this line five which you'll see on Bootstrap's documentation
and other sites, so now I have sort of a good starting point for this page.
But there's really no content, there's just a block being defined here,
block body.
So that's just my same layout as before.
And indeed if I type ls you'll see an application
dot py and a templates folder.
In that templates folder for now is just layout dot HTML.
So let's go ahead and start writing a quick application whose purpose in life
is just to present the user with a form via which they can log in
and then remember that they are logged in.
So from Flask let me import Flask.
Let me also import the redirect function, the render template function,
the request variable, and now a session variable.
This is something new.
And then from Flask session, let me go ahead
and import session, and then down here let me do app gets Flask,
underscore, underscore, name.
And now I need a few more lines of code from documentation.
App dot config, open bracket, session permanent equals false,
and again, this is specific only to the library called Flask session
that I'm using, and that library is going
to enable hand stamps, essentially, for my application.
And I'm going to configure it to use literally the file system,
so session type equals file system, and file system
is just fancy speak for the hard drive or my own IDE account,
and this is an alternative to using a database or something
else like that, and then session app.
So this is just boilerplate, I literally copied
these three lines from the documentation for this library.
But to use this library, and this was also
true of the mail library a moment ago, there's
another file I should point out.
So when I am here, I should create one other file that I mentioned earlier
but we haven't created ourselves, this time called requirements dot text.
And I'm going to put this into my login directory,
and I'm going to require Flask for this application,
and I'm also going to require a library called Flask session.
So again, I claimed before on the slide that requirements
dot text is just a simple list of the requirements, the libraries you
want to use.
And there are commands you will use at the command line
to install those requirements.
You won't typically need to do this for the problem set,
but just so you've seen it, if you want to install
the requirements for a Python application,
you typically do a command like this.
Pip install dash r, requirements dot text.
And I'm not going to do because I did it in advance,
but just know that in order to install libraries, it's much easier in Python
than it was, for instance, in C. They tend
to be more easily packageable via this technology called
pip and things like it.
All right, let's go ahead and create a couple of routes.
App route slash def index, so just boilerplate as before,
and let's just go ahead and simply return render template of index dot
HTML by default, and then let's go ahead and create one other route,
app dot route slash login, and in my login route, which
will be governed by a login function, let me go ahead
and return render template login.
So let's keep it simple for the moment.
And now let me go ahead and implement this form.
So let me go ahead and create a file called
login dot HTML, whose purpose in life is just going to have my login form,
and what I want in that login form is just a space for the user's name.
In the real world, I would most certainly
do something like a username and a password,
but we're going to keep it simple, focus only on the essence of the idea,
and do something like just the user's name.
So, in login dot HTML, I'm going to extend layout dot HTML as before.
I'm going to have my block body, and I'm going to have my end block tag,
and then inside of here I'm going to have a form, the action for which
will be slash login, the method for which will be post.
I'll do best practices here, I don't want my username and password
in the real world being stored in people's autocomplete histories.
Let me do input, autocomplete equals off for good measure, autofocus
for convenience, name equals the user's name,
placeholder as before equals name, and then type equals text.
And then down here, input type equals submit.
So this is almost the same to our previous forms for registering
and so forth, but now I'm going to use it for login purposes.
So let's do a quick check.
Let me go back to my application, do Flask run,
I'm going to go ahead and open up my URL, and first error, let's see,
template not found.
OK, so that's actually expected.
I kind of knew I didn't create index dot HTML,
so I'm OK with that error for now.
Let me go ahead and zoom out though, because I deliberately
wanted to go to my slash login route because I wanted to test that first.
That seems to be working.
Now let's go ahead and create the file that doesn't exist,
index dot HTML, so index dot HTML, put this in my login folder in templates,
and I'm going to do this one pretty similarly.
I'm going to go ahead and copy paste just to save a bit of keystrokes,
but here I'm going to say, you are not logged in because I literally
haven't implemented that feature yet.
So that's pretty reasonable.
Let me go ahead and restart Flask.
Let me go to my index, and I should see, indeed, you are not logged in.
But if I go to login, I see my login form.
So let's make this a little more interactive.
Let me go into index dot HTML here and say,
you are not logged in, a href slash login, log in, period.
Let's create a better user interface.
So let me rerun Flask, let me go back to my web page, hit reload,
and now I have the beginnings of user interface,
super simple, but very similar to a website
where if you're presented with the web page, you're not logged in,
you're given a link or a button or something to log in.
And if I click this, it's just a simple get
request, which is the default for links, to slash login.
So we're almost there.
We almost have the ability to log in.
Let's do something with the information.
So let me go back to application dot py, and when the user logs in,
what is it I want to have happen exactly?
Well if they log in, first of all, I want to support multiple,
I want to support post here.
So let me do that for privacy's sake.
And then let me go ahead and do this.
If request dot method equals post, as before,
let's go ahead and remember that user logged in.
And then redirect user to slash.
Else let's go ahead and render the login form.
So I'm not done yet.
But this is the key idea.
I need to somehow remember that the user logged in.
So how can I do this?
Well, one, I can get the user's name from request dot form dot get name.
And I should assume that, yeah, that's OK, we won't validate that for now.
And what do I want to do with the user's name?
I want to store it in the session.
So what exactly is the session?
Well it turns out that as soon as you have this hand stamp, it's like,
it's kind of like a coat check too.
I don't mean to add too many metaphors here,
but in a fancy restaurant if you like hand your coat to someone,
they'll often put it in the closet and then
hand you back a number, which in some sense
is a little clue like this, a little hand stamp
so that they can line you up.
It probably shouldn't just be a smiley face because otherwise everyone
would get the same coat back, but they give you some kind of identifier,
and they put your coat into some hanger, a bucket, or some space in that closet.
So why is this relevant here?
Well the same is happening in the world of web browsers.
When you get a unique cookie, that's giving you a unique hanger.
I really should have thought this metaphor through before,
but you're given a unique hanger where all of your stuff can go,
including your coat, but in this case, including any variables
you want to remember.
So with that said, what I have access to thanks to Flask,
but really thanks to HTTP, is this special variable called session.
That is a special variable in that it's at the one hand,
on the one hand global, which means I can use it everywhere,
but fancy enough, it's tied to the current user.
So even if all of us on Zoom right now visit my URL,
as is kind of happening anyway, each of us
is getting our own copy of a variable called session.
And it's implemented underneath the hood by way of this cookie mechanism.
Each of us when you visit my application are getting a different cookie value.
That cookie value is like mapping to a different hanger in the closet
so that my data will go on this hanger, your data will go on that hanger,
and so forth.
And now I'm actually kind of proud of this metaphor that's working out OK.
The hanger is going to be where all of our individual data is stored.
What do we want to store?
This is simple, let's just store the user's name,
thereby remembering that I've logged in, or Brian's logged in,
or Sophia's logged in, or Ryan's logged in, or anyone else.
But the session global variable here that I've
highlighted in this very first line is going to be implemented by Flask for us
in such a way that each of us as users get our own version thereof.
So I'm going to quite simply use this as a dictionary in Python.
I'm going to go ahead and add to the session here the user's name as name.
And I don't even need this variable now.
Let me tighten this up just a little bit.
Let me go ahead and just store in the session, which
is like this hanger, the key of name and the value of whatever the user logged
in as.
And redirects I know how to do.
Let me go ahead and return redirect slash, let me get rid of my pseudocode,
and I think that's it.
That is sufficient, excuse me, to remember that a user has logged in.
You sort of put their name in the session,
but then it stands to reason that we need to check for this value.
So now let's improve index dot HTML, which at the moment
just blindly says always, you are not logged in.
Well let's actually make that decision, not just hard code that.
So let's do this.
If not session dot get name, this is the syntax
for saying if there's no name in the session,
then go ahead and return redirect slash login.
So if there's no name in the session, that
means you haven't given me your name yet.
Let me force you to by sending you to slash login.
Otherwise go ahead and render the template.
All right, well what more might I do now?
Well let me go into my index file here.
And instead of just naively hard coding this, watch what I can now do.
I can in here say, in Jinja syntax, not the curly braces, not the block syntax,
not the for loop, you can also use if conditions.
If session dot name exists, then I can say something like,
you are logged in as session dot name, period.
Then I might have something like a href equals slash logout.
Doesn't exist yet, but we can come back to that, log out.
Else if there is no session name, then let's assume
that the user is indeed not logged in and then say, you are not logged in,
log in.
So if I did this right, let me go ahead and stop the server here.
Let me go ahead and rerun Flask run, reload my form,
I still see, OK, wrong URL.
Let me go to index.
Oh no, this is right.
I'm on slash login, method's not allowed,
I think Santiago already pointed out what mistake I made earlier,
which I just repeated.
Let me go to application dot py.
I need to support multiple methods here.
I also want to support get so that I can actually render my template as needed.
So let me go ahead and rerun this server.
Here we go it's slash login.
And notice what happens.
Let me zoom in on this.
Let me get rid of the login and just go to slash,
notice that I'm immediately redirected.
Let's do what we did last week.
Let's open up the inspector.
Let's go to the Network tab.
Let's again visit slash and hit Enter, voila, notice what happens.
If I visit that request, zoom in on Chrome's network tab,
scroll down to response headers, notice that we get a 302, found,
which is a redirect code like 301, and the location is actually,
if you look over here, slash login.
So all the mechanics of HTTP are coming back into play.
All right let me go ahead and log in as David, click Submit, amazing.
You are logged in as David.
Notice what's going on.
I'm now at the slash route.
This is true, so this does not apply, so I'm actually seeing index dot HTML.
And notice I accidentally clicked log out, let's fix that.
Let's give us our logout route.
So here we go.
App dot route slash logout, def log out, and then in here I
need to just forget that the user has logged in.
And there's a few different ways to do this.
Let me do it the simplest for now.
Session name equals none.
Let's just change whatever your name is or my name is to none,
then let's return redirect back to slash.
So let's see what happens now.
Let me restart the server.
Let me go back to my index page.
Because I'm still logged in here, I'm going to go ahead and log out.
OK, let me go ahead and log in as Brian, for instance, submit.
You are logged in as Brian.
And notice I can reload, I can close the tab, I can reopen the tab.
It's all being remembered thanks to my browser representing the cookie.
And in fact, you can see that.
Let me go to inspect, let me go to the Network tab,
and let me reload this same page.
Reload.
Notice this response here.
In 200, notice that if I scroll down here to the request headers, notice
that my browser is requesting, oops, sorry,
notice that my browser is requesting slash, but notice if I scroll down,
down, down, nope, there we go, down, down, down, notice, unbeknownst to me
and unbeknownst to you perhaps all this time,
my browser is also presenting the hand stamp and saying my cookie value
is session equals 35456 dot, dot, dot.
Generally speaking it's bad to tell other people your sessions because now
you could all hijack my session by pretending to be me,
but of course this application doesn't do anything.
But don't do this with accounts that you care about.
No one should be seeing your cookie values.
So we actually see the cookie there, and it came from the server
the very first time I visited.
All right, any questions on that there?
BRIAN: Does the cookie just exist forever,
or is there a time when it disappears?
DAVID J. MALAN: Good question.
Does the cookie live forever or is there a time limit?
It depends on how you configure it.
So this cookie will have a default time limit
of some number of minutes or hours or days, after which it will expire
unless the server refreshes it for me.
You can, though, set cookies that do last forever,
which you probably don't want to do for the notion of logging in because odds
are all of us have accidentally walked away
from like a lab computer or a friend's computer, forgotten to log out.
It's at least nice to know that eventually I'll
be logged out automatically from this site,
and that's what's called a session cookie, one that eventually expires.
But it's also useful when you visit websites,
you notice that some websites have those buttons that say remember me,
or something like that.
Well you can actually set permanent cookies that don't just
store some random value on your computer,
they actually store some piece of information
that gets prefilled into forms subsequently.
And this is useful to remember your preferred settings for a website
if you've customized something, if you're
in night mode or day mode or something like that,
it can remember those kinds of settings.
And even CS50's website remembers what your selection was.
When you log in to some of CS50's websites,
you'll sometimes see a login prompt for Harvard or Yale.
And if you've never noticed, I regret this because we put a lot of effort
into this, it remembers if you used Harvard last time
or used Yale last time so you don't have to constantly interact
with the dropdown, it just remembers your implied preference.
We do that using cookies.
And so as an at home exercise if of interest, truly do start
opening the browser's network tab, start looking at those headers,
and you can begin to infer how all of today's websites work.
And in fact it's becoming increasingly a challenge
to implement things with cookies because long story short, a lot of the browser
manufacturers are finally starting to disable what are called third party
cookies, those sort of advertising style cookies that come from other servers,
not gmail.com, not Facebook.com, but other servers that are all too often
used to track people.
But recently, especially in the EU and now more recently in the US,
if you've ever had to click boxes saying I accept cookies,
I accept the necessary cookies, and so forth, honestly this
is sort of a nice idea, but it's also a little silly in
that now the world is just conditioning you and me to just constantly click
these boxes without really thinking of it.
But what you're really doing is granting permission
when you click those boxes to allow your hand to be stamped.
All right, any questions then on cookies,
or on how we've gone about implementing this login mechanism?
Seeing anything on your end Brian?
BRIAN: Nothing else here.
DAVID J. MALAN: All right, well let me go ahead and do this.
Let me go ahead and get ready an example that we
made in advance that will allow us to explore
a full-fledged e-commerce website, a store of some sort.
And we'll call this literally Store and this time
rather than write things from scratch, let me go ahead
and open things in my browser from a directory called store.
So this is available as always on the course's website.
And let me go ahead here and into store, let me go ahead
and open up application dot py, requirements
dot text, and my templates, including layout, books, and cart.
So this is a good exercise in honestly reading
someone else's sizable amount of code.
It's a few files.
How do you even begin when you've downloaded a program like this,
when you've downloaded distribution code for CS50 or any other course,
how do you begin to wrap your mind around it?
Well again, if you understand what the roles are
played by these different files, application dot py is your controller,
anything ending in dot HTML and templates is a view,
you can begin to sort of follow the breadcrumbs
and figure out what does what in these files.
So let's start with application dot py.
Notice I'm importing CS50's library, a bunch of Flask functionality,
including session support.
Below that I'm initializing the application as I've done before,
and below that I'm connecting to a database which
contains a whole bunch of products in a store, books in this case.
I then configure sessions, and we just did that,
so that code is the same as before.
And I think we can begin to infer what this website's going to do.
So my default slash route has an index function,
and it looks like there's a table in that database called books,
so I'm selecting star from books to get me all the books.
And then it looks like I'm rendering a template called books dot HTML,
and I'm passing in those books.
So let's follow the breadcrumbs here and go into books dot HTML.
It looks like books dot HTML extends layout dot HTML.
All right, let's look at layout dot HTML.
Nothing really interesting going on there, just the boilerplate placeholder
code that we've seen before.
So I'm going to close that and assume that it's not interesting.
Been there, done that.
But books dot HTML, if we continue further, is a little juicy.
We've got my block body between lines 3 and 14.
In there I've got a H1 tag just saying books at the top of the page.
And then I've got a for loop in my template,
again using Jinja syntax, that has below that an H2 with apparently
the title of a book, and then below that, a form,
and I haven't seen all of these features before,
like I'm not sure yet what hidden means, but notice that I do recognize a Submit
button that has a value of add to cart.
So it sounds like we're implementing a shopping catalog for like an amazon.com
or some kind of bookstore online.
All right, so now let's go back to application dot py,
and let's look in cart.
It looks like cart supports both get and post, all right,
so maybe I'm using the same route to handle two different things,
and we've seen that before.
The function is called cart, which makes sense.
This thing, let's come back to this, just to initialize our session,
we'll come back to that.
And it looks like there's not much more to this program.
If get is the verb, let's focus on the bottom
first, so let's ignore the if condition, what am I doing?
Selecting star from books where the ID of the book is in this list of books.
Well, where is this list of books coming from?
Session, quote unquote, cart.
So in the last example I used the session to store your name and my name
and everyone's name on a per user basis.
Now I'm using it to store what we'll call a shopping cart.
And indeed, this is the reason why there's a shopping cart,
thanks to our friends at the theater, on stage.
This is how shopping carts are implemented
on websites, via cookies underneath the hood
to give you the illusion of user specific variables, dictionaries
that you can store anything you want in there.
So it looks like my session cart key is going
to have a value equal to the IDs of a whole bunch of books.
I don't know how they're there yet, but let's assume
that's what I'm interpreting.
And then down here, I'm just rendering a template called cart dot HTML,
passing in the books from the database.
So how do they get into the session?
Well, if the user submits the form to the cart,
I think this code is now relevant.
If the user posts to slash cart, it looks
like I'm going to get the ID that they've typed in maybe?
Maybe?
And if there is in fact an ID, and it's not none,
it's not the default value of none, I'm going
to go ahead and append to the cart the ID of that book,
and then I'm going to redirect the user to cart.
So I think that's enough complexity that I think it's time
to look at what's actually going on.
So let me go ahead and do exactly that.
Let me scroll up and run Flask run.
Let me go ahead and open my URL.
OK, it's not the prettiest of website, but it
looks like my database has like all seven Harry Potter books in it,
and notice that each of them has a button, add to cart, add to cart,
add to cart.
Let's look at the HTML source to see what
my books dot HTML template generated.
Kind of interesting.
So there's that H1 books at the top.
Here's the first of my H2s with the title of the book.
Notice that there is this funky symbol here because it's like a curly quote,
so this is one of those HTML entities that lets you output symbols
that you couldn't type easily on your keyboard.
Here's my first form that I dynamically outputted.
And I claimed earlier that I didn't know what hidden was,
but it does what it says.
It allows you to submit a piece of data in a form that
is visually hidden from the user.
It's not a text box that they can type into but it is there.
And what's cool about this is that notice I gave it a value of 1.
But in this form I gave that hidden field
a value of 2, down here a value of 3.
Well, why is that?
Let me go into my IDE for just a moment, let me run SQLite on my store dot db,
let me show you with dot schema that inside of this database
is a books table, each of which has an ID, a title, and that's it.
Let me do select star from books using SQLite 3, and voila,
you see not only the titles of the books,
but a unique ID numbered aptly according to the order in which they came out,
as a coincidence, but intentional.
And so now down here, let me go ahead and move my terminal down a little bit
here.
Let me go ahead and rerun Flask run, and if I now submit one of these forms,
let's see what happens.
Add to cart the first book.
Interesting.
I've been redirected to slash cart, and notice I have an ordered list,
an OL tag, it would seem, with that first book.
Let me go back to the catalog.
Prisoner of Azkaban was pretty good.
Let's add that to cart.
Now I see this here.
And notice this, if I reload, reload, I don't
know why I'm holding up my hand because I'm clearly using my other hand,
but I'm reloading the page again and again,
and it's showing me exactly what is in my cart.
So where is this coming from?
Well, if we go back to our code, this logic here under the post condition
is adding the ID, book number one, book number three, to my session's cart key.
Now where did that cart key come from?
This is the only other piece I proposed we come back to.
I just need some way at the top of my code to initialize the cart.
And long story short, there's not going to be a key called cart in the session
unless you put it there, like Flask doesn't come with name or cart.
You have to put it there.
So notice if cart, quote unquote, is not yet in my session,
I'm just initializing it to an empty Python list.
And I could use a set, I could use a dictionary,
I could use anything I want.
I chose to keep it simple with a list, but that's
how I'm remembering what's in my cart.
And unlike all of our previous examples, because I see a bunch of you
are now trying to add things to your own cart, each of us
is seeing different shopping carts.
We all have different things in our shopping cart.
If I keep reloading, doesn't matter how many of you are trying to add books
to your cart, you're only adding them literally to your cart
and not mine because we all have different stamps on our hand.
All right, so here too I would encourage you when you, next time you
go on Amazon and add some things to your cart,
like literally, that is all that's happening underneath the hood.
And Amazon surely has other features, you
can change quantities, which is kind of nice, you can add things to wish lists,
which is nice, but all of that, if you start looking at the HTML,
and you look at the network request going back and forth, all of those
features that we just take for granted these days are just
built on very simple, well, relatively simple HTML
forms and these inputs and these buttons and on the server, these sessions.
And Amazon might be using a different language, a different framework,
but all of these ideas are the same.
And so indeed this week is not about teaching you Flask which in a few years
time will probably be irrelevant.
It's about teaching these ideas and principles of these HTTP requests
and responses, these cookies, these sessions, and the features
that we can therefore build on top of them.
All right, I think we have time for one final example that
will now bring back our old friend JavaScript from last week.
Up until now, we have been using Python on the server
to generate dynamically HTML, and even though my designs today
have been hideous, you could certainly imagine adding CSS to them
some, classes, some Bootstrap, just a pretty things up like
the forms especially, but that's not anything new versus last week.
But last week we did have JavaScript as a programming language,
but it was a client side programming language,
at least in our usage thereof.
It was a way of getting the user's browsers
to run code, conditions, and functions, and loops, and so forth on the browser
not on the server.
Today, we've introduced Python again, so we have now the ability
to do something on the server, and where the web gets
really interesting these days is when we marry
the so-called front end that the user sees
and the backend which is the server.
The front end is the user interface you and I interact
with, the backend is the Python code, the SQL code
that the humans never see but you, the programmer, see.
So what can we do to combine these two components, ultimately,
front end and backend?
Well let's do something like this.
Let's bring back our old database of shows, and let me go ahead
and show you quickly what's in this folder here.
In shows we have SQLite 3 shows dot db, a smaller version
of what we played with a few weeks back.
I've whittled this down just for size's sake
to have only the names, only the IDs of TV shows and the titles of TV shows
and that's it.
I threw away the ratings and the writers and the directors and all of that
just to keep the focus only on the titles
because now we're going to start sending a lot of data back and forth
over the internet using Python and JavaScript.
Well let me go ahead now and open up this example as made in advance.
And this is in shows.
And we'll see a few files here, application
dot py, requirements dot text, and my templates,
which are index and layout and search.
All right, so a bunch but not more than we've really seen before.
And let's go ahead and, I kind of cheated earlier.
I didn't look at requirements.
But I might as well look at requirements for good measure.
Here's just an example of what else might be in requirements dot text:
CS50 library and Flask.
And again, those are preinstalled in the IDE,
but we do this so that if you get adventurous and try
running these things on your own Mac or PC, you have all of the configuration
that you need.
Well let's start as before with applications dot py,
and let me go ahead and do this first.
Let me run this thing first so we can actually see the app in motion
and then understand how it works.
So if I visit this, let me go ahead and type in office.
All right, nothing happens yet.
Let me click Search.
OK.
I then see a big bulleted list of all shows that mention office, so not
The Office per se, but an office.
And let me go ahead and zoom out here and look at the URLs.
Let me go back here.
Let me point out that we're at the slash route.
Again, the slash is there, it's just Chrome
is hiding it by default these days.
And when I search for something like office and hit Enter,
notice that I end up at slash search, question mark, q equals office.
So I borrowed some inspiration from Google, right?
I kind of cheated last week where I really only
implemented the front end to Google, the HTML form,
and then I let Google find me some cats and some dogs and so forth.
But now as, with Python, you now have the ability
to implement both sides of that application.
So if I look at the source code here, notice
that I seem to be returning a really big unordered list of list items that
represent, again, all the titles of office related shows.
All right, this is fine and good, but this
is of like old school web programming where you fill out
a form, you hit Enter, and boom.
But these days recall most any time you visit a website,
lots is happening at once.
You've got some autocomplete going on here,
you've got chat messages popping up here,
you've got a map over here that's automatically moving
and a little car for Uber is moving around at the same time.
Like the web of today, web 2.0 if you will,
which is not a technical term as much as it is a buzzword,
refers to just increasingly dynamic web interactions
between users and computers.
And with JavaScript and with your own backend,
be it Python or something else, you can now
begin to build these more dynamic interfaces as well.
Wouldn't it be cool if when I start typing O F F I C E,
we actually immediately saw the results.
And we've seen this already.
Last week, remember that I started searching
for all words that start with A, and I used a really large dictionary
from p set 5, but that was 140,000 words but just words.
This database of titles is several megabytes large.
And I don't necessarily want to send the entire database
of movies to the browser, one, because of performance and size,
and maybe two, intellectual property.
I don't really want to send every user my entire database.
You can imagine wanting to keep a little more control over the data you're
providing, requiring users to log in or pay for your service or the like.
So there's reasons where you might want the browser to talk to the server
and only get the data the user is searching for, not the whole thing.
So let's do that.
Let's enhance this show's application in such a way
that it actually does things more dynamically,
but let's first understand how it works.
So I have a couple of libraries here, CS50's and Flask.
I'm configuring Flask here.
I'm then configuring my database here.
Down here, I just have a very simple route
that renders index dot HTML, which apparently, let's take a look,
has that form, very simple, very similar to last week,
very similar to today, all of the forms we've done thus far.
Here's that name equals q, and everything else is pretty boilerplate.
So let's get rid of that file now.
The layout, also pretty boilerplate, it looks like things in the past.
So let's close that one as well and go back to index, application dot py.
So the juicy part seems to be the search route.
And this was the piece we were missing last week when we used Google instead
to find us some cats and some dogs.
Here I have a route called slash search, a function
called search that will be called for that route,
and here is kind of a nice amalgam of SQL and Python.
I'm going to give myself a variable called
shows, which is going to be the result of all of the rows
that come back when I execute this.
Select star from shows where title is like this placeholder question mark.
Now what am I going to plug-in?
Notice it's a little cryptic at first glance,
but I seem to be taking the user's input, q, from request
dot args dot get, so that's using get apparently because of the word args,
and it's not form now, it's request dot args.
So I'm getting the user's q value, which was office in my case, and just
as a quick check, what's with the percent
signs on the left and the right?
Because we've seen a lot of percent signs today, but these are different.
Perhaps in the chat?
What do these percent signs represent in this context?
Brian, you want to echo the consensus?
BRIAN: Yeah, people are saying they're SQL placeholders.
DAVID J. MALAN: Yeah, so SQL wildcard placeholders.
That means zero or more characters to the left, zero or more to the right.
This is why I was getting matches with office at the beginning of the word,
office at the end, and office in the middle.
If I really wanted to be anal I could get rid of the percent signs
and require only a perfect match, but that's not my goal
with this particular implementation.
Once I get back all of, whoops, once I get back all of these rows
I have here a line, return render template, search dot HTML,
passing in all of those shows.
So the last thing to look at is search dot HTML.
It's pretty simple, and surely like an hour ago
this would have looked super cryptic.
It frankly might very well still look cryptic.
But once you start using this syntax yourself
you'll see that, oh, this is just a template that
is allowing me to dynamically output an unordered list of LI tags, each
of which represents the title of a show that's been passed into this template.
All right.
So now then the final flourish.
Let's go about enhancing this application so that there is no form
to submit in the same way, but instead all of the logic
actually happens between JavaScript and the backend.
I'm going to go ahead and do this first.
I'm going to go back to application dot py here, and instead of this here,
I'm going to go ahead and do the following: I'm going to go ahead
and instead of rendering a template, I'm going
to use a new function called jsonify.
It's kind of a funny name, and I have to import it up here.
So let me import jsonify.
It turns out that jsonify, or json, means JavaScript Object Notation.
And we'll see what it looks like in a moment.
But the world years ago standardized on a format
for transmitting data between servers and browsers
using a very lightweight text format, Jason, or json, JavaScript Object
Notation.
Jsonify is a Flask function that takes a Python list or a Python dictionary
and converts it into json format.
So we'll see this in just a moment.
So notice that I'm now returning the jsonification of the show's
variable, which recall is just the rows that came back from db dot execute.
So let's see this in action.
I'm actually going to go to my, let me go ahead
and restart the server because I've made some changes.
Let me go ahead and now go to the server slash search, question mark,
q equals office.
So I'm going to manually visit this URL and hit Enter.
Notice what comes back.
It's kind of cryptic at first glance here.
But notice this is indeed what's called JavaScript Object Notation.
There's a square bracket over here at top left.
This means, hey browser, here comes a list.
There's a curly brace, which means, hey browser
here comes a dictionary, otherwise known as an object in JavaScript.
Quote unquote ID is the key in this dictionary.
108878 is the value of that key.
Here comes the second key, quote unquote title, the value of which
is nice day at the office, quote unquote.
Then there's a closed curly brace, which is
end of the dictionary or end of the object, a comma,
and then there's a whole bunch of these other dictionaries.
So in short, all we're looking at here is a very raw text
based format of a Python dictionary converted to again,
something called json, JavaScript Object Notation,
which literally, this is about it.
Json uses double quotes, colons, curly braces, and square brackets,
and that's about it, to represent information.
So this is great.
But this is a horrible, horrible user interface if what the user sees
is this mess.
But that's not the point.
The point now is to have our backend return a json array of search results
and let the browser convert it into HTML.
Recall from last week I claimed that with JavaScript you
can mutate or change your dom, the document
object model, the tree in memory.
And what you can do in particular with JavaScript
is add more nodes to that tree.
You can add more things to that family tree like structure.
So yes, hello.
We just got, Hi, David, Hi, David, Hi, David via get.
Thank you very much.
OK.
So let me minimize that, and let me open up now, not
search dot HTML which we're not going to bother using anymore,
but instead index dot HTML.
And index dot HTML, you know what, I'm going
to go ahead and borrow some code here.
I'm going to go ahead and open up index dot HTML,
and I'm going to simplify this.
Let me stop the server.
I'm going to go ahead and get rid of my layout template,
and I'm going to get rid of my search template
because I just don't need them anymore.
So I'm going to simplify this program and focus entirely now on index dot
HTML.
So what do I want to do in here?
I need to involve some JavaScript now.
So how am I going to go about doing this?
Well first I need to go ahead and use a library called jQuery,
and jQuery is something that's a little decreasing in popularity,
but it's still used by Bootstrap, and so we'll
continue using it because you're already including it in fact,
for some of your other examples, but we need a different version of it.
So I'm going to quickly Google jQuery CDN, I'm going to go to this website,
and long story short, there's a lot of libraries
out there that you can use by just grabbing their script tag
and using it inside of your own.
So I'm going to go ahead here, and let me just clean up the tag
so it's all in one long, if ugly, line, but via copying and pasting
that line, which we'll typically do for you in the problem sets in distribution
code, I'm going to go ahead and copy that library into my file
so that it's among the libraries I can use.
And now I'm going to go ahead, just as we did last week,
and I'm going to add a script tag inside of my own pages head,
and then down here, let's see, where do I want to do this?
Actually, you know what?
Let me go ahead and do this a little differently just for consistency
with what you'll see online.
I'm going to go ahead and do this all in my body
so we don't have to worry about things being out of order.
We'll indeed keep it all inside of the page's body.
And what I'm going to do is give myself this:
an input, autocomplete equals quote unquote off,
autofocus, and then I'm going to give it a placeholder of query
and then a type of text.
So I'm going to have a very simple search box there
and I'm going to have an unordered list with nothing in it.
And that's deliberate for now.
Then I'm going to import this, the jQuery library.
And then down here, inside of my own script tags,
this is where I'm going to write the actual code.
I'm going to do let input equals document dot,
let's do query selector just like last week, and let me go ahead
and select my input tag.
This works.
I don't need an ID or any class because at the moment I only have one input,
so query selector of quote unquote input will give me that tag specifically.
Let me go ahead now and add to that input
an event listener that listens for the key up event.
Recall from last week our discussion of events, and key up,
like the user touching and letting go of the key
is one of the events you can listen for.
And any time the user does that, I want to call the following function,
and it's an anonymous function which we saw last week too.
And what I'm going to do inside of this anonymous function is the following.
I'm going to call a new function, the only new JavaScript function today,
called dollar sign underscore get.
This is shorthand notation for jQuery dot get,
but, sorry, dollar sign dot get is shorthand for jQuery dot get.
And I'm going to go ahead and get the following.
I'm going to get a slash search URL with a question mark followed by q equals,
and then I'm going to go ahead and put input dot value.
So notice with this line of code, this JavaScript string,
I'm kind of dynamically constructing in JavaScript code
the URL that I want to request shows from, slash search question
mark q equals, whatever the value of the input box
was that the user typed something into.
And the way this dollar sign dot get function works
is it supports what's called a callback.
A callback is a function that will eventually be
called when it has an answer for you.
And this is important on the web.
The internet can sometimes be slow, and you
might want to write code that contacts another server
like I'm about to do now.
And that server might be slow or the internet connection might be slow,
and it would be annoying if your entire browser froze
while you waited and waited and waited for the server's response to come back.
It's a lot nicer if you can say, when you have the data,
call me back, so to speak, sort of metaphorically on the phone,
call me back by calling this function.
So what function do I want it to call?
I want it to call this anonymous function that's
going to take as input an argument called shows,
but I could call that anything I want, and I'm going to do this.
I'm going to create an HTML string that's
initially nothing, quote unquote.
I'm then going to do this, a for loop in JavaScript
letting a variable called ID equal to whatever ID is in the shows that just
came back to me, and now inside of this for loop,
let me go ahead and inside of this for loop
create a title that's equal to shows, which is again,
the data that came back on the, came back via that callback so
to speak, shows dot ID dot title, and semicolon,
then I'm going to do HTML plus equals quote unquote list item plus title
plus quote unquote closed list item.
Then down here, lastly, I'm going to do document dot query selector, quote
unquote ul, dot inner HTML gets HTML.
All right, this is definitely the craziest thing
we've done thus far today, and it's tying together
so many different ideas, not to mention new syntax, so of all the examples,
don't worry too much if this is just a lot at once.
The goal really now is not to introduce you
to this syntax so that you can repeat it tomorrow, but rather
the idea of what we're doing.
So what are we doing?
We have a simple web page with a text input.
We've got a simple web page with an unordered list that's currently empty.
Below that, we're including this third party JavaScript library called jQuery,
which Bootstrap also happens to use for some of its graphical components.
Then I have my own JavaScript code that first
declares a variable that gives me access to the input tag on my own webpage
a few lines above.
I'm then using this fancy function dollar sign
get to do what's called an AJAX call.
AJAX refers to the ability for a web page
to make additional HTTP requests programmatically to a server.
It is allowing, this is how all websites today
get your chat messages if a friend just messaged you on some platform.
It's how when you're typing in your address,
it can autocomplete your address based on the entire globe's
worth of addresses.
It's how Google search autocomplete works as well.
Dollar sign dot get is a function built into jQuery that's
using some standard JavaScript functionality that's
going to allow me to visit the URL that ends with slash search
question mark q equals whatever the human typed in,
and when the response is ready, this anonymous function
is going to get called back, and it's going to be handed an input,
an argument called shows that is going to equal this stuff.
This is what my JavaScript function is going to receive as its shows argument.
And notice, it's a list or an array, inside
of which is a list of dictionaries or objects with two keys, ID, and title.
This, let me stipulate, happens to be the syntax in JavaScript
for iterating over such a data structure,
and this happens to be the syntax in JavaScript
for allowing me to create one line after another, another LI, another LI,
appending it using concatenation to this variable called HTML.
And this very last line of code says to the browser,
go select me that UL and plug into its inner HTML
the contents of it, the value of my variable.
And now crossing my fingers I did not screw up syntactically, let me go ahead
and do Flask run, I still need to run Flask because I do have a backend here.
Let me go back to my browser and reload the slash route.
Let me go ahead now and type in O, F, dammit.
Oh wait, it was just slow.
So notice, everything I've typed thus far or everything I'm seeing thus far
matches the word off.
And if I continue this, office, and let it search
and let the internet do its thing, now indeed,
it's been whittled down to a list of office matches.
And notice again if I go to the inspect tab,
open the network tab under inspect, let me go ahead and type in office,
notice here that every keystroke I typed triggered, O F F I C E to be executed,
triggered an AJAX call, that is an HTTP call to the server.
And notice that if I click on the last one there, scroll down,
I will see not only my request and my response,
but if I click on the response, I'll indeed see this big json
array of all of the data.
And it's actually rather fortuitous that it was slow,
because we were going to end with one demo.
For instance, if I was curious to know, being indoors
all day, what the weather is like, I might maybe
call Brian up on this here telephone.
[PHONE RINGING]
BRIAN: Hello?
DAVID J. MALAN: Hi, Brian, do you know what the weather is like outside today?
BRIAN: I haven't been outside in a little bit, but I can check,
and I will call you back.
DAVID J. MALAN: OK.
And notice it would be annoying if I had to wait on the phone for him
to go outside.
So I'm going to hang up, and I'm just going to wait for him to call me back,
and that's exactly what's happening in my code.
When I pass this anonymous function to this get function,
and they get here means HTTP get, this is telling the browser,
call this function once you have the answer,
much like hopefully Brian's about to have the, and indeed, the call back.
Hello.
BRIAN: Hi.
I looked up the weather.
It looks like it's going to be a brisk fall day today.
DAVID J. MALAN: Wonderful, well thank you so much.
And that is it for CS50.
We will see you all next time.
[MUSIC PLAYING]