[MUSIC PLAYING]
DAVID MALAN: All right, this is CS50, and this is already
week nine, which is our second to last.
Indeed, this is really the last week where you'll learn in this class
how to program.
But indeed, it's this week that's really meant
to be the pedagogical climax of all of these various languages we've
been looking at-- all of these various techniques, all of this syntax.
So that at the end of CS50 in just a few weeks,
you indeed feel that you didn't take a class on C,
and you didn't take a class on Python, but you really more
generally took a class on programming.
Because indeed we know already about half of you
will go on to study computer science further, but half of you will not.
And indeed all of your programming chops here on out,
theoretically, will have a foundation in what
we've been doing these past many weeks.
But here on out, it's really going to be up to you
to learn some new fangled language when it comes out,
or to follow some new trend when some language eclipses
the ones we've been using as more popular, as
more appropriate for problems you want to solve.
And so today really is about synthesizing so many
of the past few weeks, but doing it in the context of web programming, which
for better or for worse, is so very much in vogue
nowadays, both on our laptops and phones.
And indeed the languages we looked at in recent weeks are
used not only to make websites, but also full fledged applications, and app
stores, and the like.
So this really will be the culmination of those past several weeks.
And indeed we'll even talk about some familiar concepts like shopping carts
when you're on Amazon, and these things called cookies
when you're visiting websites.
All of those topics too will come into play
and you'll have an understanding of what all that means from the ground up.
So how did we get here?
Well just last week, we focused on HTML and CSS
primarily, which are not programming languages.
They're just about aesthetics, structuring your data,
presenting your data, and so forth.
And we served the web pages we wrote using this program HTTP server.
This is just one such program-- there's dozens, hundreds of different web
servers that you can use out there.
This is just a super simple one we preinstalled in your code space for you
in VS code so that you can just serve up web pages.
At the end of last week too, though, we teased
JavaScript, a full fledged programming language
that you can use to manipulate the user's experience for the better.
To make things more dynamic and interactive by actually running code
in the user's browser--
on their Mac, their PC, their phone-- as opposed
to server side, which up until now is where all of our code and C and Python
has been written.
So you're writing code on a server, you're serving code from a server,
but now with HTML, CSS, and JavaScript it's getting executed in a browser.
But today we're going to be one final feature of Python--
or really languages like it-- that you can also use code on the server
to generate automatically dynamically.
The HTML, the JavaScript, the CSS that you actually want the user to receive.
You don't have to hard code everything as you have when
making your own home page.
Well, let's consider what some of the building blocks were last week.
So here's a sample URL, and over here slash
is the default page on any web server.
It might be index.html, it might be something else,
that's just a convention.
But it refers to whatever the default actually is.
You can visit, of course, in any browser-- like a URL that
ends in file.html, or something else dot HTML--
and that literally means your browser wants this file on this server.
Or of course, we saw that it can be a folder.
And inside of that folder is presumably some default file name, like again,
index.html, or you can be more explicit like folder/file.html,
and these more generally we just called paths.
And indeed a path is just a location on your Mac, your PC,
or on a server of some piece of information.
But today we're just going to rename this only
to use other common terminology, but they're really just synonyms.
Today we're going to refer to those same things as routes
because now today we're going to ultimately replace HTTP server, which
just serves up static content that you all write with your own web server.
Like now you will be the ones controlling
what it is the server does in response to the user's request
so that you can respond interactively and dynamically.
But we're still going to see techniques like this.
These were our so-called HTTP parameters.
They are everything that comes after a question mark in a URL.
And it can be key = value.
An example was, what?
When we played with Google, what was the key
and what was the value that I first tried?
Any recollection?
I was searching for cats and so the key I figured out
was cue because that's what Larry and Sergey who created Google years ago
decided the name would be of the HTML textbox that you type your query into.
And if I type C-A-T for cat, the value of that
would end up in the URL for Google as being a question mark cat = value.
And I mentioned that it's often the case that you
want to send two different inputs to a server.
And this is why I propose that you just keep an eye out for ampersands.
And ampersands separate these key value pairs.
But again, this is the same darn paradigm as before,
and we've seen this so many times, right?
Key value pairs in dictionaries, in Python.
We've seen HTML attributes and their values.
We've seen CSS properties and their values.
It's all the same thing, associating something with something.
Else even though every language, every person
seems to have their own vernacular for it, it really is just the same idea.
This associating of something with something else we'll continue to see.
And, here to be concrete, were the HTTP lines of text
that were in those virtual envelopes, if you will.
If I were indeed selecting-- trying to search for something like cats
on Google, this, recall, was the message that got sent to the server
by my browser in order to tell Google to please search
for not dogs, but in this case cats.
Now what has HTTP server been doing for us?
Well it's just been serving up HTML file, CSS files, maybe some JS
or JavaScript files, but it has been ignoring any HTTP parameters.
Like HTTP server does not take user input.
Why?
Well what's it going to do with it?
Because you already wrote the HTML, you already
wrote the CSS, like there's no decisions to be
made until we introduce a proper programming language on the server.
And so we're going to move away now from this simple HTTP server program
and introduce you to your own server that's going to handle the parsing,
that is the extraction of these key value pairs,
so that you and I don't have to write Python code all of a sudden
that analyzes this stuff, figures out what
pages requested the key value pairs.
All of that we're still going to get for free by just using the right framework.
And so today we revisit Python, which we've now used in some form
the past few weeks.
And indeed it's kind of been the glue that allows us to stitch together
some of our own logic.
We saw it with SQL, we're going to now see it with HTML, CSS,
and even JavaScript if we want.
And we're also going to see another language today, not a programming
language, called Jinja.
And this is going to be a common paradigm in the real world, whereby
different languages, different libraries, different frameworks
often borrow from each other, or they use technologies
that someone else wrote just so they don't have to reinvent that wheel.
So Flask is just a framework.
That is a third party library, it's pretty popular nowadays,
it's relatively simple, which is why we use it in CS50.
If you've programmed before CS50, Django is
another popular framework, or library, in the Python space,
but it's a little more complicated.
So we focus on Flask.
And Jinja, we'll see, is not a programming language.
It's just going to be some syntax--
thankfully familiar-- with curly braces that
allow us to use placeholders in our actual web pages.
So again, you'll wrap your minds all eventually
around where the lines are among these various technologies,
but these are not the interesting ideas.
The interesting ideas are the ones will focus on in code.
But starting today, instead of running HTTP server
to serve up a static website, we'll have you start running literally Flask space
run in your terminal window to run your own web server that's implemented
in Python using this Flask framework.
So bootstrap was a library for making your CSS
and JavaScript prettier and more interactive.
Flask is a framework, or library, for just
making your Python code more pleasant to use since you're borrowing features
from someone else.
All right, so how can we go about doing this?
Well, if you were to write your very own web application, your own amazon.com,
your own google.com in Python using Flask,
minimally, you need to have a file called app.py by convention, which
is where all your Python code goes.
And then a folder called templates, which is where all of your templates
go.
And for now your templates are just your HTML files.
So if we're going to now start building more interesting interactive things
like google.com or amazon.com, we need to be
able to execute code on the server.
And so this is the convention.
It's not indexed at HTML anymore necessarily,
it's these two things at the top level.
With that said, we'll quickly see that there are some other conventions.
And in my examples online, and in the problem set nine,
you'll see another file called requirements.txt,
which is just a text file that allows you
to enumerate all of the third party libraries your application might
want to use.
It's a convention so that the server can automatically install things for you
without you having to do it manually.
And then static is going to be where literally your static content goes.
So if you've got images for your web application,
if you've got JavaScript files, CSS files,
by convention that goes in static.
These are just conventions, all of this can be changed.
But this is the way to do things.
So we'll introduce you to the defaults.
All right so what does this mean?
How, for instance, could I go about implementing my own web
application using Python that somehow spits out a message like, hello, world?
All right, well turns out, just this.
Now we'll tease this apart in just a moment,
but this is the contents of a sample app.py file that apparently uses some
library stuff-- like familiar syntax from something, import--
something else.
We've seen that before with CSVs and other libraries--
this is somewhat new syntax, but it's kind of copy paste for now.
This is definitely new syntax, and kind of weird with the @ sign here.
But we'll see this again and again today.
And it's just copy paste initially until you understand what it's doing for you.
But at least there's some familiar stuff here,
like index.html is still going to be with us, but it's going to be up to us
when and how to show it to the user.
So let's make this more real.
Let me go over to VS code here, and let me go ahead and create a--
how about we'll do this in Hello-- let me
do mkdir Hello to make a new folder called Hello.
And I'm going to CD into it just to isolate all of these files
to the same directory, so that we have different apps today
and different folders.
And now I'm going to do code of--
let's do this actually.
Let's do our mkdir templates.html--
sorry, not templates.html.
Let me rename that to templates using the MV command.
This has nothing to do with web programming, this is me making typos.
So if I type LS now, I've got a folder called templates.
All right, in there let's create a file called index.html
that is going to be super simple and pretty much copy paste from last week.
Let me hide my terminal window, and let me just very quickly whip up
a simple Hello, world page using my HTML tag.
Lang will = English, then inside of this, I'm going to have a head tag.
Inside of this I'm going to have a title tag,
and I'm just going to call this thing Hello.
I'm going to then have a body, and in this I'm
only going to say something simple like, Hello, world.
And just so this is mobile friendly, recall
that we touched on these meta tags.
So just in case you after class play with your mobile device instead
of your laptop, I'll do name = quote unquote "viewport".
Viewport-- and then content = and I never remember this,
I'm literally reading it off of a cheat sheet.
Initial scale = 1.
Width = device width.
And this is just this magical incantation
that says to the browser, size things appropriately
for the size of the device.
It blows up the font sizes a bit.
All right, so that's what I would have done last week.
And I would have served this web page by running
HTTP server in the same directory, and boom, I would see that HTML.
But let's now start to take some control over the user's experience.
And for now it's going to be underwhelming.
It's just going to always say Hello, world.
But in a moment, version two is going to say Hello, David,
or Hello, Carter, a bit more dynamically.
And we'll quickly escalate from there to just more interesting applications as
well, culminating with things like cookies,
and shopping carts, and the like.
So let me go back into my terminal window,
and as promised, let me create another file called app.py.
And this is where now I need to implement the web server I'm going
to run using this Flask framework.
And for now, I'm just going to kind of do
some copy paste from what we saw on the slide a moment ago,
from the Flask library--
which we've preinstalled for you--
I'm going to import a function called Flask capital F. It's subtle,
but it's important there.
And I'm also going to import a few other things-- a function called
render template, and another variable called request.
And the only way I know this is from having taught this before,
read the documentation, followed a tutorial.
You wouldn't know this unless someone told you or you read how to do this.
But what this means is that this library called
Flask has three things in it-- a function called Flask capital F,
a function called render template, and a variable built into it called request.
And this is going to be all the building blocks
I need to implement my own web server.
The convention in Flask, when you want to create a web app in Python is you
create a variable by convention called app,
and then you assign it the return value of that Flask function-- capital F--
and pass into it __name__, which is weird but we have seen this before.
A few weeks ago anyone recall when and why we mention __name__?
Yeah?
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Yeah, if we wanted to check if the name of the file was itself main
so that we avoided a situation where if you're writing your own library code,
you don't want your code to be executed automatically.
You want to potentially execute the main function.
And that was a solution to that problem here.
For today's purposes, this is just the way you do it,
__name__ refers to the current file.
And so this is just a little trick that says turn this file into a Flask
application.
That's all it is, and for now that line suffices.
All right, what do I want to do after that?
Well now I'm in charge of the web server.
I need to write the code that decides, based
on the browser's request, what file or files I'm going to send from the server
to the browser.
Last week HTTP server did all of this for us just based on the file name.
But today I'm going to take over control over that process.
And the way I do that is as follows.
I say app.routh with, weirdly, in @ sign in front of it.
This is known in Python as a decorator.
And it's a feature of Python not of Flask
that we just didn't introduce in weeks past, but it's a special--
it's a handy trick to do what we're about to do.
The root I want to define is quote unquote "slash", so that is,
here is code I want the server to execute whenever a user visits,
/slash default page of the website.
Well what code do I want them to execute?
Well I want them to execute a function.
And I can therefore define in Python a function.
I can technically call this thing anything I want-- x, or y, or z.
But because they're accessing the default page otherwise known
as the index of the site, I'm going to just more reasonably call
this function index.
But just a convention, you could call it anything you want, but x,y,
or z is probably a bad stylistic choice.
It doesn't need to take any arguments in this case.
And the only thing this code--
this function is going to do is, for now, let's go ahead
and have it return "Hello, world" quote unquote.
And that's it.
All right now let me go into my terminal window, let me go ahead
and do Flask run in the same directory that has app.py and hit Enter.
I'm going to see some cryptic output, but including a URL of my code space,
and if I open that URL after hovering over it,
I'll indeed see Hello, world as you might hope.
But let me do this.
Let me go ahead and right click on the page
and click View page source, which, if you haven't done before,
shows you all of the HTML for a page, however pretty or messy it is.
And that's it, there's no HTML that I've spit out,
it's just quote unquote "Hello, world".
Well if I actually want to spit out a full web page, which is not
a big deal here because who cares?
It's just the text anyway.
But if I want to spit out a whole file, let me do this.
I want to return, essentially, the contents of index.html,
which have all of the tags I want-- the mobile friendly stuff and all of that.
Well I can't just return index.html, but I can return this.
Render template, quote unquote, "index.html".
And per the documentation for Flask, this render template function
will go find that file for me in my templates folder by convention.
It will open it up and then it will spit the whole thing out
to the browser for me, so I can keep all my HTML in one place,
and all my Python code in this one place.
So now if I go back to my browser and reload,
I don't think I'll really see a difference,
because it's the same text, ultimately.
But if I view page source now, notice that, ah, there is all of the HTML
that was just sent to the browser.
So this is only to say, we have the building blocks-- the puzzle
pieces, if you will-- via which to now store
all of our HTML in one place-- and presumably CSS,
JavaScript, and so forth-- but then serve up whatever we want,
even though I'm just blindly spitting out index.html.
So before we proceed, any questions on this, which again, I claim
is like my manual version of what HTTP server was
doing for us automatically last week.
But this is how you do it yourself.
Any questions?
All right, well let's make it more interesting,
which we could not do with HTTP server and HTML alone.
Why don't we go ahead and do this.
Let me visit the same URL, and I'm going to Zoom in.
And your URL will differ from my code space,
but it's going to end similarly here.
I'm going to do /?name=David, for instance.
Or q=cats, or name=Carter-- any key value pair I want,
I'm going to append after and slash and a question mark,
thereby providing user input to the server.
Albeit in a very user unfriendly way.
No one's going to normally do this in their browser.
Enter-- nothing changes here, it just says Hello, world,
but wouldn't it be nice if it says Hello, David?
Or equivalently, if I Zoom in here again and change David to Carter
and hit Enter, wouldn't it be nice if it says Hello, Carter instead?
So we need some dynamism there.
And here's where now Python is going to be our friend.
If I want to access the HTTP parameters that the user has provided via
the URL-- be it q=cats or name=David--
I can use this special variable I already preemptively imported earlier.
And I can do this--
If there is an HTTP parameter called name
in what I'm going to call request.args.
Then I'm going to go ahead and create a variable called name,
and I'm going to set it equal to request.args bracket name.
Else-- if there is no, quote unquote, "name"
key in this special variable called request.args,
I'm going to just assume that the user's name is world by default. Now
what's going on here?
Well, it turns out that Flask provides us with this special variable called
request.args, and in there is all of the key value pairs that
might have come in via the URL.
So if you had to guess what type of a--
or what data type is request.args?
That's its name, and here is in context.
Line nine might provide a clue.
In Python what data type might request.args be?
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: It's not going to be an array or a list because those
are always, in every language we've seen, numerically indexed.
But you're close.
Someone else?
AUDIENCE: [INAUDIBLE]
DAVID MALAN: It's a dictionary.
So a dictionary is similar syntactically to a list in Python,
but instead of numeric indices like 0 1 2,
you can literally use strings like, quote unquote, "name".
Now that's a bit of a white lie, it is a dictionary.
But it's Flasks special fancy version of a dictionary.
But the syntax by which you can access it is exactly the same.
And I actually-- this is a typo.
I didn't mean to say names there.
I meant to say name singular, but otherwise I think the code is correct.
This is going to, on line eight, check if there
is a key called name in request.args, and if so,
it's going to set it equal to that value.
Otherwise it's going to default to world.
I deliberately did not do this.
I added this if else and did not do this why?
What error might happen if I just blindly grab name?
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Exactly.
If there was nothing at the end of the URL that was of the form ?name=someone,
then there would be no name key.
And this is a couple of weeks back, but this would give you
one of those annoying key errors, when you
get a traceback because you screwed up because you
used a string that doesn't exist.
That's why I'm just proactively trying to avoid that situation just like I
might have a couple of weeks ago.
So even though it's more verbose, this is just much more defensive
so that I don't accidentally index into a dictionary where there is no key.
But we'll see how we can tighten this up to be not four lines but one.
But I think now I can do this.
Wouldn't it be nice if now in my index.html file--
which recall is in my templates folder--
wouldn't it be nice if I could do the equivalent in C
of like a person S here for instance?
Or in Python, something like this name.
Well it's close, and this is just because a few different humans
invent different languages, invent different frameworks.
The syntax for this in Flask is to actually
do, whoops, two curly braces, and then name of the variable inside of it.
Why?
It's just probably someone figured, what are the odds
that a normal person is ever going to use two curly braces at once
versus just one?
So this is probably decreasing the probability
that people actually want to output literal curly braces like this.
So it's similar in spirit to Python's f strings, it's similar in spirit
to C's person S, It's similar in spirit to SQL's question marks.
Same idea, slightly different syntax, and this there is Jinja.
So it's not programming code per se, it's just a template.
And indeed that's why this folder is called templates.
It is sort of like a blueprint for what I want to be spit out to the user,
but I've got these placeholders like this variable
that I want to plug-in to that value.
Now, this alone is not enough.
Watch what happens if I go back to my other browser and I
reload the page after changing up here.
Let's do name=David again.
Enter-- nothing outputs after the Hello comma.
So it seems that the name variable doesn't exist yet.
And that's why, indeed, if I do view page source,
you can see what was sent to the browser,
something's wrong with my placeholder.
But I just need to be a little more explicit
as to what I want to send where.
So it turns out that the render template function takes not just one argument--
the name of the template you want to spit out--
but it takes after that, with commas, all of the placeholders
you want to plug-in.
So for instance, if you want the placeholder to be this-- literally
placeholder inside of those curly braces,
you can then specify as the second argument to render template,
a placeholder named argument equals whatever the name is.
So name is the variable in the lines above, placeholder
is the name of my literal placeholder in the curly braces,
and so now if I go back to my browser and reload this with still,
quote unquote--
with still ?name=David in the URL, now I indeed see Hello, David.
And if I Zoom in here--
and let me move over here.
Let me type in Carter and hit Enter--
now I see Hello, Carter instead.
Now this is a little unnecessary to explicitly call the placeholder
"placeholder", especially if you want to have two or three of them.
So you can actually call this anything you want.
And I'm going to change it back to name, which is a little more straightforward.
The only weird thing here is that now you'll
see that you're writing code like this.
And this is correct, and this is the norm, it just looks weird.
But the thing on the left of the equal sign
is the placeholder you're using in the template, the thing on the right
can be any value you want, including a variable.
So even though I'm naming them exactly the same-- which looks stupid,
admittedly-- this is what people tend to do.
Just because it's simpler than introducing
another word like placeholder.
Any questions now on this?
Any questions on these placeholders?
No?
All right, well let's tighten this up a little bit
and see if we can't get things to be more dynamic still.
Let me propose now that instead of outputting--
instead of using this condition which made a very simple idea like very
verbose with four different lines, it turns out
there's an easier way to do this.
You can actually still create a variable called name,
and you can set it equal to request.args,
but instead of just blindly indexing into that dictionary,
it turns out that request.args comes with a function as well called GET.
You can pass it an argument that tells you what value you want to get.
And by default, if there is no key called name in that dictionary,
this function will not throw a key error, it's just going to return none,
N-O-N-E, the special variable-- the special value in Python.
So it avoids a bug in your code, but it tightens up four lines into one.
But even nicer, if you read the documentation,
the GET function can also take a explicit default value.
So if you don't want none to be on the screen, like Hello, blank, or--
I mean, that would be weird too.
You can just put in a default value per the documentation
of this function like world.
So now we've gone from four lines to just one.
So arguably it's better designed.
And if I go back to the browser now still
with Carter in the URL and hit reload, same thing happens,
but we'll notice this.
Suppose I get rid of the name parameter altogether and hit Enter,
now it goes to the default instead--
world.
So it's just a little better designed than doing it the other way instead.
All right, how about we take things up one more notch.
And how about we introduce multiple routes?
And actually introduce, perhaps, a form to the mix.
Because again, no normal person is going to visit a URL and add
and a question mark and their name, that's not how browsers work.
Well that's how browsers work, that's not how humans interact with browsers.
You and I use a form, typically, instead.
So now things can get a little more interesting
when making our own web application because maybe we
could do something like this.
Let me go and Zoom out again.
Let me go back to my code here, and let me move this around and focus now
on the index.html file.
Instead of just this placeholder, why don't we
go ahead and give ourselves a form like we've played with a little bit
in the past, be it for Google or something else.
And let's do this form.
And inside of this form, let's have an input.
And the name of this input will be, quote unquote, "name".
So that, too, is confusing.
But inputs have name attributes, but this is a person's name.
So I'm saying name=name here.
So just a messy world of semantics.
And let me go ahead and make this a text box by default. And then
let me give myself a button whose default type will be submit.
And the name of this button will be greet for instance.
So let's see what happens here, but let me change
app.py to just be the original simpler.
I'm not passing in any placeholders now, and I'm going to even get rid of this.
I'm just going to rewind to the first version of this for simplicity.
Let's now change the URL to get rid of Carter and myself.
So we just go to and hit Enter, and now we have a super simple form again.
All right, this is not super user friendly,
but there's some nice enhancements we can make.
For instance, we can, for instance turn off autocomplete,
especially if I want to type David and Carter manually
and I don't want it finishing my thought during class.
We can do auto focus which puts the cursor there blinking
by default, which is nice because then the human doesn't
have to deal with that.
And then we can even have a placeholder attribute.
Placeholder=name so that it's like built in instructions for this thing.
And so now if I go back to the other tab,
nothing's changed yet because I have to download the HTML again, reload--
OK, now it's a little more user friendly.
It says name in light gray.
The cursor is blinking and I'm sort of ready to go.
But this form hasn't been wired up to go anywhere yet, and so let's do this.
Let's, for instance, say that the action of this form
is not going to be something like google.com,
which we did last time with for cats.
I am now going to be both the front end and the back end of this website.
The front end is what the human sees-- the web page, the graphics, the forms.
The back end is the stuff the human typically
doesn't see-- the Python code, the SQL code, the server itself.
But now I'm in control of both sides of the experience, the HTML and also
the roots.
So let's just propose that we invent our own route,
and instead of calling it /search like Google does, let's call it /greet.
And let me specify that the method this form will use,
which is technically the default, will be gets.
And confusingly, it is lowercase get even though in the envelope
we keep talking about virtually, it's actually capitals.
Again, left hand wasn't talking to right hand when these things were decided.
All right, so all I've done is create a web form that's going to submit
whatever the textbox value is to a route called /greet.
By default, because there's no HTTP, or HTTPS, or no domain name,
/greet is going to be assumed to be not a google.com,
but whatever my own server's URL is.
So whatever my code space's URL is, that's going to be the implicit prefix.
This /greet is just the route.
So now let's go back to VS codes app.py file.
How do I now stitch this together?
Well, I think we're good to go with index.html.
If index.html's purpose in life is just to spit out this form,
we're done with one of my routes.
But if I want to have a second route-- greet--
that actually spits out some greeting to the user,
well let's prepare that template too.
Let me go ahead and highlight all of this HTML.
Let me go back into my terminal window and into my Hello directory,
and then into my templates directory.
And let me create another template called greet.
HTML whose purpose in life will not be to show a form but to greet the user
with Hello so-and-so.
So in this file I'm going to paste all that same HTML,
but I'm going to get rid of the form and essentially revert
to our previous version--
Hello comma-- and then using the Jinja syntax name.
So one template-- index.html-- is for the form.
The second template, now, is for the greeting of Hello comma so-and-so.
But otherwise these files, notice, are almost the same,
except one has the form, one has just the Hello.
So now let's finish this up in app.py.
Let me go down here after a couple of blank lines stylistically,
let me do app.route, quote unquote, "/greet"--
but I could call this route anything I want.
I'm just using a reasonable verb.
Then let's define another function.
I could call a function anything I want, X,Y, or Z.
I'm going to call it more reasonably greet.
No arguments.
And then now is the code where I want to render the template.
So I do return, render, template, greet.
HTML.
But I need to do one more thing.
What else do I want to do if I want greet.html to have access
to the human's name?
Just to recap.
I think we solved this already but I deleted it.
But what do I have to add back?
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Yeah, so I got to pass in the placeholder somehow.
So I can do this a couple of different ways.
I'm going to keep it a little more elegant this time.
I'm just going to put my name--
argument-- there, and I'm going to set it equal to request.args.GET,
quote unquote, "name", "world".
Before I used a separate variable, but I only used it in one place,
so that's not strictly necessary.
So this is fine too, but if this gets a little overwhelming,
notice that I can alternatively do this.
I can create an actual variable called name
and then I can pass in an argument called name
with a value that is that variable.
But again, what's really the point here?
It was kind of prettier all on one line.
So these are the exact same things, I'm just
trying to tighten things up further here.
All right, so what just happened?
If I go back to my form, this is still index.html.
If I reload it, nothing has changed.
If I type in my name to this form--
Notice again, the URL I'm currently at--
this is Chrome hiding things, it's technically / by default,
even though many browsers are just hiding unnecessary characters
these days.
But watch what happens now if I scroll over here
and I click greet on this new form.
Notice?
My URL, my route changed to /greet ?name=David.
And the body of the page at top left says, Hello, David.
So this is exactly how google.com works, and it's
how we implemented HTML last time.
But instead of submitting the form to Google via the form,
I'm submitting it to myself, my very own route.
So I'm implementing my own back end for this same front end.
All right, any questions just yet?
Much less interesting than Google certainly,
but we kind of have all of the wiring now.
Any questions?
No?
All right, so what can we do to further tighten this
up and adhere to some conventions?
Well let me propose that in this version we solve one problem.
And even if you've never done this sort of thing
before, I daresay we have enough weeks of CS50 where
if I show you index.html again and greet.HTML again,
odds are to someone's mind there's a opportunity for improvement.
Why is this web app-- super simple though it is-- arguably poorly
designed at the moment?
And the answer lies somewhere in these two templates--
index.html and greet.HTML.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Yeah, they're almost the exact same except
for the contents of the body.
And it wasn't a big deal, there's only two pages.
So what I copy pasted this once?
But imagine, now, a normal website, or even your home
pages which had three, four, or more pages.
I mean at this point, you're just copying
and pasting everything, honestly, as you probably
did necessarily for your home page.
Why?
Because when you have HTML only, maybe CSS, and even JavaScript,
that's all you can do is copy paste, copy paste,
and just make sure that you have the same structure.
Maybe you have the same CSS file, the same JavaScript file,
the same third party libraries, but it makes it very,
very annoying as you might have realized already to just make
a change that affects everything.
So wouldn't it be nice to factor out all of this and all of this
and just let the body change?
So here too is something that Flask and really other equivalent frameworks
let us do.
It allows us to create what we're going to call conventionally
a layout instead.
So I'm going to go ahead and do this.
I'm going to copy one last time all of the same HTML.
I'm going to go into my terminal window and I'm
going to create by convention a file called layout.HTML.
This is truly going to be a blueprint of sorts.
And in layout.HTML, I'm going to paste all of that same code,
but I'm going to use now some Jinja syntax
to indicate that I don't want to plug-in just a variable like name here,
I want to actually plug the contents of a whole other file.
So instead of just using curly braces, two of them left and right,
I have to use slightly different syntax to say I want a whole block of HTML
here from some other file.
And the way to do this, even though the syntax is a little non obvious, is you
use open curly brace, percent sign, block,
then you can call the next word anything you want.
It just has to be a special type of placeholder for an actual file,
not for just a variable.
I'm going to call it body only because I'm in the body.
So I want a placeholder to be the entire body.
And then outside of this, you then say in one word, no space--
endblock.
So it looks kind of stupid, honestly.
And why do we have yet more ugly syntax?
Again, just different software developers in the world
are all choosing their own syntax for their own libraries.
So they all kind of look different but are all kind of similar in spirit.
And you just get used to seeing the different syntax.
This now is not nearly as pretty as the pair of curly braces for variables,
but this is how I can say plug the contents of an entire file here.
And now what does this let me do?
I can now go back into my index.html file, which at the moment
still looks like this, but almost all of this is copy paste.
The only lines that are interesting and different
are these four lines here in the body.
So what I can actually do now is I'm going to highlight that and cut it,
and then I'm going to highlight everything else
and just delete the entire file.
And I'm going to use some of that same syntax and say
[? %extends"layout.html"% ?] And then I close my thought with a % and closed
curly brace.
So this syntax as you might just be inferring
is now saying, please extend whatever layout.HTML looks like.
That's the original blueprint, the mold out
of which I want to make this web page.
And now here the syntax is a little weird too, but similar,
at least from before.
I can now say the block--
the body block that I want you to plug into that layout
is going to be everything between these two tags, which we already saw earlier.
But in layout.HTML, They're sort of giving a placeholder.
In index.html, this is what I'm going to plug-in to those other placeholders
as well.
So I'm just going to give myself some extra whitespace.
I'm going to paste the HTML that was there.
If I want to make clear what's going on, I
can indent it, although this has no functional impact.
But it just makes clear that just like in HTML, you can open a Jinja tag
and close it.
But in Jinja here, we have this here--
hey Python, here comes the body of this page.
Hey Python, that's it for the body of this page.
And all of this stuff should be plugged into this main parent layout,
if you will.
So super ugly, admittedly, but now at least
things get way less redundant because I'm going
to do the exact same thing over here.
In greet.HTML, it looks like this.
But now I'm going to do this-- extends, layout.HTML also, just as before.
The body that I want to plug-in is going to be everything inside of these tags
here.
And this body is just going to be Hello, name in curly braces like that.
So again, ugly-- syntax got really ugly fast.
But it's really just following these patterns now.
And we have two types of placeholders.
Two curly braces for variables, and now this kind
of syntax with the percent signs and the single curly
braces for contents of actual files.
And so now in this world-- or in the world of a home page,
if you were using Flask and Python to make your personal home
page with all of those various pages, you would probably
design one main layout with all of your pretty logos and colors and fonts
and what you want the site to look like.
And then each of your smaller pages would now
be distilled into just these smaller fragments.
And whether using Python, or Java, or JavaScript, or other languages too,
all different programming languages have popular frameworks
that do things like this.
The idea is the same across all of them.
All right, let's see if it works.
Let's go back into the browser.
Let me go back to my /route.
There's that same form.
Let me type in David and type-- and click greet.
And indeed I see Hello, David, I see that greet was automatically
added to the URL by the browser when I submitted the form, followed
by the key value pairs.
And if I view the page source as I did earlier,
you'll see that you have the entirety of that layout
with Hello, David plugged in.
Meanwhile, if I go back to the form and view this page source,
you'll see the exact same layout, but with the form tag plugged in.
And here's where you can be a little less nitpicky with styling.
OK, yes this isn't technically indented inside of the body.
But it was relative to the original file.
So at this point in the game, you don't need
to worry about your outputted HTML looking super pretty.
You want your source code that the humans
see to be pretty, not the browser.
This is not a stylistic concern.
OK, questions on these capabilities then a Flask?
Or problems that we've just solved and why?
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: OK.
So if the files in question are in different folders--
For instance, if I go back into my index page, which has the form,
the roots here are entirely dependent on what is in app.py.
There's no notion of a folder when it comes to implementing a web
application anymore.
They are more generically routes.
However, and we've not done this yet, you
can put your static content-- your images, your video files,
your CSS files-- in a folder called static,
and there can be subfolders they're in.
And that would affect what you use as your source attributes for images,
or your source tags for video, or any of those kinds of assets.
And we'll see that eventually in the home-- in the problem set next.
Other questions on what we've just done here?
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Good question, how do I-- how
did I ensure that the web app starts on the form
and then goes to the Hello page?
So whatever you decide your default index route
is, like the implicit slash, that is what
is going to be pulled up when a user visits the domain name where
your website is hosted.
So if I go back over here to app.py, because my /route is designed to return
index.html, that's exactly why that response came back.
Good question.
All right, so beyond this, let's consider, now, issues of privacy.
And we'll touch on this too, as we get to issues like shopping carts
and cookies, if I Zoom in on the URL here--
even though the URL itself is a little long and ugly and cryptic--
but when I type in my name and hit greet,
of course we keep seeing name=David in the URL.
In what sense might this be bad design?
Or in what kinds of web apps might you not want the name
to show up in the URL like that?
Because this is what Google does, and this is what my app does.
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Yeah.
So if I'm logging in with a username and password,
I could imagine that they show up in the URL after the question mark where
username= malan and password=12345, but then all my nosey siblings need to do
is go through my browser history and boom,
it's right there for them to copy paste.
So that doesn't seem particularly secure.
Or if someone's walking past you in a cafe, they can just look at your URL
if it's revealed by the browser and they see it too.
So GET is not necessarily the best verb to use
even though it's the default when submitting forms.
Typically, when you've got anything remotely sensitive, or anything large--
so be it a password, or credit card number, or an image or a video
that you're uploading to Instagram, or to YouTube,
or any such site like that-- you don't want the data going into the URL.
And thankfully, there's actually an easy way to fix this.
I can go into my form, which is currently an index.html,
and I can just change the method from GET to post.
In lowercase here, but the verb in the virtual envelope we discussed last week
would itself be capital P-o-s-t.
Now unfortunately, watch what happens here.
Let me go over to my original form by going back to the /route--
and I'm reloading the page to make sure I get the latest, freshest HTML.
And just to confirm here--
yep, if I view source, method is now POST.
So let me go ahead and type in David now and click greet.
And before we saw Hello, David.
But now I get method not allowed.
And this is somewhat subtle but in the title of the tab, notice
that it's a 405 error, which is not familiar, probably.
Almost all of us have seen 404 file not found.
Turns out 405, a little more arcane, is the method.
The HTTP verb is not allowed.
Why?
Because by default, my app.py only currently supports GET by default.
How do I support POST?
I just need a little bit more syntax.
So let me go back into VS Code here.
Let me go into app.py now.
And after changing the form, I just need to inform Flask
that what the method I want this great route to use
should not be the default, which is only GET, I want it to use these methods.
And it takes a second argument called methods, the value of which
is a list, the default of which is, quote unquote, "GET".
So that's the default. This has not made any changes.
But if I want to support POST instead, I can explicitly
pass a list with one string in it--
P-O-S-T instead.
And now what does this mean?
We didn't talk about this in any detail last week,
but inside of this virtual envelope, typically,
is that line like GET/search, queue=cats after the question mark.
If you want to hide that kind of information for privacy's sake,
or because you want to upload an image, which just doesn't make sense
to put in the URL, essentially the part of the story would be,
well the computer looks deeper inside of that virtual envelope.
And anything submitted by a POST goes below the HTTP headers,
like deeper in that envelope.
So they're still there.
They're just not obviously visible for prying eyes in the user's own browser.
So just by making that change in the HTML,
telling the browser to submit the data via POST,
and changing app.py to tell the route to expect the data via POST,
I can now go back to my other tab.
Let me go back to the original page.
Let me reload just so I've got the latest HTML.
And indeed, view page source, it's still--
yep, it's still POST.
But now when I type in D-A-V-I-D and click greet now it works.
But, but, but, notice the privacy implication--
I'm at the greet route but where's my name?
It's not actually there.
It still went to the server, but it's not
in your autocomplete or your history now for privacy's sake.
Questions now on POST?
Yeah.
No?
Just scratching.
All right, can you, the programmer see this?
Well, let me show you a couple of other features of Chrome's--
Chrome, and Safari, and other browsers as well.
I keep going to view page source, which just shows
you a read only version of your HTML.
But recall that last time, I actually right clicked and went to inspect,
or viewed developer tools.
And this brings up a much fancier version of the developer tools.
And under elements here, you see everything.
And it's nice and pretty printed, it's hierarchical, ,
it collapses things into these clickable triangles but it's the exact same
thing.
It's just more interactive.
But notice what I can do today is this--
If I go to the Network tab here, and let me Zoom out a little bit,
Let me go ahead and re load the form here and type in David
again and click greet.
Notice now in the Network tab of Chrome's developer tools,
I see a few things as we saw before.
One, I see that the request method is POST.
Two, I see that the server automatically,
without me writing any code for this, returns 200 when it's successful.
But I can scroll down, down, down, down, down,
and you'll see that eventually after all these cookies-- more on those later.
If I click on payload, the second tab next to headers,
you can see as the developer what was actually sent to the server.
So indeed, this.
Is going to be super useful like when doing problem
set nine, maybe your final projects, if you
want to see what's going from browser to server,
you have complete control over all of that information.
Even if you're using HTTPS, because your browser and you,
the developer can certainly see all of this.
So again, these developer tools, even though there's
a lot of tabs and buttons you probably won't need any time soon,
some of them like elements, and network, and with JavaScript console
are going to be super useful to start to get familiar with.
All right, any questions now on this implication of POST?
Anything at all?
No?
OK, how about one final Hello example that ties a few of these things
together.
How about now we try to tighten things up further only in anticipation
of something like problem set 9, or really more complicated
web apps where you might have not two, but 20
or maybe even more different routes.
It might be ideal to just minimize how many total routes we have.
So we don't get a little too overwhelmed.
And I daresay that these two routes are so short,
maybe I can combine them into one.
And maybe I can keep the user at what seems to be the same URL,
but just to kind of tidy things up.
So let me propose that we do this instead.
Let me get rid of my greet route and let me go into my form in index.html
and let me go ahead and just have the action of this form still be slash.
I want the form to be visible at the slash, the index of the site.
But I also want the form to submit to itself
if only because I don't want to introduce another route like greet
which eventually, indeed, will be compelling
so you don't have one root for everything you want your website to do.
So technically this is the default too, and if I omit action
the exact same thing would happen as well.
But let me rewind and let me now go into app.py
to see how we can make this happen.
Well, if I want my one and now only route to support both methods,
I can say methods=, and then a list with both GET and POST in any order
but I'll keep them alphabetical like this.
Now tells Python hey, this route should handle both GET and POST
requests at the same place.
Let's now go into this function.
I kind of want to say the equivalent of this--
if GET, then I want to return the form.
Else if POST, I want to then return render template
of greet.HTML with the user's name.
But this is not yet complete code, but I think I can do this.
I'm going to go ahead and save the following.
I'm going to go ahead and say if request.method = = GET,
then indeed return index.html.
L if request.method = = POST, then go ahead and return greet.HTML.
This isn't quite enough though because I still want to pass in that placeholder.
So let me again add back name=request.args.GET, quote unquote,
"name", and then a default value of world.
What does this now do for me?
Well let me go back to my other tab here.
Let me close the developer tools, let me go back to the form
here, let me reload to make sure I have the latest.
Let me view page source just to make sure I have the latest-- and yep,
I have the latest because it still says POST.
But it now says slash.
And let's see what happens now.
If I type in my name David, previously this submitted via POST,
so I didn't see any name or value thereof in the URL,
but I did end up at /greet.
But if the action is now slash I click greet,
notice that it still kind of works.
I see Hello, world, although that didn't quite work.
So we'll come back to that issue in a moment.
But notice the URL ends in just slash.
And again, Chrome is hiding the slash because that's all that's there,
but it does not end in name=David in this case, or name=world.
Now notice this too--
if I reload I'm going to get this warning.
Do you want to confirm form resubmission?
The page you're looking for used information that you entered.
Returning to that page might cause any action you took to be repeated.
Do you want to continue?
You might have seen this on websites you've actually visited,
where you hit reload and you're prompted-- wait a minute,
do you want to do that?
Odds are you've been prompted to reload explicitly because why?
Whatever you just did was POST instead of GET.
And by convention, besides POST being used for privacy
to hide your username, your password, your credit card number, or the like.
Besides being used to upload bigger files like images or videos,
POST is also used by convention to make changes to the server,
to add something to your shopping cart, to add something to the database.
Whereas GET, as the name suggests, is all
about getting information, not posting or sending information instead.
So this is Chrome being a little careful because if you just checked out
on Amazon and then you hit reload, you don't
want to accidentally buy the same book again, so to speak,
even though Amazon and fancy websites have other defenses
for this too to avoid this issue.
Now there is a bug, though, here.
It says Hello, world instead of Hello, David, and it
actually would have said the same a moment ago
and I just didn't retest the code and reveal as much to you.
Or if I did I didn't even notice it said Hello, world instead of Hello, David.
It turns out that request.args is only used for GET.
When using GET, request.args is a dictionary that
contains all of your key value pairs.
But somewhat confusingly, when using POST with Flask,
you have to go into request.form.
I have no idea why these are not more obvious opposites like request.GET,
request.form, and--
sorry, request.GET and request.POST would be sensible names.
In this case, though, we have request.args for GET,
and request.form for POST.
All right, that's an easy fix though.
If I go back to VS Code here, let's change request.args to request.form.
Let's go back to my other tab.
Let me just reload, and you know what?
I'm going to say, OK, continue to resubmit the same form
because the form was OK, it was my Python code that was buggy.
Hitting Enter now-- it's accessing David OK.
But watch this-- again if I hit reload, Command R or Control R,
I get the same Warning.
Are you sure you want to submit the form?
Yes.
If I do it manually with the reload icon, I get the same Warning as before.
But if I want to manually induce a GET request, well that's fine.
Don't hit reload and send the same request.
Instead go up to your URL and just put the cursor up there and hit Enter.
And now notice, same URL is a GET by default.
So any time you and I have typed URLs into browsers,
GET is always the default. Only when you click on a button,
typically, that the programmer has configured
to use POST, are you actually adding things to your shopping cart,
or the like.
All right, so we are back.
And if I go way back in time myself, like this is actually
the first web application I made back in 1997 I believe.
So at the time this would have been, what, my sophomore or so year.
I had taken CS50, I took a follow on class
called CS51, which is a different type of programming.
And then I pretty much taught myself a language called Perl, which
is somewhat less popular nowadays.
But it's another language like Python, like Java, like JavaScript,
like others that can be used to make web based applications.
And the web was very young at the time and the process via which
students, my classmates, could register for the first year intramural
sports program-- a.k.a.
frosh IMs was to grab a piece of paper, and write your name and email
address on it, and walk it across the yard to Wigglesworth, I believe,
where the Proctor lived.
And you'd slide the piece of paper under the door,
and that was how we submitted forms in my day.
So this was an opportunity, even back in 1997-ish, to move things online.
And the website went on to live on until I think 2007.
I found this online and then it's become something else since.
But this was a website via which people could register for sports,
and people could log in the scores for various games and whatnot.
And so underneath the hood, I didn't even
know anything about databases at the time,
it was just CSV files that I was storing the data in.
But there were HTML forms.
And there was with Perl, the language at the time, the way
to do the exact kind of stuff that we've just been doing already with Flask.
And so what I thought we'd do is implement a slightly less ugly version
of this.
Repeating graphical backgrounds were in Vogue in 1997, as you can see here.
But these were the aesthetics of the day, including the so-called Blink tag.
So let's at least focus on the functionality of this website
and not so much the aesthetics, and see if we
can implement some of the plumbing for actually solving a real world
representative problem.
Be it for freshman intramural sports, or something else like it
where you're getting data from users, and processing it somehow.
So let me go over here to VS Code.
Let me create a new directory called frosh IMs, just so we can keep
all of this code in its own directory.
Let me CD into frosh IMs.
Let me proactively make another directory called templates,
in which our templates-- our .HTML files--
do need to live.
And eventually I'm going to go ahead and create two
files minimally, app.py and index.html.
So let's do the first of those, app.py will live in my frosh IMs directory,
and I'm just going to recreate something very simple like we have previously.
So from flask in lowercase, import Flask capitalized, render template, and also
request.
So same first line as before.
Let me then give myself a variable called app,
set it equal to calling the Flask function, capital F, with __name__,
and then let me give myself a route for slash as before with an index function.
Though again, I could call that anything I want.
And just for now, let's return render template of,
quote unquote, "index.html" as though that exists.
So this is not really a web application as much as it is at the moment
just a recreation of HTTP server for one file.
Let's now in another tab create a templates file called index.html.
And I'm going to save myself a few keystrokes.
Let me copy paste from earlier almost all of the layout from before.
I've changed the title in advance to frosh IMs instead of Hello,
but this is essentially the same template.
And for now, though, because I'm in index.html,
I'm not going to use extends or any of that fancy block stuff yet.
I'm just going to go ahead and create a relatively simple form
via which back in the day my classmates could
have registered for intramural sports.
So let's go ahead here, and I'll propose that we do this.
In this page we'll have a form, the action of which will be a route called
/register, though I could call that anything I want.
It'll be somewhat private, so I'm going to use POST instead of GET,
just so that people don't accidentally maybe register twice
by hitting reload without warning.
Inside of this form, let's go ahead and give them
an input where autocomplete will be off, as always, for demonstration's sake.
Auto focus so the cursor goes there initially.
The name of this field will be literally name because I want my classmate's name
if they want to register for some sport.
The placeholder will again be, quote unquote, "name",
just so they see some gray instructions.
And the type of this field will indeed be text as before.
And then I need to give them the ability to register for a few sports.
Why don't we keep it simple, like back in the day basketball, soccer,
and ultimate Frisbee were three of the sports that we supported.
And so let me do this-- and you might not
have seen this before unless you dabbled further on with forms on your own.
But I can create a select menu-- otherwise known as a dropdown menu--
in HTML, inside of which are a whole bunch of options.
And each option typically follows this paradigm--
the value of the option, and then the actual text that the human sees.
So the value of these options will be-- how about we do basketball as one.
And I want the human to see literally the same thing.
Though just like with a link in HTML, they could be different,
but I'm going to keep them the same.
Another option will be, let's say soccer.
And-- oops, let me fix my quotes.
And this human will see the exact same thing,
though it could say something else.
And then lastly, the value will be, quote unquote, "ultimate Frisbee",
and the humans will see the same thing there-- ultimate Frisbee.
All right, so this is going to create, as we'll soon see, just a dropdown
menu with three separate options.
If I want the students to be able to submit this now,
let me give them a button, the type of which is submit.
And this button will be like the word register on it.
So I think we're pretty much good to go.
This is all just HTML-- no Python, no Flask per se,
except for the rendering of this same template.
So let me go into my terminal window.
Let me do Flask run inside of this directory
because I need to serve this app instead.
I'm going to see some ugly output, including my own URL.
And if I hover over that and then open that URL,
I should now see a more interesting form.
It's got not only a field for their name,
but also this dropdown menu with all three sports.
Now this isn't maybe the best user experience thus far
because I feel like I'm biasing people to registering for basketball
maybe because it's checked by default. I mean a lot of forms
nowadays have a blank placeholder for the form.
So this is just an aesthetic thing, but I can do this.
Let me go back to the same form and let me give myself
just a blank option at the top that in fact, I'm going to disable.
So you technically can't select it proactively,
but I am going to select it by default. And so
we probably haven't seen those HTML attributes before.
But if I want to create the equivalent of like a title for this dropdown,
I'm going to literally create a disabled option that's automatically
selected called Sport so that you can't select it per se,
but it is there at the top.
So if I go back now to my other tab, reload--
just marginally prettier than before.
And I'm not biasing people toward accidentally registering for basketball
alone.
And if I click on this, you'll see that sport is grayed out and therefore not
manually selectable, but I can select any of these other three still.
All right, well unfortunately if I type in David and I try registering,
for instance, for soccer and click Register, I do end up at /register.
And there's no question mark, or name, or sport,
so it's probably indeed POST instead of GET-- those are hints--
but not found.
Notice the tab here very succinctly says, 404 not found.
Well why is that?
Just to be clear, why did /register give me a 404?
What's the logic here?
Perhaps just state the obvious, or--
It doesn't exist, right?
We haven't done that step yet, all right?
So something as simple as that.
And so I actually sort of belabor that point because as you're
learning a lot of these conventions and some of this new syntax,
honestly you're just going to make stupid mistakes.
Something's not going to work.
But again, go back to first principles.
Why is it not found?
All right, register should be a template maybe called register.HTML.
Oh, I forgot my app.route.
So that should be the kind of thinking as you try to diagnose
these problems moving forward.
All right, so let me go into app.py, and let me give myself a second route here.
So app.route, quote unquote, "/route".
Then let me define a function called anything I want,
but I'm going to call it-- sorry, not /route, /register.
Let me call the function, just to be consistent, register.
So-- but I could call that anything I want.
And just for now let's not do anything too interesting,
let's just return the rendering of a template called success.HTML.
Let's just pretend for now that registration is successful
no matter who you are or what you do.
Now I need that template and I only have index.html at this point.
So let me actually now do my best practices.
Let me copy all of that.
Let me-- in a separate terminal window, let me do code--
let me go into my frosh IMs directory.
And let me create a new template called layout.HTML just like before.
Let me paste all that same code.
Let me delete the form and just put in that big placeholder,
so block body and then end block is all I did earlier.
This is just kind of boilerplate now convention.
Everything else I'm going to leave the same.
But if I wanted to make it prettier, I could add my CSS up top.
If I wanted to add this crazy repeating background,
I could probably do that up top too.
So I can make every page look as ugly as it did back in my day,
but we'll focus just today on the text.
All right, so add now that I have layout that HTML, let me clean up index.html.
I don't need all this redundancy.
I don't need all of these tags at the top.
Instead recall, I think I just need extends, quote unquote,
"layout.HTML" with the appropriate signs and curly braces.
I then have the appropriate block body, though I
could call body anything I want.
But I'm going to stick with my convention earlier.
And I'm going to delete the tags down here that I no longer need.
Why?
Because if I go into layout.HTML, I already have all my open tags,
all my close tags.
The only stuff I want in index.html is going to be which belongs in the body.
So end block down here.
And just to be pedantic, let me go ahead and highlight all that.
Hit Shift Tab and that will unindent it just to line things up.
Just to be tidy.
All right, so better, even though it looks a little cryptic now.
But now I've laid the foundation for making
a third page, a fourth page, that don't have all of that same copy paste.
All right, so now let's go back into app.py.
Success.HTML is where I left off.
So OK, let me open my terminal window.
Let me code up a template called success.HTML whose purpose in life
is literally just going to be to say, you are registered.
Just so that we see some informative message on the screen.
So this part I do still need extends layout.HTML.
So there's a little bit of copy paste still, which is a little ugly
but so be it.
Block body for this template, and I'm just going to say, you are registered!
All right, and then end block.
So super simple.
It's just an informative message claiming
that the student is registered.
All right, let's go back to the original form, which is this.
Let me reload to make sure my HTML has reloaded.
Type in David, I'm going to register again for soccer, and click Register.
And, oh, interesting-- method not allowed.
So I'm not getting a 404 anymore, I'm getting 405 at /register.
What's the deduction here?
How did I screw up this time?
405 is progress.
Yeah?
AUDIENCE: [INAUDIBLE]
DAVID MALAN: So it's not-- the placeholder I think is OK.
This is now about the underlying HTTP stuff.
The method was disallowed-- was not allowed.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Say again?
AUDIENCE: [INAUDIBLE]
DAVID MALAN: So GET versus POST thing too.
So by default, all of these routes in Flask just by default
assume GET because it's safe.
It doesn't allow you to send information to the server in quite the same way.
But if I do want to support POST, recall that we change this to be methods=,
and then a list with, quote unquote, "POST" in it.
So I just need to enable support for that method that is that HTTP verb.
All right, let's go back to the form.
Reload just to make sure I haven't screwed up.
Type in my name David, select soccer from the dropdown, click Register.
And now I'm not only at /register in the URL,
it claims that I am indeed registered.
Now of course I'm not--
I've done nothing interesting.
There's no database, there's no CSV file, we'll get to that in a bit.
But at least I now have the plumbing in place
to do something dynamic based on that sport.
All right, well how can I now improve upon this?
How about we go ahead and implement-- store the actual registrants
in a dictionary in the computer's memory.
So instead of just claiming that they're registered,
let's actually make a notation.
And the simplest way as we did weeks ago in Python,
is just store things in a variable in memory.
Like a list, or dictionary, a set, anything like that.
All right, well let me go back into VS Code and in app.py,
and I think what I'm going to have to do here is change my register route
to actually do some useful information.
But before I register the user, let's consider
where I want to actually put them.
And so let me propose that how about we do this.
At the top of my file, let me go ahead and declare
a global variable called registrants, and set
that equal to an empty dictionary.
So we've done this before when we were playing around previously
with using dictionaries to store key value pairs.
And I'm going to propose that we store the registrants as a dictionary why?
Because I'm going to keep it simple.
Like the name is going to be the student's name-- or sorry,
the key is going to be the student's name.
And the value is going to be whatever sport they registered for.
So David and soccer, and Carter and basketball, and so it's kind
of make sense for a two column dictionary, so to speak,
as we often depict it on screen.
So how can I use this dictionary?
Well let me go ahead and do this.
Down here under /register, let me go ahead and initially do this.
How about we get the user's name from request.form.GET
and set it equal to whatever the value of name is.
And I'm not going to give a default value now
because I don't want to call the student world or something strange like that.
I'm just going to assume for now that it's there.
Let's then create another variable called sport, and do
request.form.GET, quote unquote, "sport" to get these students' sports.
And then let's go ahead and do this-- in the registrant's dictionary, let's
index into it using the student's name, and let's
set it equal to whatever the sport is.
So I've got these variables just to keep my code tidy,
and I'm now putting a key value pair in that--
into that dictionary.
All right, well what do I want to now do?
And so I'll go ahead and say success.HTML, sure.
Let's go ahead and do that.
But now I think success.HTML means that.
So let me go back to the form-- reload.
Let me type in David and soccer--
register.
Let me go back and say Carter and basketball--
register.
OK.
Now let's see what I want to do next.
How about I go into--
let me give myself another route and let's play around here.
So app.route, let's give myself another third route
called registrants, whose purpose in life is just to show me
who all of those registrants are.
Just like you would expect from a website like this.
And then let me define a function called registrants or anything else.
And then let me return the rendering of a template called registrants.HTML.
And let me pass in--
this is kind of neat.
I can do registrants=registrants.
Which again looks weird, but what am I doing?
I'm presuming to pass in a placeholder called
registrants, the value of which is this dictionary that I've been
collecting all of the registrations in.
So similar to the name placeholder before,
but it's a little more powerful because now it's a whole dictionary, not just
a single string.
So I think now--
let me be creative here.
Let me go into my templates under my templates folder and let's do this.
Let's go into my terminal window, let's create
another template called registrants.HTML that's
actually going to do this displaying of all of the registrants for us.
So extends layout.HTML just so I can borrow all of the same HTML as before.
And let's define block body just like before.
And inside of this end block I want to put,
I don't know, a bolded list, or an ordered list of all of the registrants.
So how can I do this?
Well, let's do an unordered list, UL.
And here's where Jinja and Flask more generally get kind of interesting.
I want there to be something like this-- an li and then the student's name,
and then an L--
maybe-- li, yeah, like that.
And then maybe sport?
Something like this?
But I didn't pass in a name, I didn't pass in a sport,
I passed in the entire dictionary of registrants.
Now in Python, if we were just doing something
at the black and white terminal window and doing a command line program,
you know I'd probably have some kind of for loop in Python.
Jinja does allow you to do this.
So a templating language tends to come with very lightweight mechanisms
for doing placeholders, doing simple loops, doing simple conditions.
So Python like syntax, and it's almost identical.
So watch what I can do.
Inside of this unordered list, let me not
start to manually output a single li, let me use this syntax.
The same Jinja syntax that I used for block, so curly brace percent sign.
And I'm going to say this-- for name in registrants.
So this is just like Python syntax for iterating over a dictionary.
And now this is going to look stupid, but the opposite of that is end for.
So in HTML, you use the slash.
In Jinja.
You literally use the word end, no space,
and then the name of the keyword.
So end for is how you close this.
But this is where templating gets really cool.
You can now do li, and in here I can do something like that student's name.
And that's it.
I'm going to leave it like that.
And what I'm doing here is using really a template as templates are intended.
I've got the basic building blocks of what
I want this output to look like, but thanks to this little for loop here,
thanks to Jinja syntax--
the curly brace and the percent sign, I'm
going to iterate over every dictionary printing out name, name, name, name.
And so if I've got two kids registered now, I'm going to see two li's.
David and Carter respectively.
So let's see, let me go back to my frosh IMs tab here.
And I don't have a link yet, so I've got to do this manually
like a developer would.
Let me go to /registrants and I'll Zoom out and hit Enter.
And you'll see what you'll probably see too when making mistakes
for the first time in this world.
So where is the error message?
Unfortunately internal server error is not all that useful.
But we do tell you see terminal window.
So if I go to the terminal window, I haven't been paying attention
to this for quite some time.
And in fact, I have two terminal Windows open so
that I can still use commands at the prompt.
But if I go back to my first terminal window, a.k.a. bash there,
you'll see in your terminal window when developing
web applications, all of the mistakes you made in the terminal itself.
This is one of those Python tracebacks that's related to me screwing up here.
Now let me go ahead here.
And let's see-- type error-- function is not iterable.
End block-- for name function is not iterable.
All right, so what mistake did I make?
Well this is what happens when I don't follow my notes
and make changes on the fly.
So I have this variable on line five called registrants and all lowercase.
But what did I then do on the fly here in line 22?
I defined a function called registrants.
So newbie mistake, I shouldn't have done this.
I can't have a variable and a function of the same name
because the symbols are literally identical.
So just to make clear that this variable up here is actually global,
we'll use our convention like we did in C often, when we had a global variable.
We'll capitalize it all just to make it stand out like a constant value
up there.
And so down here, what I'm going to do is pass in registrants in all caps.
So that was stupid, didn't mean to confuse there.
But the reason for that error, to be clear,
is that you can't have a function that's the same name as a variable.
I could just change the variable name altogether.
I'm going to go ahead and just capitalize
it to make it really stand out that this is, in fact, a global variable up top.
All right, now I'm going to go back to my browser.
Let's do David and soccer.
All right, but there's going to be some other mistakes here.
So on line 17, let me go ahead and change this variable
to be capitalized there because indeed I want to put the key and the value
in this newly named variable as all capitals registrants.
Let me now go back to VS Code here.
Let me go back to the form and let me start adding some data fresh.
Let me register David for soccer.
Clicking register now.
And we should see, you are registered.
But, hopefully, now it's indeed in the computer's memory.
Let me go back and register now Carter for basketball.
Clicking register again.
And hopefully it's now registered.
If I now change my route manually to be /registrants,
which is this newly added route that I made, and hit Enter, now I see--
thank God.
Now I see the unordered list containing everything in the computer's memory.
So when I say, you are registered, I kind of
mean it now because the server is still running.
And in the computer's memory is, in this registrant global variable,
a dictionary of key value pairs.
Of course, we're only seeing the keys at the moment.
So it might be nice to actually see the values as well.
So let me go back to VS code and let me go into registrants HTML.
And I'll just do something a little messy.
I'll just say-- how about--
just to make it a sentence, is registered for.
And now another placeholder, I'm going to say registrants bracket name.
So just like in Python, if registrants is itself
a dictionary, registrants bracket and then the key you want to index into
is perfectly valid syntax as well.
So now let me go back to /registrants, let me click reload again.
So why isn't it working everyone?
What's the bug that I introduced earlier?
If David is registered for none, and Carter is registered for none,
but David and Carter are in the dictionary, that's a good thing.
So some of the data is in there.
So why are there no sports associated?
Well the first thing I literally just did in front of you
all was I went to app.py and I stared at line 17 thinking,
how did I screw this up?
I'm putting sport as the value of the key which is the student's name.
All right, line 17 looked fine to me a few seconds ago.
So I look then with my eyes at line 16, and this, too, looked OK.
My first thought was, oh did I use request.args instead of request.form
instead?
Because that would have assumed GET instead of POST.
But no, that looks OK too.
So then my final instinct was. oh my God, did I screw up the HTML form?
And so that's why I went back over to my tab here.
I went to the original form here, I then view page source.
And this might not be as obvious to you if you've never
seen the Select menu before--
what is apparently missing here that might explain my mistake?
Yeah.
AUDIENCE: The name is form.
DAVID MALAN: Yeah, I didn't name this form field, quote unquote, "sport".
Now to be fair, you haven't seen me do this as a select menu before
and it's different from this input.
When you have an input tag, you literally say name= whatever
on the input tag.
It turns out-- I don't know why I skipped this earlier.
I probably meant to come back to it-- the select tag also
can take a name parameter.
So if I go back to the name parameter here and go back
and add the name parameter, let me go into that template, which
is index.html.
Let me add name= quote unquote, "sport" in all lowercase,
which is different from the visual aesthetic of this temporary disabled
option that's just there to make things prettier for the human.
Now let me go ahead here, and first I'm going to go into my terminal window,
and I'm actually going to hit Control C to stop the server altogether
because I want to throw away the contents of memory
and therefore get rid of that dictionary that had David and Carter and those non
values.
So this is sort of me clearing the computer's memory.
I'm going to rerun Flask run.
I get that same URL as before.
So I'm going to hover over that and open the new tab.
And just to be sure, I'm going to do view page source.
And here I see now, OK, now the form has both a name and a sport in it.
All right, now I'm really going to cross my fingers
because I intend for this now to work.
David will register again for soccer.
Register-- claims we are registered.
I'm going to go back and do it again for Carter and basketball.
Register-- we still don't have a link, so I'm going to manually go up
to the URL and change /register to registrants as before.
Zooming out and hit Enter.
And thank God.
Now I'm actually registered properly for this.
So-- oh, thank you.
So what is it, like 20 years later, I'm still
struggling to implement this site?
OK, so here now we have for the first time in Python and web stuff,
now we have a proper web application.
And it's not just echoing back Hello, David, Hello, Carter--
this could now work for any of you.
And it's currently served privately, but if I made this URL public,
I could put this on the web now and let anyone in the world register.
But there's some issues here.
There's some security flaws potentially.
And so for instance, let me go back to the web form here.
And let me open up the inspect tab-- the developer tools--
and just remind you that anyone on the internet, not only you, the developer,
but an adversary can see all of your HTML, see all of your CSS,
see all of your JavaScript.
But more importantly, because this is all client side in the browser,
there is literally nothing technically stopping them
from changing the HTML, or at least their copy of it.
And I did that last week with Yale.
I changed their website.
But no, I changed my copy of their website.
But when forms get involved, you could maybe be actually malicious now.
Because even though this dropdown menu only
has basketball, soccer, and ultimate Frisbee, suppose
I really want to register for--
how about let's say--
name your favorite sport.
AUDIENCE: Volleyball.
DAVID MALAN: Volleyball.
We really want to register for volleyball,
but this website won't let me.
Well there's nothing stopping me from going under the Elements tab
in my browser, going into the Select menu here,
and you know what, no one [INAUDIBLE] ultimate Frisbee.
Let's change this to volleyball.
And let's change this to volleyball--
Enter.
I'm going to close the inspector now.
And as requested, now we support volleyball in the form.
Now it's not changed on the server, to be fair,
but think about how HTTP works.
When I fill out this with say, let's see, Bernie's name--
Bernie really wants to register for volleyball as well.
At the moment, my code is just going to trust that what's in request form
is what was in the original form itself, no matter
whether the human adversarially actually changed it.
So if I actually submit this form and click
Register for Bernie and volleyball, even though that's not
one of the supported available sports, if I now go to registrants,
my website nonetheless has trusted that Bernie
and perhaps you are registered for volleyball.
So what's the implication of this?
Is surely happened in the past when really poorly implemented
websites allow you to specify the price of an item, for instance,
in your shopping cart.
And they just trust that when you click Submit or add to cart
it adds the price to the back end server.
If you're not validating the price and making sure, as with a database that,
wait a minute, that price is valid.
Or wait a minute, those sports are valid.
Who knows what people are going to do to your site?
And it's that simple to actually hack a website accordingly.
Now, we can very easily fix this with some week six style Python.
We really just need to do a bit of logic here.
And so let me propose this.
Let me go into app.py here.
And at the very top let me also create how about a global variable called
sports in all caps.
And I'm going to set that equal to in square brackets
the list of sports I actually want to support.
So I'm going to put in basketball here, I'm going to put in soccer here, and--
I'm sorry, no volleyball officially.
I'm going to put in ultimate Frisbee here.
So I've got this global list of supported sports.
Now think about how I made this form a while ago.
I just hardcoded these sports here.
Well I don't have to do that, I can draw upon my own official list of sports
instead.
So let me scroll down to my index.HTML rendering template here.
Let me say that the sports I want to support are these.
So just using the same placeholder trick as before,
but I'm now telling the template what sports we currently support.
Now if I go back into index.HTML, I don't have to manually do any of this.
Let me get rid of all three of those options, which I manually
inputted earlier, let me use my new trick with Jinja syntax
and say for sports in sports.
Then let me proactively say end for, just to finish that thought.
And then in here let me do option value=value=[? sports ?]..
And then so that the human also sees the same words,
I'm going to say sport out here.
So I've completely changed what was hardcoded--
manually typed-- to something now that's completely dynamic.
So now it's not going to stop someone adversarially like me
from changing the HTML, but watch this.
The behavior on the form if we go back is still now
the same dropdown as before.
So aesthetically, it looks the same.
But you know what?
Why don't we be clever now.
And let's go into app.py and the /register route,
and why don't we say this.
If-- how about sport not in sports, Then let's return render
template failure.HTML.
Now this template doesn't exist yet.
So let me just quickly make this real fast.
I'm going to copy that code from before.
Let me create a code file in failure.HTML.
I'm just going to paste this here.
So we have a super simple error message, and I'm going to say,
you are not registered.
That's what we mean by failure.
And now in app.py, consider what logic I've added.
SPORTS in all caps on line 22 is that same global list
as before by asking [? Pythonically, ?] if sport not
in sports, well then you hacked me.
Like you tried to inject volleyball or some other sport into request.form.
So I'm just going to say, no failure, not letting you register.
And I can do this a little more verbosely too.
Why don't I also say this.
If not name-- so if the name is blank, let's similarly return
a render template of failure.HTML.
In other words, if you didn't give me a name,
you left it blank, that's not useful for me running the sports program.
Let's also consider that to be a failure.
So if I go back to this tab now, I'm going
to reload just to make sure I have the latest client side, let me be lazy
and just click Register.
Enter.
You are not registered because I didn't give it an actual name.
All right, well let's go back.
How about I now type David but no, I'm not going to choose a sport.
I just want to register myself.
Nope, that did not work.
Now let me go ahead here and choose soccer.
This, I think, does work.
Let me go back now and try this hacker trick whereby I go into the dropdown
menu, I go into the Select menu.
I change, as before, ultimate Frisbee to volleyball,
and I'll change this one here to volleyball.
Let me close the tab now.
This looks like it's available now, but when I click Register this time,
it says you are not registered.
And this is much better than relying on other techniques you
might see or have seen online with regard to HTML because there's also
this trick.
Let me go back to the screen here.
Let me go back to index.html, and you might have seen online,
or you might eventually see online that there's
other attributes you can use like required.
You can literally tell the browser, uh-uh, this field is required.
You cannot leave it blank.
If I go back to the browser now, reload, and I again presume to be lazy
and I don't type in any name and click Register--
So that's kind of nice.
Now the browser is being a little more helpful for me, saying no, no, no.
This is required.
You have to fill this out.
But again, if you know what you're doing.
OK, well I disagree with your requiring a name of me.
Let me go in here.
Let me go over to this tag, let me delete the required attribute,
and now I slip through.
But I didn't slip through on the server.
And so there's a difference here and an important distinction,
and so many people in the real world still screw this up.
There's client side validation, like actually checking
that the data is as you expect on the client side, the browser.
And there's server side validation.
And even though client side validation, adding that required attribute
makes things more user friendly, right?
Like that was a pretty little pop up.
It tells me that it's required.
It just looks better than the previous version.
It is not trustable.
You cannot trust any input that ever comes from the user because clearly
with an hour or so of CS50, they can learn how to turn all of these defenses
off.
So even if you like the user interface better client side,
you have to, have to, have to do server side validation always.
Users are not to be trusted.
And as soon as any app or website you make becomes popular,
unfortunately then you have to deal with all of the adversarial possibilities
as well.
AUDIENCE: Is there any way you can hack inside the server side
information or potentially hack [INAUDIBLE] or something like that?
DAVID MALAN: A good question.
Could the adversary potentially access things sensitive like app.py?
Theoretically no.
If Flask itself is buggy, then sure, maybe.
If you're running some other software on your server on your laptop then sure,
it's possible.
However, if your server is properly configured,
theoretically they should not be able to get access to that.
With that said, we'll soon see, or you might
with your final project if you do something web based,
you're never going to want to write usernames
and passwords in your actual code.
You can put them in what are called environment variables.
So sort of in the computer's memory, but not
in your code just in case you or someone screws up,
there are still ways to defend against those kinds of possibilities,
however slim.
Yeah.
AUDIENCE: [INAUDIBLE] making sure volleyball didn't get registered.
DAVID MALAN: A good question.
And this comes back to first principles, just like in C and in Python,
as soon as you return from a function, that's it.
Nothing below that line of code executes.
And so to summarize the question, even though I'm
returning this failure.HTML template, how
am I making sure we still don't accidentally put volleyball
in that global dictionary?
It's because, for instance, if you don't give me a name,
on line 22 at the moment, I'm returning the failure template and that's it.
Lines 23, 24, 25, 26, 27 never execute.
In particular, 26 never executes, and that's
where I would have been saving the name.
Similarly, if we do we get an invalid sport that is either blank or not
in the original authoritative list, we return failure.HTML
as a template in line 25.
We never get to line 26.
So it just boils down to return and what that means in Python too.
Good question.
Other questions as well?
No?
All right, how about we make one more enhancement here or so.
Because the problem with storing everything in this global dictionary
might be what?
We've got it all working, finally, but why
is probably a global dictionary not the place
to store frosh IMs registration data?
What's the implication of this?
AUDIENCE: Might slow the whole process.
DAVID MALAN: OK, it might slow the whole process down.
But that actually-- RAM is actually good.
Memory is actually generally a good thing.
So not going to be a dealbreaker here.
Why might I not want to store that data, though, in that variable?
You can perhaps infer how I fixed something earlier.
Yeah, in the back.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Yeah, it gets-- it's-- the memory gets deleted.
Garbage collected, if you will, as soon as Flask stops running.
So if you so much as hit Control C, you've just lost all of your freshmen
who registered for the sport.
Probably not a good thing.
I did this deliberately a moment ago and I hit Control C because I did
want to clear the dictionary, but trusting that your server will never
crash, and your code will always work, and the power will never go out,
that's not the right way to build any kind of web application
with persistent data.
So what we probably want to do is reintroduce CSVs
and we've played with those in C and in Python.
Could totally use CSVs, but we also now have SQL at our disposal.
And let me propose that we do this in SQL instead.
And for this, let me go ahead and open up a version of the program
that I wrote in advance.
So let me go ahead and close these templates
which will look very similar but a little different
from the ones I wrote in advance.
And let me go ahead and open up in today's--
let me go into source nine, let me go into frosh IMs how about--
version four, technically, in the versions online.
And let me go ahead and open up app.py as follows.
So here is an already made version that does just a little something different.
At the very top, I'm importing CS50 SQL library,
which you might recall we used a couple of weeks
pass just to write Python that talks to a SQL database.
And this feels like an opportune moment to bring that idea back.
Down here on line eight, I'm creating a DB variable
that opens up a file called frosh IMs.db using syntax that we've seen before.
I did create this frosh IMsdb file in advance of class
just so that we have a couple of columns in which
to store names and sports and such.
Here's that same global array a global list called sports,
and let's just see what's going on down below.
If I scroll down to index, this is the same
as before as we wrote together on the fly.
Let's skip de-register for a moment and go now into register.
So this one's a little different, but let's see what I've done.
I've got some comments in here because I wrote it in advance.
And I think this logic is pretty much the same, though I tightened it up.
And I'm asking two questions at once using on line 38, the or keyword here.
Just to say, if there's not a name, or the sport
is not in sports, that is what we'll call, now, a failure.
But what's fun now is that on line 42, I'm using the CS50 SQL
library to execute some actual SQL.
And I'm going to insert into a table called registrants two columns--
name and sport.
What names and sport?
Well these two values with placeholders, plugging in name and sport.
Notice I'm using the question marks.
Absolutely necessary so we don't get one of those SQL injection
attacks because that too could be possible
if someone typed in some dangerous words or keywords like delete,
or single quotes, or semicolons in the form.
Here I'm letting the library sanitize the data.
And then this is a trick we haven't yet seen,
and it's really going to start tying things together.
I can also use a redirect function in Flask that has the effect of doing
the, if you will, safetyschool.org trick again,
whereby after the user registers, if I want to automatically show them now
everyone who is registered at /registrants,
I don't have to manually expect that they'll change the URL like I've been
doing for the past few minutes.
I can just redirect them anywhere I want on my app.
Or heck, I could redirect them to any URL on the internet using this function
call.
And it's just a nice way to send them to a different route
if you want them to see, in this case, those registrants.
So let me do this.
In the same directory, let me increase my terminal window size.
Let me do SQLite three of frosh IMs.db, and let me type .schema.
And you can indeed see it wraps onto two lines
here that each registrant has an ID--
which will be automatically assigned 1, 2,
3, on up-- a name which is not null text,
and a sport which is also the same.
And the primary key is just going to be this unique identifier.
So that I made in advance.
But if I do select star from registrants semicolon,
there's no one currently registered for any sports.
But let's try now running this.
Let me go ahead and close my old version, which we wrote together.
And I'll close that tab.
Let me do Flask run in this version four here.
All right, I'm going to see some similar output.
I'm going to open the URL now.
And you'll see that I made a couple of changes before.
Instead of using a select menu, I used what are called radio buttons now,
which is a reference to old school radio buttons
that were mutually exclusive in cars back in the day.
And we'll see how to do this.
But it's just an alternative to a select menu.
And I'm going to go ahead and type in my name again here.
So I'll do David, I'll do soccer by selecting this radio button,
and I'm going to click Register now.
And notice what happened.
It's a little ugly the formatting, but so again was this 20 years ago.
Here I have now at the /registrants route, instead of an unordered list,
I'm just using a simple HTML table.
So I'll show you what this looks like in just a moment too.
And I'll show you this deregister button,
which is sort of unnecessarily large.
I also have functionality, we'll soon see,
for how you can unregister someone from a sport as well.
So take your name out of contention.
Well, let me go back to my terminal window here.
And I'm going to click the plus to give myself a second terminal so I can go
back into source nine frosh IMs four.
I'm going to do SQLite of frosh IMs.db.
I'm going to do select star from registrants now,
and now you'll see that indeed there's David registered for soccer.
And in fact, if I quit the Flask program with Control C and rerun it again,
no big deal because that next version of Flask
will just use the database as well.
So I'm persisting keeping the data in SQLite,
whereas I'm actually grabbing it using my Python code in Flask.
All right let's put one more person in here so we can delete one of us too.
Carter for basketball, register, and now we see both of us here.
All right, so let's see how we did this.
Let's go back over to VS Code, let me shrink down my terminal window.
Let me go into the--
actually let's go into the templates directory,
and let's look at, for instance, index.html.
So previously we were using a select menu.
Turns out radio buttons use the input tag,
but instead of having an input of type=text, like for the human's name,
you have type=radio.
And so long as each of your radio buttons
has the same name, the same name, the same name,
that's what makes them mutually exclusive.
So checking one radio button turns off the others
because they have the same name.
The value I want to assign to each of these radio buttons
is just the sport placeholder.
This is what the human sees on the screen.
So it's almost the same as the Select menu,
it just looks aesthetically different.
But there's my same button.
So that's all the difference I made there.
And I added a heading tag, H1, just to say register to make clear what it is.
But let's take a look at another file.
This one now being, how about the /registrants route.
So if I open up registrants.HTML here now,
it's way more verbose than my unordered list.
But this is just kind of boring HTML.
Here's my table tag, table head, table row, table heading.
This makes things bold as the first row of the table.
Name, sport, are my two columns.
I've got a third empty column just so I can fit that button,
as we'll soon see again.
T body for table body.
Here's the same for loop trick again so that I can output
for every registrant a whole table row.
And there's this weird form in there, but we'll come back to that.
But there's the registrant's name, there's the registrant's sports.
But notice the slightly different syntax here.
Recall that CS50's select--
CS50's execute function, when it returns to you a list of dictionaries,
you can then get at the individual columns by way of those keys.
So let's go to the /registrants route.
Let me go back to app.py, scroll down here, and it's actually super simple.
Here I have a /registrants route that first executes select star from
registrants.
So just old SQL stuff.
Give me every one from the registrants table,
let me then render the template called registrants.HTML
and just pass in this list of dictionaries.
And we haven't quite done this yet, but if you go back
to registrants.HTML, how do you iterate over each dictionary in that list?
Well the syntax is just for registrant and registrants.
That makes this a dictionary one at a time in the list, just like in Python.
So registrant.name and registrant.sport is just another syntax
for using the square bracket notation.
It's just a little cleaner and slightly more
succinct than having quotes and square brackets everywhere.
And then the rest of this is just HTML.
So what happens now if I want to--
Carter been cut from the basketball team, if you will.
So how do we do that?
Well, we want to click this button, deregister, next to Carter's name.
But how does this work?
And think about now any website you visited
that has something like a shopping cart where you can remove things
from your cart, or update quantities, or add more quantities to your shopping
cart on Amazon or anything else.
Well, let's actually look at the HTML that my app has spit out.
Let's actually look at this here and we'll
see the following-- we'll see that we have here in the HTML
that reached the user, not only is David in the first column, soccer
and the second, notice that my registrants.HTML form is also spitting
out a tiny little web form of its own.
It's ugly but I only care about its functionality for now.
And notice what I'm doing here.
Every registrant in this database gets their very own deregister button.
And that form has a button that says deregister.
But notice what else each of those forms have.
There's no text box, there's no dropdown menu, there's no radio buttons.
Rather you have a hidden input field here.
So there is a way with HTML to have a form that will submit information,
but you don't have to give the user the ability to change that information.
You can just go ahead and tuck it inside of the form invisibly, if you will.
Hidden in fashion.
And so what's going to happen is if I click the deregister button
next to Carter, his primary key is two.
Mine is instead one.
So what's going to happen if I click his deregister button?
It submits a form with a ID parameter whose value is two,
and it submits it to the de-register route.
So what does that mean?
Well, if I go to VS Code and I go to app.py let's
look at the de-register root that I skipped over.
So if you access the deregister route via POST, this code gets called.
I grab from request.form the ID that was submitted in hidden fashion.
If there's indeed an ID, that is it's not blank, it's not zero,
it's an actual number like 1, 2, 3, or more,
I execute delete from registrants where ID = that value
with a question mark placeholder.
And then I redirect the user back to registrants.
Now if I go back to this form here, I click deregister,
we'll see that in action.
Gone is now Carter.
And in fact, if I go back to my terminal window here,
I open up SQLite three of frosh IMs.db and rerun select star from registrants,
Carter is now gone.
So again using very simple HTML forms, you
can get buttons, and links, and other such UI mechanisms
to do things on the server that you want.
But there is a danger here.
This now is really meant-- this example as an administrative website
like it was some 20 years ago just for us internal staff to be doing things.
Technically this is dangerous, what I've just done, too.
Even though Carter's ID is two and hidden, and mine is one and hidden,
what could this allow an adversary to do if they
had admin access to the same site?
Any thoughts?
Yeah.
AUDIENCE: Could they change the value and then deregister?
DAVID MALAN: Yeah.
They could change the value of that hidden attribute
by opening up Chrome's, developer tools, change the number in the HTML.
They could delete anyone, de-register anyone they want from the database.
Now in this case, I claim this is fine because this is only
meant for us staff who were running sports back in the day.
But it's indeed a risk.
So wouldn't it be nice if we could actually
ensure that only those users who are authorized
are allowed to execute certain actions?
I think for this capability we're actually
going to need to introduce something a bit more.
And so here, of course, is an opportunity
to talk briefly about, really, what you and I do all day long every day.
We log in to one or more websites or apps,
or at least until you're logged out automatically
and you have to do it again.
So here for instance, is a screenshot of Gmail.
When you type in your username, you type in your password, maybe
your two factor code that gets texted or sent to your phone,
then you're logged in.
And thankfully you're not prompted to log in again, typically,
for a number of hours, or days, or weeks, depending on the website.
Like Gmail keeps you logged in for ages.
Your bank probably logs you out within an hour or so for safety's sake.
So that is completely configurable on the server.
But how does Gmail know, how does Google know
that even as you're checking different mails again, and again,
and again, how do they know that you're still the same person who logged in?
Well it turns out that using these same building blocks as today--
HTTP and HTML and more--
you can actually implement the notion of a login feature
by doing the equivalent of something with something called cookies.
Essentially what happens when you first log
into a website for the very first time successfully
with your username and password, a cookie, so to speak,
is planted on your computer.
And metaphorically, this is kind of like taking a hand stamp,
and your hand is now stamped-- in this case,
a smiley face-- so that every other time you
click on a link on that same website-- google.com,
gmail.com, whatever-- unbeknownst to you,
your browser is constantly presenting that hand stamp.
Just like going into a club, or an amusement park, or something
like that where they don't want to check your ID again.
They don't want to check your ticket again,
they just want to quickly see the same hand stamp.
And so that is one of the things that a browser is always
doing for you once you're logged in.
Any cookies that have been planted, so to speak, in your Mac, or PC, or phone,
are constantly represented to the site every time you click a link
or make another request to that website.
And mechanically how this works, not just metaphorically in ink,
essentially this is what happens.
Here is an example of an HTTP request to something like Gmail.
And suppose, for instance, that you've logged in.
Typically, as of last week, we said that coming back from the server
would be another virtual envelope containing a 200 OK message, and then
the actual web page, or the picture of a cat, or whatever it may be.
But Google can also, if they verified that you have some username
and password correctly inputted, they can do
the equivalent of stamping your hand.
And the way they do this is they send an additional line
of text in that virtual envelope from the server to your browser,
literally using another HTTP header.
Not content type-- which just mundane tells you what kind of content
has come back-- they literally send an HTTP header called set-cookie.
And then they set a key value pair on your Mac or PC.
This is the technical equivalent of this smiley face hand stamp.
And what your computer is designed to do--
because your computer and internet browser are supposed to implement
HTTP--
any time you click another link on Gmail, or click on another mail,
or the like, your browser unbeknownst to you, presents that hand stamp.
And how it does it technically is in the envelope it
sends to Google from your browser, it doesn't
send set-cookie it just sends cookie: and the exact same thing.
And so long as Google is smart and they have a database or something
of all of the session values--
session is the technical term for this maintenance of information across HTTP.
So long as Google has a big database that
knows that my cookie value is 1, 2, 3, and your cookie value is 4, 5, 6,
they can infer from this virtual hand stamp
whose emails they should be showing.
Mine or yours or the like.
So this is just scratching the surface.
But if I really want it to enable only Carter to deregister himself,
I just have to make sure that I log him in somehow--
username, password, all of that.
I stamp his hand, or really, put a cookie on his computer.
And only if his cookie lines up with the user
ID he's trying to deregister should he be allowed to in fact do so.
So all of this is quite possible.
And indeed the technical term for this is session.
And what we thought we'd do in our remaining time today,
show you some examples of exactly how some of the most familiar web
functionality is implemented today, some of which
you'll use in your own problem set nine, which itself will be a web
app or perhaps even your final project.
So let me go ahead and do this.
Let me close my previous tabs and all things frosh IMs, and let's move
on to implementing some notion of login.
So in just a moment, I'll switch over here to VS Code,
and what I'm going to do is indeed in my source nine directory,
I'm going to go into a login directory.
And if I type ls here, you'll see app.py,
requirements.txt, which just refers to libraries I
want to automatically install, and a templates folder as well.
I'm going to go ahead and stop the previous server
and close that terminal window, and I'm going
to open up this version of app.py.
So there's a few new lines here.
And we'll give you these lines for problem set nine,
but I've got some of the familiar stuff up here, including this new redirect
function we just used.
And I have a session variable that comes with Flask too.
So what's nice again about Flask is that it deals
with all of this cookie stuff for you.
It sets the cookie.
It checks the cookie.
And what Flask does for you is it gives you
the abstraction of a variable called session so that anything
you put in the session variable, which itself is a dictionary,
will be there again, and again, and again
whenever that same user comes back.
A session is how you implement, essentially,
the proverbial shopping cart.
If I'm logged into Amazon, you're logged into Amazon,
Amazon knows which of us is which by way of that cookie.
And Amazon, if they're using Flask, provides the programmer
with a dictionary called session.
And flasks make sure that when Carter is visiting the site,
the code uses his session object.
When I'm visiting the site, it uses my session object.
But it's all implemented with those same cookies.
This is the same as before.
These lines are new, and you'll see these in problem set nine.
This is how we enable sessions in a web application.
And I'll just wave my hands at the detail.
There's different ways to implement sessions,
whether you use cookies on the server, cookies on the browser,
or other things.
These just ensure that we're storing the session information
the shopping cart on the server itself.
Now down here, let's go ahead and do this.
Let's go ahead and run Flask.
Run so I can see what this app does.
If I do this and visit the URL that gets outputted,
you'll see a very simple web page here.
And if I type in, for instance, my name--
I'm not going to bother with a password-- and click Log In,
you'll see that you are logged in as David.
And now I can log out.
So I'm going to go ahead and click Log Out.
And now it seems to know that I'm not logged in again.
I can log in as Carter because I didn't bother implementing passwords
for simplicity, but now the site knows I'm logged in as Carter.
Better yet, if I reload, reload, reload, or click this button again,
and again, notice it still knows that I'm Carter until such time
as I log out.
All right, well how is this working?
Well let's go back to VS Code here and let
me scroll down to, first, this route.
This is a very common paradigm here whereby
I'm checking for the index route.
If there is not a name in the session, redirect the user to /login.
Now what does that mean?
Well, let me go back to VS Code here.
Let me go to the /route.
So again, your URL will be different, but I'm just
going to go to slash and hit Enter.
Notice that I got automatically redirected to log in.
And so many websites do this.
If you go to a website and you're not logged in, you're very often redirected
to /login, or /account, or something like that, where you're prompted.
The code for doing that is right here.
If there's no name in the session, if there's no name in the shopping cart,
if you will, go ahead and return the user to log in, that route.
Otherwise, , implicitly if they are logged in, show them index.html.
So let's go down that rabbit hole.
Let me open up in VS Code a second terminal window.
Let me go into source nine and go into this same login demonstration,
and let me open up the template called index.html.
And here's all this is.
There some layout, but who cares at this point,
it's just the boilerplate generic HTML.
Here is the body block.
I have this.
And this, too, is Jinja because of the curly brace and the sign. percent sign.
If there's a name in the session-- this is just Python syntax--
then say this sentence.
You are logged in as--
whatever name is in the session, in the shopping cart, if you will.
And then I just have this HTML link for logging the user out.
Else if there is no name in the session, logically,
just say you are not logged in.
And give them a manual link for logging in instead.
So that's all this particular template does, but how does the /login work?
Well let's go into this other template, code of login.HTML,
to which I'm redirected.
Super simple.
This is just copy paste from HTML before.
I've got a login form that's going to have an action of /login,
submits for privacy sake just by a POST, and then the rest of this is just
a simple form.
And I'm using an input type=submit instead of button=type=submit,
but same idea here too.
And if I go back to app.py, well let's see how login works.
All right.
It's a lot all at once, but they're relatively simple reapplications
of the same idea.
So if the user visits /login via GET or POST, call this function login.
If the user has submitted via POST, and we saw this technique before,
go ahead and do this on line 23.
Store in this special session variable that comes with Flask a name key,
and store in it the user's own name.
So, quote unquote, "name" will have a value of David, or Carter, or the like.
And as soon as you do that, redirect the user back
to slash just so they see the home page again.
And this is how Amazon and all these other websites work too.
Otherwise if they visit this page implicitly via GET,
and even though I didn't say = = GET anywhere,
that's the implication because if you can only get here by a GET or POST,
and we already handled POST, logically all that remains is GET.
Well then just show them the login screen instead.
But there's a half a dozen ways we could express that same logic.
And then for log out, this is kind of straightforward.
If the user clicks that logout link and ends up at /logout, this route,
well just change the value of that key in the session to be none.
Effectively, no, Carter is gone.
David's gone.
There's no one logged in.
So that is all that's required to actually implement
the notion of logging in and logging out of a website.
Plus the password thing, which should probably involve the database,
but one thing at a time.
And really, session is sort of like the code version of a shopping cart
whereby if I visit the same code, I get my own session object.
If Carter visits the website, he gets his own session object.
And the way Flask keeps us straight is they
put one cookie on my computer, a different cookie on his computer,
and uses those to line up with making sure the right session gets
shown to the right actual user.
Questions on this notion of sessions?
No?
All right, how about a couple of final examples just to tie this all together.
Let me go back into VS Code here.
Let me quit my previous version of Flask.
Let's go into source nine and go into store,
which is a separate app altogether.
And let's start by just running Flask to see what it does.
Let's hover over the URL and open it in another tab.
And this is pretty ugly too.
Let me Zoom in.
But it's a very simple bookstore.
Like an early amazon.com for each of these seven books here,
each of which seems to be--
maybe this is H1, this is H2, H2, H2, H2.
And then there's a button underneath each.
Well now let's use this as an opportunity to kind of infer,
like for any website, how this thing works.
Let me go ahead and do view page source--
and you can do this for any website on the internet.
Let's try to figure out how this bookstore adds things to a cart.
Well here's the H1 tag.
Uninteresting.
H2, H2, H2.
So the juicy part is in these forms.
Each of these forms has an action of /cart,
so that's the route that's going to be interesting in a moment.
And it uses POST for privacy's sake.
Each of these forms like the deregister feature for Carter,
has an ID attribute-- an ID parameter that's hidden, visually,
that has a value of 1, or 2, or 3.
So like the unique bar codes for the books, if you will.
But super small numbers, in our case.
And then each of these other forms just--
each of these other books has an identical form
except for the value of this here.
Now in this case, this isn't such a big deal
that a user could technically hack the HTML of this bookstore, amazon.com,
and change the IDs because, whoa, what's the worst they're going to do?
Buy more books by adding more IDs to their shopping cart?
That's not a problem.
There's no prices here, it's just the unique ideas of books.
So whereas the deregister was maybe worrisome
because you're changing the server, I think
it's OK because the user can only at worst buy more books than they
might via the buttons alone.
So how does this now work?
Well let's go into VS Code again here.
Let me give myself another terminal window.
And in source nine/store let me open up app.py which is where all the logic is.
So I'll flip through most of this quickly because we've seen this before.
These imports are pretty much the same as before.
This line is the same.
This line is almost the same but the database now
is called store.db instead of frosh IMs.db.
This is the boilerplate code for just enabling sessions,
this notion of a shopping cart.
And so let's see how the index works.
How is it I'm seeing all seven books at once?
Well in this index function, I'm using on line 19, select star from books
to get all the books from the database.
And then I'm rendering a template called bookstore HTML passing in
as a placeholder all of those books.
All right, let's go down that rabbit hole for a second.
Let me open up books.HTML in my templates directory.
And here again, even though it's new today,
it's probably increasingly familiar syntactically.
Here's the H1.
Here is my loop.
Here's the H2, which is going to output the current book's title in that loop.
Here's the form.
Here's the ID of that book that's going to be outputted in the form.
So I didn't manually type out seven long forms, I just did one in this template
so that it gets generated automatically.
Literally what a website like Amazon would
do to show you 10 books at a time, or 10 search results or more at a time.
All right, well let's go to /cart, which was the juicy one.
And this one's longer, but let's see if we can reason through it.
If the user submits via GET or POST to /cart, well we first do this.
And this is just necessary and some boilerplate rewrote.
We're going to use the session object to store a variable called cart that
in this case, is going to be a list.
So session, again, is just a dictionary.
You can put anything in it you want.
Previously we put students' names and sports.
Now what I want to do is--
sorry, previously we put the student's name, the user's name in it.
Now I'm going to actually store a cart key whose value is a list.
Why?
Because I want to aggregate more and more books in this list.
All right, so that just makes sure that I have at least an empty shopping cart
the very first time the user does this.
If they visit this form via POST, let's go ahead and get the ID of the book
that they posted.
If it is not empty, if there is a number, like 1, or 2, or 3,
let's go ahead and go into the shopping cart--
which is a list, per this line--
and just append that ID.
So this list of books in your shopping cart
is going to contain 1, 2, 4, 6, whatever books you're actually buying.
And then the user gets redirected to cart.
What if, though, the user got here via GET and not POST?
Well this one's relatively straightforward.
If you just visit /cart, we select star from books where ID is in--
OK, so this is interesting--
this list.
And this is syntax you might not have seen before.
But if you read the documentation for CS50's library, if you
select something and use a question mark placeholder,
and the placeholder itself is a list, we output a comma separated list of values
just like you would use maybe in problem sets seven
for doing SQL queries on your own.
So this just means show me only those books in my shopping cart.
Not Carter's, not someone else's, not in the whole database,
only show me the books in my shopping cart and then render it as such.
So we only saw what the catalog here looks like at /books.
Let's go ahead and add maybe the first book to my cart.
And now I see at /cart only that first book whose ID is one.
Let me now go back to the bookstore here,
scroll down to maybe the seventh book and add that to my cart.
And now I see this here too.
Meanwhile all of this information is stored in my session.
And so when I reload this cart again and again,
the reason I'm only seeing my two is because we're
checking only the list in my session.
And flasks make sure, again, that my session is different from your session,
is different from Carter session as well.
But you write the code once and it works for thousands,
millions of people in parallel.
Any questions on this?
Yes, in the back.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Sorry, say a little louder.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: So to recap.
So users will never have the same session values.
Theoretically, the cookie that gets planted
does not look like a smiley face for everyone.
Each of us gets a big random number that's assigned to us.
So it'd be like each of us gets a completely unique hand
stamp that no one else can see.
The reason no one else can see it is because if the website's using HTTPs,
every time this hand stamp is shown, every time this cookie is sent back
and forth, it's all encrypted as well.
So each of us can-- even if we have the same contents by coincidence
because we like the same books, they will be separate cookies,
separate memory, separate sessions.
Behind you.
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Really good question.
When does the session end?
Totally configurable.
Typically it ends when you close the tab or when you quit the browser.
Or you can also configure cookies to themselves be persistent for a day,
for a week, for a longer.
So for instance, when you log into say Gmail,
they plant a cookie on your computer probably
for a week, a month, a year, something like that
because it would be annoying and probably drive you to Outlook
or something else if you kept having to log into your account.
Whereas your bank account might actually wait for you to just close the tab.
And then for your own financial safety they just
automatically delete the session far sooner.
But totally configurable.
By default as I'm using it, it will typically
be thrown away when the browser itself quits.
And here, too, is another reason to develop websites using incognito mode
because if you want to just throw away all of your cookies,
you close the incognito window mode.
Open a new one and now you're starting from scratch.
You don't have to manually delete all your cookies,
which could log you out of websites you actually care about.
Yeah.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: A good question.
When using sessions, if someone maliciously changes
the value of such forms, could it affect other people?
Theoretically, no because the worst you can do
is like add books to your own shopping cart that you don't want there.
So at that point, even though it's on the server,
it doesn't affect you, or Carter, or anyone else,
unless there is something more globally happening like registering for a sport,
or removing books from the Amazon database.
That would be problematic.
But in this case, we're removing things only from my own session
that the website is giving me.
All right, the last topic for today is this thing here,
which is sort of everywhere nowadays, these things
called APIs, or application programming interfaces.
And this is a very generic term, in fact.
Because any function you've used in C, in Scratch, in Python,
in SQL, are all APIs.
There is a standard way of interfacing with those functions.
They all have names.
They sometimes have return values.
They sometimes have arguments.
And an API is just how you use a function.
Or more generally an API just specifies how you interact with some service.
And so nowadays there's a lot of web based services
that you can use to get back data like the weather, or the current time,
or the database of Amazon books, for instance,
all might have APIs, often web based, that
allow you, using URLs or some other technology,
to just get data from someone else as though it's
a function you're calling remotely.
But HTTP is very often the mechanism that's
used to actually get data from servers.
And the way the data can come back can be as follows.
Let me end with one final example using some of our familiar shows
from weeks past.
Let me go ahead and close the old Flask version, go back into source nine
and go into--
how about an example called shows.
And the first version of this, zero, I'm just going
to go ahead and run with Flask run.
I'll hover over my URL and open it here.
And you'll see now that I have a very simple form, as we keep doing today.
I'm going to type in like O-F-F-I-C-E, office,
into this search box and click Search.
And you'll see now that I ended up at a URL ending in /search?q=office.
So this is like my own baby version of google.com but I implemented it myself.
And for any title of a TV show from a couple of weeks past that matches
O-F-F-I-C-E, I spit it out into an unordered list.
How is this working?
You can maybe imagine, even if you might not
be able to program this off the top of your head, certainly, so soon--
let me go into source nine.
Let me go into show zero, let me open up app.py.
And in this file, you'll see that I'm grabbing
a file called shows.db, which is like a simpler version of the one
from a couple of weeks passed.
Here is why I see the web form.
My first route, my index is super simple.
It just spits out that form.
And my search route, you can think of this as google.com,
is only like four lines of code.
So if the user sends data to /search, this function called search is called.
I declare a variable called shows, I execute
a SQL command that is select star from shows where title like question mark.
And the syntax here is a little crazy, but I
want to prefix to the user's input percent sign,
and suffix it with a percent sign as well,
putting in between those two values the actual input, why?
In SQL what does it mean if you have a percent sign to the left
and to the right?
Nothing to do with Jinja today.
AUDIENCE: [INAUDIBLE]
DAVID MALAN: Yeah, it's a wild card.
So it means match zero or more characters on the left,
or match zero or more characters on the right,
you have to do the concatenation as the second argument to this function.
You can't do something clever like put it here around the question mark,
the question mark is the placeholder that you plug these values into.
But this just means, hey SQL, show me all of the titles that have O-F-F-I-C-E
somewhere in them.
That gives me back a list of dictionaries.
I pass that in as a placeholder for a variable called shows,
and if we look at search.HTML, let's look at that.
In my templates directory there's something called search.HTML.
Super simple, I mean this is like the essence of google.com search results.
I'm using an unordered list to keep things simple,
but I integrate over every show in the show's list that came back,
and I output an li with each of those shows' titles.
And that's it.
Now Google has blue links, and little previews, and other text,
the first sentence or so from each page.
But that's the idea.
This is really similar in spirit to what google.com/search does for you.
Now how is this working there's no app.py involved here yet.
This is just very basic HTTP.
I submit the form, I go to another route, and I get back the results.
But check out this version.
Let me close these tabs here and open my first terminal window.
Let me go into shows one from today's source nine directory
and do Flask run this time.
Let me go ahead and hover over that URL and open it here.
And gone now is the Submit button.
Now I'm going to make a user interface that uses a technique called AJAX--
for Asynchronous, JavaScript, and XML, which is somewhat of a dated term
because we're not using something called XML anymore.
But AJAX is a technique whereby you don't have to submit forms anymore
to get more data from the server.
You can use JavaScript, per last week, listen for an event like the key press
coming down or up, and as soon as you hear such an event,
you can secretly in JavaScript code send a request to the server to get back
more data, and then plug it into the Dom-- the tree--
in the computer's memory.
And this just makes for more seamless experiences
like autocomplete on any website.
So now let me try typing O--
we got autocomplete super fast.
F-F-I-C-E. And you'll see every time I add more keys to my input,
I'm doing another search, another search, another search,
and the data is changing.
Now how is this working?
Well let me go back to VS Code here.
And in my other terminal window, let me open up app.py.
And in app.py, you'll see that there's still
a search route down below that returns a search template, but watch this.
Let me go into templates/search.HTML and notice here that we're indeed getting
back an unordered list of shows again, and again, and again.
And this HTML that's coming back--
let me go here.
Let me open my terminal-- oh, sorry.
This is the wrong version.
Sorry, I was in the wrong folder.
Let's fix this.
In shows one, code of app.py, it's almost the same thing.
In search here--
OK, well I change this slightly.
Let me show you this version of search.
If I open up app.py, here's my search route.
I'm getting a variable called q, giving it the value of whatever request.args
has from the user, like q=office.
And then I'm checking if the user actually typed
something in execute this SQL query.
Select star from shows where title is like that using the same.
And this time just to keep things efficient,
I limited the total results to 50 instead of an infinite number.
Otherwise if the user type nothing, just to be super safe here,
I'm setting shows equal to an empty list.
So if you don't type anything there's nothing to show.
And no matter what, I render this template called search.HTML.
Well, let's look at that.
If I open up templates/search.HTML, this time there's no layout.
There's no inheritance of that layout .HTML,
I am literally generating just a whole bunch of li fragments.
Why?
Well let's see what's happening in the browser.
And this is the beginning of an API.
If I wanted to make this API available-- this application programming
interface--
I could tell the world that if you want to search for TV shows in my database,
go to URL, that's something, something, /search?q=office or cat, or dog,
or anything else.
And what I will return to you is this.
Enter-- I will just give you a whole bunch of li tags, which
almost looks the same.
But let me view the page source.
Only am I going to hand you back a fragment of HTML.
I'm not giving you an HTML tag, a body tag, a title tag, a head tag.
I'm not giving you a web page.
I'm giving you a fragment of HTML that you can now do whatever you want,
including insert this into your own unordered list.
So notice what happens in this actual app.
If I go back to VS Code here, let me open up my index template here,
and you'll see some JavaScript magic.
So in JavaScript here, in my form that only had the textbox and no button,
what am I doing?
In a script tag here, I am creating a variable called input.
And I'm using this function called query selector that
just gets me a reference to the input text box on the form.
So I can see what the human typed.
This is a little different today, but I'm
using input.addEventListener, which is a way in JavaScript to tell it, just
like in Scratch, listen for something to happen,
like the green flag being clicked.
But in this case listen for an event that involves input.
That is like typing on the keyboard, whether it's
by a copy paste, manual input, or anything else.
Then whenever that happens, call this function.
And async stands for Asynchronous.
This is a term of art which means that this function might
take a split second, maybe even a second or two to execute.
So it's going to do it behind the scenes, like in the background,
so to speak.
And what is it going to do?
Well, it's going to call a JavaScript function that all browsers now
support called fetch, which is a function that uses HTTP to go
fetch more data via from the server.
It's going to fetch data from a route called /search?q= and whatever
the value is that the user typed in.
So I'm just manually creating my own mini URL
and telling JavaScript go fetch me that HTML.
When it comes back via this line of text here called response.text,
and let me wave my hand at await.
Await just means this might not come back immediately.
Let's await the response and when it does come in,
then let's execute this code.
I'm going to do this.
I'm going to search the document, the whole web page, for this UL tag
which is somewhere in this page that we'll see in a moment.
Change its inner HTML to be that fragment of li,
li, li, of all of those matching shows.
And where does this all go?
Well if we scroll up here, you'll notice that there's
my usual HTML up at the top.
Head tag, body tag, and all of that.
There's the text box that we've talked about already.
There's no button for submitting because it all happens automatically.
But there is by default this empty UL that has nothing by default
until we start using that API.
And the final flourish here this.
This is kind of an ugly, sloppy way to get data from a server
to just get back a fragment of HTML.
Like what if I'm not using HTML?
I want to store these TV shows in a PDF, or in some other web tag,
in a table, or something like that.
It doesn't really make sense for the server
to be presuming that I want like tags surrounding each of my data.
Better would be to get a more generic format back.
And that format is almost always nowadays called
this, our final acronym--
JSON, JavaScript Object Notation.
And let me do this.
Let me close these two tabs here and open my terminal window,
and cancel the previous version of Flask that was running.
Let me close this version and look at our final version called
shows two and do Flask run.
I'm going to hover over that URL and open it in a browser.
And I'm going to manually visit, after zooming in, let's do again
/search?q=office Enter.
And this is what JSON looks like.
Now at a glance, this does not seem like an improvement.
This looks crazy that it's just this big blob of text.
But it's just enough text for the computer
to be able to process it reliably.
Notice that there's a square bracket here.
And if I actually scroll to the bottom, there
would be a closed square bracket like way down there.
Inside of that square bracket is a curly brace, then ID, colon,
and then a number.
Then a title, quote unquote, colon, and then the title.
And then the closed curly brace.
So what you're seeing in JavaScript Object Notation
is a very standard super popular format that's
just text that still uses square brackets for lists, a.k.a. arrays.
That still uses curly braces for dictionaries, key value pairs.
So what you see here is a massive list-- up to 50,
I think-- shows that came back from this API, each of which
has a dictionary, if you will, an object of key value pairs.
What keys and values?
An ID key and a title key, each of which has a value respectively.
And this is the same data from IMDb, some of which
you might be recalling visually.
This is just a very raw, computer friendly way
of returning a whole bunch of data that we humans don't need to see.
But I can use this data by going back into VS code.
Let me open another terminal window and go into source nine/shows2.
And in here, let me go ahead and open up, how about templates index.html,
which previously just use that inner HTML trick.
And this is not going to impress-- you're not
going to be pleased with this syntax.
But let me just at least explain what we're doing.
It turns out that JSON is just the better
way in general, the more generic, multipurpose, user agnostic, language
agnostic way of returning data from a server because it's just text.
So it doesn't matter if you're using Python, or C, or C++, or JavaScript,
or Ruby, or PHP, or something else.
All of those languages can process JSON information.
And indeed here is some JavaScript that does just that.
Same code is before initially.
I declare a variable called input that gives me access to the user's text box.
I listen for input like keystrokes going up and down.
And when they happen, I call this anonymous function.
I fetch data from the server using the exact same code as before.
Search?q=office, or anything else.
And then this is just now new code that I
use to convert that JSON data into my own HTML format,
be it an unordered list, and ordered list, a table, or anything else.
What am I doing?
I've got a variable called HTML initialize to nothing.
So I've got no HTML initially.
I then iterate over every ID in those shows.
So every one of IMDb's unique identifier,
I integrate over them one at a time.
And then I go into the show at its ID location and I grab its title.
And this is-- forget this for just a moment.
I then take this HTML variable, concatenate,
or join onto it, my own li tag, plus the title, plus the close li tag.
And I skip this because it got scary pretty fast,
but it turns out that if some TV shows have actually angled brackets in them,
that could break my HTML entirely.
So it turns out, you might recall super briefly last week
we had the copyright symbol, using an HTML entity,
using the ampersand, and the hash symbol,
and 169 semicolon-- it turns out there are
other such cryptic sequences of characters that represent otherwise
dangerous or untypable characters.
Like this, which could confuse the computer into thinking
it's at the beginning of a tag, and an ampersand,
which could similarly trick the computer into thinking that it's an entity which
it isn't.
But long story short, there are libraries, thankfully,
that handle much of this for you.
For our purposes, the takeaway is that now that you understand a bit of HTTP,
now that you understand a bit of HTML, CSS, and JavaScript, all of which
have their roles, you can use them ultimately
to start assembling your own web applications as you will
for problem set nine, stitching together all of those languages
and building full fledged web applications, mobile applications,
or anything more.
And for that, I think we are all set.
And the first one up here can have these cookies as well.
We'll see you next time for our very last CS50 lecture.
[MUSIC PLAYING]