[MUSIC PLAYING]
DAVID: All right.
So this is CS50 and this is week nine, and this is it
in terms of programming fundamentals.
Today, we come rather full circle with so many of the languages
that we've been looking at over the past several weeks.
And with HTML and CSS and JavaScript last week,
we're going to add back into the mix, Python and SQL.
And with that, do we have the ability to program for the web.
And even though this isn't the only user interface out
there, increasingly-- or people certainly using laptops and desktops
and a browser to access applications that people have written,
but it's also, increasingly, the way that mobile apps are written as well.
There are languages called Swift for iOS,
there are languages called Java for Android,
but coding applications in both of those language
means knowing twice as many language, building twice as many applications,
potentially.
So we're increasingly seeing, for better or for worse,
that the world is starting to really standardize,
at least for the next some number of years, on HTML, CSS, and JavaScript
coupled with other languages like Python and SQL on the so-called backend.
And so today, we'll tie all of those together
and give you the last of the tools in your toolkit with which
to tackle final projects to go off into the real world,
ultimately, and somehow solve problems with programming.
But we need an additional tool today, and we've sort of outgrown HTTP server.
This is just a program that comes on certain computers
that you can install for free, happens to be
written in a language called JavaScript, but it's a program
that we've been using to run a web server in VSCO.
But you can run it on your own Mac or PC or anywhere else.
But all this particular HTTP server does is
serve up static content like HTML files, CSS files,
JavaScript files, maybe images, maybe video files, but just static content.
It has no ability to really interact with the user beyond simple clicks.
You can create a web form and serve it visually using HTTP server,
but if the human types in input into a form and click Submit, unless you
submit it elsewhere to something like google.com like we did last time,
it's not actually going to go anywhere because this server can't actually
process the requests that are coming in.
So today, we're going to introduce another type of server that
comes with Python that allows us to not only serve web pages
but also process user input.
And recall that all that input is going to come ultimately from the URL,
or more deeply inside of those virtual envelopes.
So here's the canonical URL we talked about last week for random website
like www.example.com.
And I've highlighted the slash to connote the root of the web server,
like the default folder where, presumably, there's
a file called index.html or something else in there.
Otherwise, you might have a more explicit mention
of the actual file named file.html.
You can have folders, as you probably gleaned from the most recent problem
set.
You can have files in folders like this, and these
are all examples of what a programmer would typically call a path.
So it might not just be a single word, it
might have multiple slashes and multiple folders and some folders and files.
But this is just more generally known as a path.
But there's another term of our, that's essentially equivalent,
that we'll introduce today.
This is also synonymously called a route, which is maybe
a better generic description of what these things are because it turns out
they don't have to map to, that is, refer
to a specific folder or a specific file, you
can come up with your own routes in a website.
And just make sure that when the user visits that, you give them
a certain website page.
If they visit something else, you give them a different web page.
It doesn't have to map to a very specific file, as we'll soon see.
And if you want to get input from the user, just like Google does,
like q=cats, you can add a question mark at the end of this route.
The key, or the HTTP parameter name that you want to define for yourself,
and then equal sum value that, presumably, the human typed in.
If you have more of these, you can put an ampersand,
and then more key equals value pairs ampersand, repeat, repeat, repeat.
The catch, though, is that using the tools that we had last week alone,
we don't really have the ability to parse, that is, to analyze and extract
things like q equals cats.
You could have appended question mark q equals cats or anything else
to any of URLs in your home page for problem set eight,
but it doesn't actually do anything useful, necessarily,
unless you use some fancy JavaScript.
The server is not going to bother even looking in that for you.
But today, we're going to introduce using a bit of Python.
And in fact, we're going to use a web server implemented in Python, instead
of using HTTP server alone, to automatically, for you,
look for any key value pairs after the question mark
and then hand them to you in the form of a Python dictionary.
Recall that a dictionary in Python, a dict object, is just key value pairs.
That seems like a perfect fit for these kinds of parameters.
And you're not going to have to write that code yourself.
It's going to be handed to you by way of what's called a framework.
So this will be the second of two frameworks,
really, that we look at in the class.
And a framework is essentially a bunch of libraries
that someone else wrote and a set of conventions,
therefore, for doing things.
So those of you who really started dabbling with Bootstrap
this past week to make your home pages prettier and nicely laid out,
you are using a framework.
Why?
Well, you're using libraries, code that someone else wrote, like all the CSS,
maybe some of the JavaScript that the Bootstrap people wrote for you.
But it's also a framework in the sense that you have to go all in.
You have to use Bootstraps classes, and you
have to lay out your divs or your spans or your table tags
in a sort of Bootstrap-friendly way.
And it's not too onerous, but you're following conventions
that a bunch of humans standardized on.
So similarly, in the world of Python, is there another framework
we're going to start using today.
And whereas Bootstrap is used for CSS and JavaScript,
Flask is going to be used for Python.
And it just solves a lot of common problems for us.
It's going to make it easier for us to analyze the URLs
and get key value pairs, it's going to make it easier
for us to find files or images that the human wants
to see when visiting our website.
It's even going to make it easier to send emails automatically,
like when someone fills out a form.
You can dynamically, using code, send them an email as well.
So Flask, and with it some related libraries,
it's just going to make stuff like that easier for us.
And to do this, all we have to do is adhere to some pretty minimalist
requirements of this framework.
We're going to have to create a file for ourselves called app.py,
this is where our web app or application is going to live.
If we have any libraries that we want to use, the convention in the Python world
is to have a very simple text file called requirements.txt
where you list the names of those libraries,
top to bottom, in that text file, similar in spirit to the include
or the import statements that we saw in C and Python, respectively.
We're going to have a static folder or static directory, which
means any files you create that are not ever going to change,
like images, CSS files, JavaScript files,
they're going to go in this folder.
And then lastly, any HTML that you write,
web pages you want the human to see, are going
to go in a folder called templates.
So this is, again, evidence of what we mean by a framework.
Do you have to make a web app like this?
No, but if you're using this particular framework,
this is what people decided would be the human conventions.
If you've heard of other frameworks like Django or asp.net or bunches of others,
there are just different conventions out there for creating applications.
Flask is a very nice microframework in that that's it.
All you have to do is adhere to these pretty minimalist requirements
to get some code up and running.
All right, so let's go ahead and make a web app.
Let me go ahead and switch over to VS Code
here, and let me practice what I'm preaching here
by first creating app.py.
And let's go ahead and create an application that very simply,
maybe, says hello to the user.
So something that, initially, is not all that dynamic, pretty static, in fact.
But we'll build on that as we've always done.
So in app.py, what I'm going to do first is exactly the line of code
I had on the screen earlier.
From Flask, import Flask, with a capital F second and a lowercase f first.
And I'm also going to preemptively import
a couple of functions, render template, and request.
More on those in just a bit.
And then below that, I'm going to say, go ahead and do this.
Give me a web-- a variable called app that's
going to be the result of calling the Flask function and passing in it
this weird incantation here, name.
So we've seen this a few weeks back when we played around with Python
and we had that if main thing at the bottom of the screen.
For now, just know that __name__ refers to the name of the current file.
And so this line here, simple as it is, tells Python, hey, Python,
turn this file into a Flask application.
Flask is a function that just figures out, then, how to do the rest.
The last thing I'm going to do for this very simple web application is this.
I'm going to say that I'm going to have a function called
index that takes no arguments.
And whenever this function is called, I want
to return the results of rendering a template called index.html.
And that's it.
So let's assume there's a file somewhere, haven't created
it yet, called index.html.
But render template means render this file
that is printed to the user's screen, so to speak.
The last thing I'm going to do is I have to tell Flask
when to call this index function.
And so I'm going to tell it to define a route for, quote unquote, "slash."
And that's it.
So let's take a look at what I just created here.
This is slightly new syntax, and it's really the only weirdness
that we'll have today in Python.
This is what's known in Python is what's called a decorator.
A decorator is a special type of function
that modifies, essentially, another function.
For our purposes, just know that on line six this says,
hey Python, define a route for slash, the default
page on my website application.
The next two lines, seven and eight, say, hey Python,
define a function called index, takes no arguments.
And the only thing you should ever do is return render template of quote unquote
"index.html."
All right, so that's it.
So really, the next question, naturally, should be all right, well,
what is in index.html?
Well, let me go ahead and do that next.
Let me create a directory called templates, practicing, again,
what I preached earlier.
So I'm going to create a new empty directory called templates,
I'm going to go and CD into that directory
and then do code of index.html.
So here is going to be my index page.
And I'm going to do a very simple web page, doc type HTML.
I'm just going to borrow some stuff from last week.
HTML language equals English.
I'll close that tag.
I'll then do a head tag, I'll do a meta tag, the name of which is viewport.
This makes my site recall responsive.
That is, it just grows and shrink to fit the size of the device.
The initial scale for which is going to be one, and the width of which
is going to be device width.
So I'm typing this out, I have it printed here.
This is stuff I typically copy paste.
But then lastly, I'm going to add in my title, which will just
be hello for the name of this app.
And then the body--
whoops, Bobby.
The body of this tag will be--
there we go.
The body of this page, rather, will just be hello comma world.
So very uninteresting and really a regression to where we began last week.
But let's go now and experiment with these two files.
I'm not going to bother with a static folder
right now, because I don't have any other files that I want to serve up.
No images, no CSS, nothing like that.
And honestly, requirements.txt is going to be pretty simple.
I'm going to go requirements.txt and just say make sure the system has
access to the Flask library itself.
All right, but that's the only thing we can add in there for now.
All right, so now I have two files, app.py, and I have index.html.
But index.html thank you is inside of my templates directory
so how do I actually start a web server last week,
I would have said HTTP server.
But HTTP server is not a Python thing.
It has no idea about Flask or Python or anything I just wrote.
HTTP server will just spit out static files.
So if I ran HTTP server, and then I clicked on app.py,
I would literally see my Python code.
It would not get executed because HTTP server is just for static content.
But today, I'm going to run a different command called Flask run.
So this framework Flask that I actually preinstalled in advance,
so it wasn't strictly necessary that I create that requirements.txt file just
yet, comes with a program called Flask, takes command line arguments like
the word run, and when I do that, you'll see somewhat similar output to last
week whereby you'll see the name--
your URL for your unique preview of that.
You might see a pop up saying that your application is running
on TCP port, something or other.
By default, last week, we used port 8080.
Flask, just because, prefers port 5,000.
So that's fine too.
I'm going to go ahead and open up this URL now.
And once it authenticates and redirects me,
just to make sure I'm allowed to access that particular port, let me zoom in.
Voila, there's the extent of this application.
If I view source by right-clicking or control clicking,
there's my HTML that's been spit out.
So really, I've just reinvented the wheel from last week
because there's no dynamism now, nothing at all.
But what if I do this?
Let me close the source and let me zoom out.
So you can see my URL bar.
Let me zoom in now, and I have a very unique cryptic URL.
But the point is that it ends with nothing.
Or implicitly, it ends with slash.
This is just Chrome being a little helpful.
It doesn't bother showing you a slash, even though it's implicitly there.
But let me do something explicit like my name equals, quote unquote, "David."
So there's a key value pair that I've manually
typed into my URL bar and hit Enter.
Nothing happens, nothing changes.
It still says hello, world.
But the opportunity today is to now, dynamically, get at the input
from that URL and start displaying it to the user.
So let me go back over here to my terminal window and code.
Let me move that down to the bottom there.
And what if I want to say, huh, hello, name.
I ideally want to say something like--
I don't want to hard code David because then it's never
going to say hello to anyone else.
I want to put like a variable name here, like name should go here.
But it's not an HTML tag, so I need some kind of placeholder.
Well, here's what I can do.
If I go back to my Python code, I can now define a variable called name.
And I can ask Flask to go into the current request,
into its arguments, that is in the URL, as they're called,
and get whatever the value of the parameter called name is.
That puts that into a variable for me.
And then, in render template-- this is one of those functions
that can take more than one argument.
If it takes another argument, you can pass
in the name of any variable you want.
So if I want to pass in my name, I can literally say name equals name.
So this is the name of a variable I want to give to the template.
This is the actual variable that I want to get the value from.
And now lastly, in my index.html, the syntax as of today in Flask,
is to do two curly braces and then put the name of the variable
that you want to plug in.
So here's what we mean by a template.
A template is like a blueprint in the real world, where
it's plans to make something.
This is the plan to make a web page that has all of this code literally,
but there's this placeholder with two curly braces here and here
that says go ahead and plug in the value of the name variable right there.
So in this sense, it's similar in spirit to our f strings
or format strings in Python.
The syntax is a little different just because reasonable people
disagree, different people, different frameworks come up
with different conventions.
The convention in Flask, in their templates,
is to use two curly braces here.
The hope is that you, the programmer, will never
want to display two curly braces in your actual web page.
But even if you do, there's a workaround.
We can escape that.
So now let me go ahead and go back to my browser tab here.
Previously, even though I added name equals David
to the end of the URL with a question mark, it still said hello, world.
But now, hopefully, if I made these changes,
let me go ahead and open up my terminal window.
Let me restart Flask so it loads my changes by default.
Let me go back to my hello tab and click reload so it grabs the page anew
from the server.
And there we go, hello, David.
I can play around now and I can change the URL appear to, for instance,
Carter.
Zoom out, hit Enter.
And now we have something more dynamic.
So the new pieces here are, in Python, we have some code here
that allows us to access, programmatically, everything
that's after the question mark in the URL.
And the only thing we have to do that is call this function request.args.get.
You and I don't have to bother figuring out
where is the question mark, where is the equal sign, where
are the ampersands, potentially.
The framework, Flask, does all of that for us.
OK, any questions then on these principles thus far?
Yeah, in back.
AUDIENCE: Why do you say the question mark in the URL?
DAVID: Why do you need a question mark in the URL?
The short answer is just because that is where key value pairs must go.
If you're making a GET request from a browser to a server,
the convention, standardized by the HTTP protocol, is to put them in the URL
after the so-called route or path, then a question mark.
And it delineates what's part of the root or the path,
and what's part of the human input to the right.
Other questions?
Yeah.
AUDIENCE: Can you go over again why the left and right in the [INAUDIBLE]??
DAVID: Sure.
This is this annoying thing about Python.
When you pass in parameters, two functions that have names,
you typically say something equals something else.
So let me make a slight tweak here.
How about I say name of person here.
This allows me to invent my own variable for my template
and assign it the value of name.
I now, though, have to go into my index file and say name of person--
did I get that right?
Name of person, yeah.
So these two have to match.
And so this is just stupid because it's unnecessarily verbose.
So what typically people do is they just use the same name as the variable
itself, even though it looks admittedly stupid, but it has two different roles.
The thing to the left of the equal sign is
the name of the variable you plan to use in the template, the thing on the right
is the actual value you're assigning it.
And this is because its general purpose.
I could override this and I could say something like name always
equals Emma, no matter what that variable is.
And now if I go back to my browser and reload,
no matter what's in the URL, David or Carter, It's always--
OK, Emma broke the server.
What did I do?
Oh, I didn't change my template back.
There we go.
Let me change that back to be name, so that it's name there
and it's name here.
But I've hardcoded Emma's name, so now we're
only ever going to see Emma no matter whose name is in the URL.
That's all.
All right, so this is bad user interface.
If, in order to get a greeting for the day, you, the user,
have to manually change the URL, which none of us ever do.
This is not how web pages work.
What is the more normal mechanism for getting input from the user
and putting it in that URL automatically?
How did we do that last week?
With Google, if you recall.
AUDIENCE: We have the search bar and we [INAUDIBLE] you have
to make something in there [INAUDIBLE].
DAVID: OK, so we did make something in order to get the input from the user.
And specifically, what was the tag or the terminology we used last week?
AUDIENCE: [INAUDIBLE].
DAVID: Sorry, a little louder?
Oh, no.
But yeah.
AUDIENCE: Is it input?
DAVID: So the input tag, inside of the form tag.
So in short, forms, or of course, how the web works
and how we typically get input from the user,
whether it's a button or a text box or a dropdown menu or something else.
So let's go ahead and add that into the mix here.
So let's enhance this hello app to do a little something more by,
this time, just doing this.
Let me get rid of this name stuff and let me just
have a very simple index.html file that, by default, is going to simply ask
the user for some input as follows.
I'm going to go back into my index.html, and instead of printing out
the user's name, this is the page I'm going to use to actually get input
from the user.
So I'm going to create a form tag.
The method I'm going to use for now is going to be, quote unquote, "get."
Then, inside of that form, I'm going to have an input tag.
And I'm going to turn off autocomplete like we did last week.
I'm going to turn on auto focus, so it puts the cursor in the text box for me.
I'm going to give the name of this input the name, name.
Not to be too confusing, but I'm asking the human for their name.
So it makes sense that the name of the input should be, quote unquote, "name."
The placeholder I want the human to see in light gray text
will be Name with a capital N, just so it's a little grammatical.
And then type of this text fiel--
type of this input is going to be text.
Then I'm just going to give myself, like last week, a submit button.
And I don't care what it says, it's just going
to say the default submit terminology.
Let me go ahead, now, and open up my terminal window again.
Let me go to that same URL so that I can see-- whoops.
There we go.
So that was just cached from earlier.
Let me go back to that same URL, my GitHub preview.dev URL,
and here I have the form.
And now, I can type in anything I want.
The catch, though, is when I click Submit, where is it going to go?
Well, let's be explicit.
It does have a default value, but let me go into my index.html
and let me add, just like we did last week for it, Google.
Whereas previously, I said something like www.google.com/search, but today,
we're not going to rely on some third party.
I'm going to implement the so-called backend,
and I'm going to have the user submit this form to a second route,
not just slash, how about /greet.
I can make it up, whatever I want.
Greet feels like a nice operative word, so /greet is where the user will be
sent when they click Submit on this form.
All right, so let's go ahead now and go back to my browser tab.
Let me go ahead, actually, and let me reload Flask
here so that it reloads all of my changes.
Let me reload this tab so that I get the very latest HTML and, indeed,
quick safety check.
If I view page source, we indeed see that my browser
has downloaded the latest HTML.
So it definitely has changed.
Let's go ahead and type in David.
And when I click Submit here, what's going to happen?
Hypotheses.
What's going to happen visually, functionally, however you
want to interpret when I click Submit.
Yeah?
AUDIENCE: [INAUDIBLE] an empty page.
DAVID: OK, the user's going to go to an empty page.
Pretty good instinct, because--
no where else, if I mentioned /greet, it doesn't seem to exist.
How's the URL going to change, just to be clear?
What's going to appear, suddenly, in the URL?
Yeah?
AUDIENCE: 404?
DAVID: 404?
No, not in the URL.
Specifically in the URL, something's going to get added automatically
when I click.
AUDIENCE: The key value pair?
DAVID: The key value pair, right.
That's how forms work.
That's why our Google trick last week worked.
I sort of recreated a form on my own website.
And even though I didn't get around to implementing google.com itself,
I can still send the information to Google just relying on browsers,
standardizing--
to your question earlier, that whenever you submit a form,
it automatically ends up after a question mark in the URL
if you're using GET.
So this both of you are right, this is going to break.
And all three of you are right, in effect, 404 not found.
You can see it in the tab here.
That's the error that has come back.
But what's interesting, and most important, the URL did change.
And it went to /greet?name=david.
So I just, now, need to add some logic that actually
looks for that so-called route.
So let me go back to my app.py.
Let me define another route for, quote unquote, "slash greet."
And then, inside of-- under this, let me define another function.
I'll call it greet, but I could call it anything I want.
No arguments, for now, for this, and then
let me go ahead and do this in my app.py.
This time around, I do want to get the human's name.
So let me say requeste.args get quote unquote "name",
and let me store that in a variable called name.
Then let me return a template, and you know
what, I'm going to give myself a new template, greet.html.
Because this has a different purpose, it's not a form.
I want to say hello to the user in this HTML file,
and I want to pass, into it, the name that the human just typed in.
All right, so now if I go up and reload the page, what might happen now?
Other logical check here.
If I go ahead and hit reload or resubmit the form, what might happen now?
Any instincts?
Let me try, so let's try this.
Let's go ahead and reload the page.
Previously, it was not found.
Now it's worse, and this is the 500 error, internal server
error that I promised next week we will all encounter accidentally, ultimately.
But here we have an internal server error.
Because it's an internal error, this means something's wrong with your code.
So the route was actually found because it's not a 404 this time.
But if we go into VS Code here and we look at the console, the terminal
window, you'll see that--
this is actually a bit misleading.
Do I want to do this?
Let me reload this.
Let me reload here.
Oh, standby.
Come on.
There we go.
Come on.
OK, here we have this error here, and this
is where your terminal window is going to be helpful.
In your terminal window, by default, is typically
going to go helpful stuff like a log, L-O-G,
of what it is the server is seeing from the browser.
For instance, here's what the server just saw in purple.
Get /greet?name=david using HTTP version 1.0.
Here, though, is the status code that the server returned, 500.
Why, what's the error?
Well, here's where we get these annoying pretty cryptic Python messages
that help50 might ultimately help you with,
or here, we might just have a clue at the bottom.
And this is actually pretty clear, even though we've never
seen this error before.
What did I screw up here?
I just didn't create greet.html, right?
Template not found.
All right, so that must be the last piece of the puzzle.
And again, representative of how you might diagnose problems like these,
let me go into my terminal window.
After hitting Control C, which cancels or interrupts a process,
let me go into my templates directory.
If I type ls, I only have index.html.
So let's code up greet.html.
And in this file let's quickly do doc type.
Doc type HTML, open bracket HTML, language equals English.
Inside of this, I'll have the head tag, inside of here, I'll have the meta.
The name is viewport, the content of which is--
I always forget this to.
The content of which is initial scale equals one, width equals device width.
Quote unquote, title is still going to be,
I'll call this greet because this is my template.
And then here, in the body, I'm going to have hello comma name.
So I could have kept around the old version of this, but I just recreated,
essentially, my second template.
So index.html now is almost the same, but the title is different
and it has a form.
greet.html is almost the same, but it does not have a form.
It just has the hello comma name.
So let me now go ahead and rerun in the correct directory.
You have to run Flask wherever app.py is, not in your templates directory.
So let me do Flask run to get back to where I was.
Let me go into my other tab.
Cross my fingers this time that, when I go back to slash
and I get index.html's form, now I type in David and click Submit,
now we get hello, David.
And now we have a full-fledged web app that has two different routes,
slash and /greet, the latter of which takes input like this and then,
using a template, spits it out.
But something could go wrong, and let's see what happens here.
Suppose I don't type anything in.
Let me go here and just click Submit.
Now, I mean, it looks stupid.
So there's bunches of ways we could solve this.
I could require that the user have input on the previous page,
I could have some kind of error check for this.
But there's another mechanism I can use that I'll just show you.
It turns out this GET function, in the context of HTTP
and also in general with Python dictionaries,
you can actually supply a default value.
So if there is no name parameter or no value for a name parameter,
you can actually give it a default value like this.
So I'll say world, for instance.
Now, let me go back here.
Let me type in nothing again and click Submit.
And hopefully this time, I'll do-- oops, sorry.
Let me restart Flask to reload the template.
Let me go ahead and type nothing this time, clicking Submit.
And hopefully, we now--
Oh, interesting.
I should have faked this.
Suppose that the reason this--
Oh.
Suppose I just get rid of name altogether like this and hit Enter.
Now I see hello, world, and this is a subtlety
that I didn't intend to get into here.
When you have question mark name equals nothing,
you're passing in what's called-- whoops.
When you have greet question mark name equals something,
you actually are giving a value to name.
It is quote unquote with nothing in between.
That is different from having no value at all.
So allow me to just propose that the error here, we
would want to require this in a different way.
And probably the most robust way to do this
would be to go in here, in my HTML, and say that the name field is required.
Now, if I go back to my form after restarting Flask here,
and I go ahead and click reload on my form and type in nothing
and click Submit, now the browser is going to yell at me.
But just as a teaser for something we'll be
doing in the next problem set in terms of error checking,
you should never, ever, ever rely on client side safety checks like this.
Because we know, from last week, that a curious programmer can go to inspect,
and let me poke around the HTML here.
Let me go into the body, the form.
OK, you say required, I say not required.
You can just delete what's in the dom, in the browser,
and now I can go ahead and submit this form.
And it appears to be broken.
Not a big deal with a silly little greeting application like this.
But if you're trying to require that humans actually
provide input that is necessary for the correct operation of the site,
you don't want to trust that the HTML is not altered by some adversary.
All right, any questions, then, on this particular app
before we add another feature here?
Any questions here?
Yeah.
AUDIENCE: Do you guys [INAUDIBLE].
DAVID: Sorry, little louder.
In the index function--
AUDIENCE: Oh, sorry.
[INAUDIBLE]
DAVID: Sorry?
AUDIENCE: [INAUDIBLE]
DAVID: Would it be a problem if what?
AUDIENCE: You have to [INAUDIBLE].
DAVID: No.
I mean no, this is OK.
What you should really do is something we're going to do with another example
where I'm going to start error checking things.
So let me wave my hands at that and propose that we'll
solve this better in just a bit.
But it's not bad to do what I just did here, it's
only going to handle one of the scenarios that I was worried about.
Not all of them.
All right, so even though this is new to most of us
here, consider index.html, my first template, and consider greet.html,
my second template.
What might be arguably badly designed?
Even though this might be the first time you've
ever touched web programming like this.
What's bad or dumb about this design of these two templates alone?
And there's a reason, too, that I bored us by typing it out that second time.
Yeah?
AUDIENCE: [INAUDIBLE] you said, stuff like Notepad and [INAUDIBLE]..
DAVID: Yeah, there's so much repetition.
I mean, it was deliberately tedious that I was retyping everything.
The doc type, the HTML tag, the head tag, the title tag.
And little things did change along the way, like the title
and certainly, the content of the body.
But so much of this, I mean, almost all of the page
is a copy of itself in multiple files.
And God forbid we have a third template, a fourth template, a hundredth template
for a really big website.
This is going to get very tedious very quickly.
And suppose you want to change something in one place,
you're going to have to change it now in two, three, a hundred different places
instead.
So just like in programming more generally,
we have this ability to factor out commonalities.
So do you in the context of web programming,
and specifically templating, have the ability
to factor out all of those commonalities.
The syntax is going to be a little curious,
but it functionally is pretty straightforward.
Let me go ahead and do this.
Let me go ahead and copy the contents of index.html.
Let me go into my templates directory and code a file that,
by default, is called layout.html.
And let me go ahead, and per your answer, copy all of those
commonalities into this file now instead.
So here I have a file called layout.html.
I don't want to give every page the same title, maybe, but for now that's OK.
I'm going to call everything hello.
But in the body of the page, what I'm going to do here is just
have a placeholder for actual contents that do change.
So in this layout, I'm going to go ahead in here
and just put in the body of my page, how about this syntax?
And this is admittedly new.
Block body, and then percent sign close curly brace.
And then I'm going to do end block.
So a curious syntax here, but this is more template syntax.
The other template syntax we saw before was the two curly braces.
That's for just plugging in values.
There's this other syntax with Flask that allows you to, say, a single curly
brace, a percent sign, and then some functionality like this defining
a block.
And this one's a little weird because there's literally
nothing between the close curly and the open curly brace here.
But let's see what this can do for us.
Let me now go into my index.html, which is where I borrowed most of that code
from, and let me focus on what is minimally different.
The only thing that's really different in this page, title aside, is the form.
So let me go ahead and just cut that form out to my clipboard.
Let me change the first line of index.html
to say this file is going to extend layout.html,
and notice I'm using the curly braces again.
And this file is going to have its own body block
inside of which is just the HTML that I actually
want to make specific to this page.
And I'll keep my indentation nice and neat here.
And let's consider what I've done.
This is starting to look weird fast, and this is now
a mix of HTML with templating code.
Index.html, first line now says, hey, Flask, this file extends layout.html,
whatever that is.
This next line, three through 10, says, hey, Flask, here
is what I consider my body block to be.
Plug this into the layout placeholder.
Therefore, so if I now go back to layout.html, and layout.html,
it's almost all HTML by contrast.
But there is this placeholder, and if I want to put a default value,
I could say-- whoops.
If I want to put a default value, I could
put a default value there just in case some page does not have a body block.
But in general, that's not going to be relevant.
So this is just a placeholder, albeit a little verbose, that says
plug in the page-specific content right here.
So if I go now into greet.html, this one's even easier.
I'm going to cut this content and get rid of everything else.
Greet.html 2 is going to extend layouts, dot HTML extends plural,
and then I'm going to have my body block here simply be this one line of code.
And then I'm going to go ahead and end that block here.
These are not HTML tags, this is not HTML syntax.
Technically, the syntax we keep seeing with the curly braces,
and these now curly braces with percent signs, is an example of Jinja syntax,
J-I-N-J-A, which is a language, that some humans invented,
for this purpose of templating.
And the people who invented Flask decided,
we're not going to come up with our own syntax,
we're going to use these other people's syntax called Jinja syntax.
So again, there starts to be at this point in the course,
and really in computing, a lot of sharing, now, of ideas and sharing
of code.
So Flask is using this syntax, but other libraries and other languages
might also too.
All right, so now index.html is half HTML,
half templating code, Jinja syntax.
Greet.html is almost all Jinja syntax, no tags even,
but because they both extend layout.html,
now I think I've improved the design of this thing.
If I go back to app.py, none of this really needs to change.
I don't change my templates to mention layout.html,
that's already implicit in the fact that we have the extends keyword.
So now if I go ahead and open my terminal window,
go back to the same folder as app.py and do Flask run,
all right, my application is running on port 5000.
Let me now go back to the /route in my browser and hit Enter,
I have this form again.
And just as a little check, let me view the source of the page
that my browser is seeing.
And there's all of the code.
No mention of Jinja, no curly braces, no percent signs.
It's just HTML.
It's not quite pretty printed in the same way, but that's fine.
Because now, we're starting to dynamically generate websites.
And by that, I mean this isn't quite indented nicely or perfectly.
That's fine.
If it's indented in the source code version,
doesn't matter what the browser really sees.
Let me now go ahead and type in my name, click Submit.
I should see, yep, hello, David.
Let me go ahead and view the source of this page.
And we'll see almost the same thing with what's plugged in there.
So this is, now, web programming in the literal sense.
I did not hard code a page that says hello comma David, hello comma Carter,
hello comma Emma.
I hardcoded a page that has a template with a placeholder,
and now I'm using actual logic, some code in app.py,
to actually tell the server what to send to the browser.
All right, any questions, then, on where we're at here?
This is now a web application.
Simple though it is, it's no longer just a web site.
Yeah?
AUDIENCE: Is what we did just better for design or for memory [INAUDIBLE]??
DAVID: It better for design or for memory?
Both.
It's definitely better for design because, truly,
if we had a third page, fourth page, I would really
start just resorting to copy paste.
And as you saw with home page, often, in the head of your page,
you might want to include some CSS files like Bootstrap or something else.
You might want to have other information up there.
If you had to upgrade the version of Bootstrap or you change libraries,
so you want to change one of those lines,
you would literally have to go into three, four, a hundred different files
to make one simple change.
So that's bad design.
And in terms of memory, yes.
Theoretically, the server, because it knows there's this common layout,
it can theoretically do some optimizations underneath the hood.
Flask is probably doing that, but not in the mode we're using it.
We're using it in development mode, which
means it's typically reloading things each time.
Other questions on this application?
Anything at all?
All right, so let me ask a question, not just in terms of the code design.
What about the implications for privacy?
Why is this maybe not the best design for users, how I've implemented this?
I've used a web form, but--
Yeah?
AUDIENCE: For some reason, you wanted your name.
So these private people could just look at the URL.
DAVID: Yeah.
I mean, if you have a nosy sibling or roommate
and they have access to your laptop and they just
go trolling through your autocomplete or your history,
like, literally what you typed into a website is going to be visible.
Not a big deal if it's your name, but if it's your password, your credit
card or anything else that's mildly sensitive,
you probably don't want it ending up in the URL at all
even if you're in incognito mode or whatnot.
You just don't want to expose yourself or your users to that kind of risk.
So perhaps, we can do better than that.
And fortunately, this one is actually an easy change.
Let me go into my index.html where my form is.
And in my form, I can just change the method from GET to POST.
It's still going to send key value pairs to the server,
but it's not going to put them in the URL.
The upside of which is that we can assuage this privacy concern,
but I'm going to have to make one other change too.
Because now, if I go ahead and run Flask again after making that change,
and I now reload the form to make sure I have the latest version.
You should be in the habit of going to View, Developer,
View Source, or Developer Tools just to make sure
that what you're seeing in your browser is what you intend.
And yes, I do see what I wanted.
Method equals POST now.
Let me go ahead and type in David and click Submit.
Now I get a different error.
This one is HTTP 405, method not allowed.
Why is that?
Well, in my Flask application, I've only defined a couple of routes so far.
One of which is for slash, then that worked fine.
One of which is for /greet, and that used to work fine.
But apparently, what Flask is doing is it only supports GET by default.
So if I want to change this route to support different methods, I can say,
quote unquote "POST" inside of this parameter here.
So that now, I can actually support POST, not just GET.
And if I now restart Flask, so Flask run, Enter, and I go back to this URL.
Let me go back one screen to the form, reload
the page just to make sure I have the latest
even though nothing there has changed.
Type David and click Submit now, now I should see hello, world.
Notice that I'm at the greet route, but there's no mention of name
equals anything in the URL.
All right, so that's an interesting takeaway.
It's a simple change, but whereas GET puts things in the URL, POST does not.
But it still works so long as you tweak the backend
to look as a POST request, which means look deeper in the envelope.
It's not going to be as simple as looking at the URL itself.
Why shouldn't we just always use POST?
Why not use POST everywhere?
Any thoughts?
Right, because it's obnoxious to be putting any information in URLs
if you're leaving these little breadcrumbs in your history and people
can poke around and see what you've been doing.
Yeah, what do you think?
AUDIENCE: You're supposed to duplicate [INAUDIBLE]..
DAVID: Yeah.
I mean, if you get rid of GET requests and put nothing in the URL,
your history, your autocomplete, gets pretty less useful.
Because none of the information is there for storage, so you can't just
go through the menu and hit Enter.
You'd have to re-fill out the form.
And there's this other symptom that you can see here.
Let me zoom out and let me just reload this page.
Notice that you'll get this warning, and it'll
look different in Safari and Firefox and Edge and Chrome here, confirm form.
args
So your browser might remember what your inputs were and that's great,
but just while you're on the page.
And this is in contrast to GET, where the state is information.
Like, key value pairs is embedded in the URL itself.
And if you looked at an email I sent earlier today,
I deliberately linked to https://www.google.c
om/search?q=what+time+is+it.
This is, by definition, a GET request when you click on it.
Because it's going to grab the information, the key value pair,
from the URL, send it to Google server, and it's just going to work.
And the reason I sent this via email earlier was I
wanted people to very quickly be able to check what is the current time.
And so I can sort automate the process of creating a Google search for you,
but that you induce when you click that link.
If Google did not support GET, they only supported this, the best I could do
is send you all to this URL which, unfortunately,
has no useful information.
I would have had to add to my email, by the way, type in the words
what time is it.
So it's just bad for usability.
So there, too, we might have design when it comes to the low level code,
but also the design when it comes to the user experience, or UX,
as a computer scientist would call it.
Just in terms of what you want to optimize for, ultimately.
So GET and POST both have their roles.
It depends on what kind of functionality you want to provide
and what kind of sensitivity there might be around it.
All right, any questions, then, on this, our first web application?
Super simple, just gets someone's name and prints it back out.
But we now have all the plumbing with which
to create really most anything we want.
All right, let's go ahead and take a five minute break.
And when we come back, we'll add to this some first year intramural sports.
All right, so we are back.
And recall that the last thing we just changed
was the route to use POST instead of GET.
So gone is my name and any value in the URL.
But there was a subtle bug or change here that we didn't call out earlier.
I did type David into the form and I did click Submit,
and yet here it is saying hello comma world.
So that seems to be broken all of a sudden,
even though we added support for POST.
But something must be wrong.
Logically, it must be the case here.
Intuitively, that if I'm seeing hello, world, that's the default value
I gave the name variable.
It must be that it's not seeing a key called
name in request.args, which is this.
Gives you access to everything after the URL.
That's because there's this other thing we should know about,
which is not just request.args but request.form.
These are horribly named, but request.args is for GET requests,
request.form is for POST requests.
Otherwise, they're pretty much functionally the same.
But the onus is on you, the user or the programmer,
to make sure you're using the right one.
So I think if we want to get rid of the world
and actually see what I, the human, typed in,
I think I can just change request.args to request.form.
Still dot get, still quote unquote "name,"
and now, if I go ahead and rerun Flask in my terminal window,
go back to my browser, go back to-- and actually,
I won't even go back to the form.
I will literally just reload, Command R or Control R,
and what this warning is saying is it's going to submit
the same information to the website.
When I click Continue, now I should see hello comma David.
So again, you, too, are going to encounter, probably,
all these little subtleties.
But if you focus on, really, the first principles of last week,
like what it HTTP, how does it get request work,
how does a POST request work now, you should
have a lot of the mental building blocks with which
to solve problems like these.
And let me give you one other mental model, now, for what it is we're doing.
This framework called Flask is just an example of many different frameworks
that all implement the same paradigm, the same way of thinking
and the same way of programming applications.
And that's known as MVC, model view controller.
And here's a very simple diagram that represents the process that you
and I have been implementing thus far.
And actually, this is more than we've been implementing thus far.
In app.py is what a programmer would typically call the controller.
That's the code you're writing, this are called
business logic that makes all of the decisions, decides what to render,
what values to show, and so forth.
In layout.html, index.html, greet.html is the so-called view templates
that is the visualizations that the human actually
sees, the user interface.
Those things are dumb, they pretty much just say plop some values here.
All of the hard work is done in app.py.
So controller, AKA app.py, is where your Python code generally is.
And in your view is where your HTML and your Jinja code, your Jinja templating,
the curly braces, the curly braces with percent signs, usually is.
We haven't added an M to MVC yet model, that's
going to refer to things like CSV files or databases.
The model, where do you keep actual data, typically long term.
So we'll come back to that, but this picture,
where you have one of these-- each of these components communicating with one
another is representative of how a lot of frameworks work.
What we're teaching today, this week, is not really specific to Python.
It's not really specific to Flask, even though we're using Flask.
It really is a very common paradigm that you
could implement in Java, C sharp, or bunches of other languages as well.
All right, so let's now pivot back to VS Code here.
Let me stop running Flask, and let me go ahead
and create a new folder altogether after closing these files here.
And let me go ahead and create a folder called FroshIMS,
representing freshman intramural sports or first year intramural sports
that I can now CD into.
And now I'm going to code an app.py.
And in anticipation, I'm going to create another templates directory.
This one in the FroshIMS folder.
And then in my templates directory, I'm going to create a layout.html.
and I'm just going to get myself started here.
FroshIMS will go here.
I'm just copying my layout from earlier because most
of my interesting work, this time, is now going to be, initially, in app.py.
So what is it we're creating?
So literally, the very first thing I wrote as a web application
20 years ago, was a site that literally looked like this.
So I was like a sophomore or junior at the time.
I'd taken CS50 and a follow-on class only.
I had no idea how to do web programming.
Neither of those two courses taught web programming back in the day.
So I taught myself, at the time, a language called Perl.
And I learned a little something about CSV files, and I sort of read enough--
can't even say googled enough, because Google didn't come out
for a couple of years later.
Read enough online to figure out how to make a web application so that students
on campus, first years, could actually register
via a website for intramural sports.
Back in my day, you would literally fill out a piece of paper
and then walk it across the yard to Wigglesworth Hall, one of the dorms,
slide it under the dorm of the Proctor or RA,
and thus you were registered for sports so.
1996, 1997.
We could do better by then.
There was an internet, just wasn't really being
used much on campus or more generally.
So background images that repeat infinitely
was in vogue, apparently, at the time.
All of this was like images that I had to hand make
because we did not have the features that JavaScript and CSS nowadays have.
So it was really just HTML, and it was really just controller code written,
not in Python, but in Perl.
And it was really just the same building blocks
that we hear already today now have.
So we'll get rid of all of the imagery and focus more
on the functionality and the aesthetics, but let's see
if we can whip up a web application via which someone could
register for one such intramural sport.
So in app.py, me go ahead and import some familiar things now.
From Flask, let's import capital Flask, which
is that function we need to kick everything kick start everything.
Render templates, so we have the ability to render, that is print out,
those templates, and request so that we have the ability
to get at input from the human.
Let me go ahead and create the application itself
using this magical incantation here.
And then let's go ahead and define a route for slash for instance first.
I'm going to define a function called index.
But just to be clear, this function could be anything.
Foo, bar, baz, anything else.
But I tend to name them in a manner that's
consistent with what the route is called.
But you could call it anything you want, it's
just the function that will get called for this particular route.
Now, let me go ahead here and just get things started.
Return, render template of index.html.
Just keep it simple, nothing more.
So there's nothing really FroshIM specific about this here,
I just want to make sure I'm doing everything correctly.
Meanwhile, I've got my layout.
OK, let me go ahead, and in my templates directory, code a file
called index.html.
And let's just do extends layout.html at the top
just so that we get benefit from that template.
And down here, I'm just going to say to do.
Just so that I have something going on visually to make sure
I've not screwed up yet.
In my FroshIMS directory, let me do Flask run.
Let me now go back to my previous URL, which used to be my hello example.
But now, I'm serving up the FroshIM site.
Oh, and I'm seeing nothing.
That's because I screwed up accidentally.
What did I do wrong in index.html?
What am I doing wrong?
This file extends layout.html, but--
AUDIENCE: You left out the block tag?
DAVID: Yeah.
I forgot to tell Flask what to plug into that layout.
So I just need to say block body, and then in here, I can just say to do
or whatever I want to eventually get around to.
Then end the block.
Let me end this tag here.
OK, so now it looks ugly, more cryptic.
But this is, again, the essence of doing templating.
Let me now restart Flask up here, let me go back to the page.
Let me reload.
Crossing my fingers this time, and there we go.
To do.
So it's not the application I want, but at least I
know I have some of the plumbing there by default.
All right, so if I want the user to be able to register
for one of these sports, let's enhance, now,
index.html to actually have a form that's
maybe got a dropdown menu for all of the sports for which you can register.
So let me go into this template here.
And instead of to do, let's go ahead and give myself,
how about an H1 tag that just says register so the user knows what it is
they're looking at.
How about a form tag that's going to use POST,
just because it's not really necessary to put this kind of information
in the URL.
The action for that, how about we plan to create
a register route so that we're sending information from to a register route.
So we'll have to come back to that.
In here, let me go ahead and create, how about an input with autocomplete
equals off, auto focus on.
How about a name equals name, because I'm
going to ask the student for their name using placeholder text of quote
unquote "name."
And the type of this box will be text.
So this is pretty much identical to before.
But if you've not seen this yet, let's create a select menu,
a so-called dropdown menu in HTML.
And maybe the first option I want to be in there
is going to be, oh, how about the current three
sports for the fall, which are basketball, and another option
is going to be soccer, and a third option is
going to be ultimate frisbee for first year intramurals right now.
So I've got those three options.
I've got my form.
I haven't implemented my route yet, but this feels like a good time
to go back now and check if my form has reloaded.
So let me go ahead and stop and start Flask.
You'll see there's ways to automate the process of restarting
the server that we'll do for you for problem set nine,
so you don't have to keep stopping Flask.
Let me reload my index route and OK, it's not that pretty.
It's not though, maybe--
nor was this.
But it now has at least some functionality
where I can type in my name and then type in the sport.
Now, I might be biasing people toward basketball.
Like UX wise, user experience wise, it's obnoxious to precheck
basketball but not the others.
So there's some little tweaks we can make there.
Let me go back into index.html.
Let me create an empty option up here that, technically, this option is not
going to have the name of any sports.
But it's just going to have a word I want the human to see,
so I'm actually going to disable this option and make it selected by default.
But I'm going to say sport up here.
And there's different ways to do this, this is just one way of creating,
essentially, a--
whoops, option.
Yep, that looks right.
Creating a placeholder sports so that the user
sees something in the dropdown.
Let me go ahead and restart Flask, reload the page,
and now it's just going to be marginally better.
Now you see sport that's checked by default,
but you have to check one of these other ones ultimately.
All right, so that's pretty good.
So let me now type in David.
I'll register for ultimate frisbee.
OK, I definitely forgot something.
Submit button.
So let's add that.
All right, so input type equals submit.
All right, let's put that in.
Restart Flask, reload.
Getting better.
Submit could be a little prettier.
Recall that we can change some of these HTTP-- these HTML attributes.
The value of this button should be register, maybe,
just to make things a little prettier.
Let me now reload the page and register.
All right, so now we really have the beginnings of the user interface
that I created some years ago to let people actually register for the sport.
So let's go, now, and create maybe the other route that we might need.
Let me go into app.py.
And in here, if we want to allow the user to register,
let's do a little bit of error checking which I promised we'd come back to.
What could the user do wrong?
Because assume that they will.
One, they might not type their name.
Two, they might not choose a sport.
So they might just submit an empty form.
So that's two things we could check for, just
so that we're not scoring bogus entries in our database, ultimately.
So let's create another route called greet, /greet.
And then in this route, let's create a function called greet
but can be called anything we want.
And then let's go ahead, and in the greet function, let's go ahead
and validate the submission.
So a little comment to myself here.
How about if there is not a request.form GET name value,
so that is if that function returns nothing,
like quote unquote, or the special word none in Python.
Or request.form.get"sport" not in quote unquote, what were they?
Basketball, the other one was soccer, and the last was ultimate frisbee.
Getting a little long, but notice what I'm-- the question I'm asking.
If the user did not give us a name, that is,
if this function returns the equivalent of false,
which is, quote unquote, or literally none if there's no such parameter.
Or if the sport the user provided is not some value in basketball, soccer,
or ultimate frisbee, which I've defined as a Python list, then let's go ahead
and just yell at the user in some way.
Let's return render template of failure.html.
And that's just going to be some error message inside of that file.
Otherwise, if they get this far, let's go ahead
and confirm registration by just returning-- whoops,
returning render template quote unquote "success" dot HTML.
All right, so a couple quick things to do.
Let me first go in and in my templates directory,
let's create this failure.html file.
And this is just meant to be a message to the user
that they fail to provide the information correctly.
So let me go ahead and in failure.html.
not repeat my past mistake.
So let me extend layout.html and in the block body, you are not registered.
I'll just yell at them like that so that they know something went wrong.
And then let me create one other file called success.html, that
similarly is mostly just Jinja syntax.
And I'm just going to say for now, even though they're not
technically registered in any database, you are registered.
That's what we mean by success.
All right, so let me go ahead, and back in my FroshIMS,
directory run Flask run.
Let me go back to the form and reload.
Should look the same.
All right, so now let me not cooperate and just
immediately click Register impatiently.
OK, what did I do wrong.
Register-- oh, I'm confusing our two examples.
All right, I spotted the error.
What did I do wrong?
Unintentional.
There's where I am, what did I actually invent over here?
Where did I screw up?
Anyone?
AUDIENCE: Register, not greet.
DAVID: Thank you.
So register, not greet.
I had last example on my mind, so the route should be register.
Ironically, the function could be greet, because that actually doesn't matter.
But to keep ourselves sane, let's use the one and the same words there.
Let me go ahead now and start Flask as intended.
Let me reload the form just to make sure all is working.
Now, let me not cooperate and be a bad user, clicking register--
oh my God.
OK, other unintended mistake.
But this one we've seen before.
Notice that by default, route only support GET.
So if I want to specifically support POST,
I have to pass in, by a methods parameter, a list of allowed route
methods that could be GET comma POST, but if I
don't have no need for a GET in this context, I can just do POST.
All right, now let's do this one last time.
Reload the form to make sure everything's OK, click Register,
and you are not registered.
So it's catching that.
All right, let me go ahead and at least give them my name.
Register.
You are not registered.
Fine, I'm going to go ahead and be David with ultimate frisbee register.
Huh.
OK.
What should I-- what did I mean to do here?
All right, so let's figure this out.
How to debug something like this, which is my third and final unintended,
unforced error?
How can we go about troubleshooting this?
Turn this into the teachable moment.
All right, well first, some safety checks.
What did I actually submit?
Let me go ahead and view page source, a good rule of thumb.
Look at the HTML that you actually sent to the user.
So here, I have an input with a name name.
So that's what I intended, that looks OK.
Ah, I see it already, even though you, if you've never
used a select menu, you might not know what, apparently,
is missing from here that I did have for my text input.
Just intuitively, logically.
What's going through my head, embarrassingly,
is, all right, if my form thinks that it's missing a name or a sport,
how did I create a situation in which name is blank or sport is blank?
Well, name, I don't think it's going to be blank
because I explicitly gave this text field a name name
and that did work last time.
I've now given a second input in the form of the select menu.
But what seems to be missing here that I'm assuming exists here?
It's just a dumb mistake I made.
What might be missing here?
If request.form gives you all of the inputs that the user might
have typed in, let me go into my actual code
here in my form and name equals sport.
I just didn't give a name to that input.
So it exists, and the browser doesn't care.
It's still going to display the form to you,
it just hasn't given it a unique name to actually transmit to the server.
So now, if I'm not going to put my foot in my mouth,
I think that's what I did wrong.
And again, my process for figuring that out
was looking at my code, thinking through logically,
is this right, is this right?
No, I was missing the name there.
So let's run Flask, let's reload the form
just to make sure it's all defaults again, type in my name and type
in ultimate frisbee, crossing my fingers extra hard this time.
And there.
You are registered.
So I can emphasize--
I did not intend to screw up in that way,
but that's exactly the right kind of thought process
to diagnose issues like this.
Go back to the basics, go back to what HTTP and what HTML forms are all about,
and just rule things in and out.
There's only a finite number of ways I could have screwed that up.
Yeah?
AUDIENCE: Are you [INAUDIBLE].
DAVID: Excuse-- say a little louder?
AUDIENCE: I don't understand why name equals sport [INAUDIBLE]..
DAVID: Why did name equal sport address the problem?
Well, let's first go back to the HTML.
Previously, it was just the reality that I had this user input dropdown menu,
but I never gave it a name.
But names, or more generally, key value pairs,
is how information is sent from a form to the server.
So if there's no name, there's no key to send, even if the human types a value.
It would be like nothing equals ultimate frisbee, and that just doesn't work.
The browser is just not going to send it.
However, in app.py, I was naively assuming that in my requests form,
there would be a name called quote unquote "sport."
It could have been anything, but I was assuming it was sport.
But I never told the form that.
And if I really wanted to dig in, we could do a little something more.
Let me go back to the way it was a moment ago.
Let me get rid of the name of the sport dropdown menu.
Let me rerun Flask down here and reload the form itself
after it finishes being served.
And now, let me do this.
View Developer Tools, and then let me watch the Network tab, which recall,
we played around with a little bit last week.
And we also played around with Curl, which let us see the HTTP requests.
Here's another-- here's what I would have
done if I still wasn't seeing the error and was really embarrassed on stage.
I would have typed in my name as before, I would have chosen ultimate frisbee.
I would have clicked register.
And now, I would have looked at the HTTP request.
And I would click on Register here.
And just like we did last week, I would go down to the request down here.
And there's a whole lot of stuff that we can typically ignore.
But here, let me zoom in, way at the bottom,
what Chrome's developer tools are doing for me,
it's showing me all of the form data that was submitted.
So this really would have been my telltale clue.
I'm just not sending the sport, even if the human typed it in.
And logically, because I've done this before,
that must mean I didn't give the thing a name.
But another good tool.
Like good programmers, web developers are using these kinds of tools
all the time.
They're not writing bug-free code.
That's not the point to get to.
The point to get to is being a good diagnostician,
I would say, in these cases.
OK, other questions on this?
Yeah.
AUDIENCE: What if you want to edit one HTML in CSS, [INAUDIBLE]..
DAVID: I'm sorry, a little bit louder?
AUDIENCE: If you want to edit in CSS or anything,
in HTML, once you have to fix the template, how do you that?
DAVID: So how would you edit CSS if you have these templates?
That process we'll actually see before long.
It's almost going to be the exact same.
Just to give you a teaser for this, and you'll do this in the problem set,
but we'll give you some distribution code to automate this process.
You can absolutely still do something like this.
Link href equals quote unquote "styles" dot
CSS rel equals style sheet, that's one of the techniques we showed last week.
The only difference today, using Flask, is that all of your static files,
by convention, should go in your static folder.
So the change you would make in your layout
would be to say that styles dot CSS is in your static folder.
And then, if I go into my FroshIMS directory,
I can create a static folder.
I can CD into it, nothing's there by default.
But if I now code a file called styles.css,
I could now do something like this body.
And in here, I could say background color, say FF0000 to make it red.
Let me go ahead now and restart Flask in the FroshIMS directory.
Cross my fingers because I'm doing this on the fly.
Go back to my form and reload.
Voila, now we've tied together last week's stuff as well.
If I answered the right question?
AUDIENCE: [INAUDIBLE] change one page and not the other.
DAVID: If you want to change one page and not the other in terms of CSS?
AUDIENCE: Yes.
DAVID: That depends.
In that case, you might want to have different CSS files for each page
if they're different.
You could use different classes in one template than you did in the other.
There's different ways to do that.
You could even have a placeholder in your layout
that allows you to plug in the URL of a specific style
sheet in your individual files.
But that starts to get more complicated quickly.
So in short, you can absolutely do it.
But typically, I would say most websites try not
to use different style Sheets per page.
They reuse the styles as much as they can.
All right, let me go ahead and revert this real quick.
And let's start to add a little bit more functionality here.
I'm going to go ahead and just remove the static folder just so as
to not complicate things just yet.
And let's go ahead and just play around with a different user interface
mechanism.
In my form here, the dropdown menu is perfectly fine.
Nothing wrong with it.
But suppose that I wanted to change it to checkboxes instead.
Maybe I want students to be able to register for multiple sports instead.
Well, it might make sense to clean this up in a couple of ways.
And let's do this.
Before we even get into the checkboxes, there's one subtle bad design here.
Notice that I've hardcoded basketball, soccer, and ultimate frisbee here.
And if you recall, in app.py, I also enumerated all three of those here.
And any time you see copy paste or the equivalent thereof,
feels like we could do better.
So what if I instead do this.
What if I instead give myself a global variable of Sports,
I'll capitalize the word just to connote that it's
meant to be constant even though Python does not have constants, per se.
The first sport will be basketball.
The second will be soccer.
The third will be ultimate frisbee.
Now I have one convenient place to store all of my sports
if it changes next semester or next year or whatnot.
But notice what I could do to.
I could now do something like this.
Let me pass into my index template a variable
called sports that's equal to that global variable sports.
Let me go into my index now, and this is really, now,
going to hint at the power of templating and Jinja, in this case here.
Let me go ahead and get rid of all three of these hard coded options
and let me show you some slightly different syntax for sport, in sports.
Then end for.
We've not seen this end for syntax.
There's like end block syntax, but it's as simple as that.
So you have a start and an end to your block without indentation mattering.
Watch what I can do here.
Option curly brace sport close curly brace.
Let me save that.
Let me go back into my terminal window, do Flask run.
And if I didn't mess up here, let me go back to this.
The red's going to go away because I deleted my CSS.
And now I still have a sport dropdown and all of those sports
are still there.
I can make one more improvement now.
I don't need to mention these same sports manually in app.py.
I can now just say if the user's inputed sport is not
in my global variable, sports, and ask the same question.
And this is really handy because if there's
another sport, for instance, that gets added, like say football,
all I have to do is change my global variable.
And if I reload the form now and look in the dropdown, boom,
now I have support for a fourth sport.
And I can keep adding and adding there.
So here's where templating starts to get really powerful in that
now, in this template, I'm using Jinja's for loop syntax, which
is almost identical to Python here, except you
need the curly brace and the percent sign and you need the weird ending
and for.
But it's the same idea as in Python.
Iterating over something with a for loop lets you generate more and more HTML.
And this is like every website out there.
For instance, Gmail.
When you visit your inbox and you see all of this big table of emails,
Google has not hardcoded your emails manually.
They have grabbed them from a database.
They have some kind of for loop like this,
and are just outputting table row after table row or div after div dynamically.
All right, so now, let's go ahead and change this, maybe,
to, oh, how about little checkboxes or radio buttons.
So let me go ahead and do this.
Instead of a select menu, I'm going to go ahead and do something like this.
For each of these sports let me go ahead and output, not an option,
but let me go ahead and output an input tag,
the name for which is quote unquote "sport," the type of which
is checkbox, the value of which is going to be the current "sport,"
quote unquote, and then afterward I need to redundantly, seemingly,
output the sport.
So you see a word next to the checkbox.
And we'll look at the result of this in just a moment.
So it's actually a little simpler than a select menu, a dropdown menu,
because now watch what happens if I reload my form.
Different user interface, and it's not as pretty,
but it's going to allow users to sign up for multiple sports at once now,
it would seem.
Now I can click on basketball and football and soccer
or some other combination thereof.
If I view the page's source, this is, again, the power of templating.
I didn't have to type out four inputs, I got them now automatically.
And these things all have the same name, but that's OK.
It turns out with Flask, if it sees multiple values for the same name,
it's going to hand them back to you as a list if you use the right function.
All right, but suppose we don't want users registering for multiple sports.
Maybe capacity is an issue.
Let me go ahead and change this checkbox to radio button, which
a radio button is mutually exclusive.
So you can only sign up for one.
So now, once I reload the page, there we go.
It now looks like this.
And because I've given each of these inputs the same name, quote unquote,
"sport," that's what makes them mutually exclusive.
The browser knows all four of these things are types of sports,
therefore I'm only going to let you select one of these things.
And that's simply because they all have the same name.
Again, if I view page source, notice all of them, name equal sport,
name equals sport, name equals sport, but what differs is the value
that each one is going to have.
All right, any questions, then, on this approach?
All right.
Well, let me go ahead and open a version of this
that I made in advance that's going to now start saving the information.
So thus far, we're not quite at the point
of where this website was, which actually allowed the proctors to see,
like in a database, everyone who had registered for sports.
Now, we're literally telling students you are registered
or you are not registered, but we're literally
doing nothing with this information.
So how might we go about implementing this?
Well, let me go ahead and close these tabs,
and let me go into what I call version three of this in the code for today.
And let me go into my source nine directory, FroshIMS3,
and let me go ahead and open up app.py.
So this is a premade version.
I've gotten rid of football, in this case.
But I've added one thing at the very top.
What's, in English, does this represent on line seven?
What would you describe what that thing is?
What are we looking at?
What do you think?
AUDIENCE: It's an empty dictionary.
DAVID: Yeah, it's an empty dictionary, right?
Registrants is apparently a variable on the left.
It's being assigned an empty dictionary on the right.
And a dictionary, again, is just key value pairs.
Here, again, is where dictionaries are just such a useful data structure.
Why?
Because this is going to allow me to remember that David registered
for ultimate frisbee, Carter registered for soccer,
Emma registered for something else.
You can associate keys with values, names with sports,
assuming a model where you can only register for one sport for now.
And so let's see what the logic is that handles this.
Here in my register route in the code I've premade,
notice that I'm validating the user's name.
Slightly differently from before but same idea.
I'm using request.form.get to get the human's name.
If not name, so if the human did not type a name,
I'm going to output error.html.
But notice I've started to make the user interface more expressive.
I'm telling the user, apparently, with a message what they did wrong.
Well how?
I'm apparently passing to my error template,
instead of just failure.html, a specific message.
So let's go down this rabbit hole.
Let me actually go into templates/error.hml, and sure enough,
here's a new file I created here, that adorably is apparently going to have
a grumpy cat as part of the error message, but notice what I've done.
In my block body I've got an H1 tag that just says error, big and bold.
I then have a paragraph tag that plugs in whatever
the error message is that the controller, app.py, is passing in.
And then just for fun, I have a picture of a grumpy cat connoting
that there was, in fact, an error.
Let's keep looking.
How do I validate sport?
I do similarly request.form.get of sport,
and I store it in a variable called sport.
If there's no such sport, that is the human did not check any of the boxes,
then I'm going to render error.html two, but I'm
going to give a different message, missing sport.
Else, if the sport they did type in is not in my sports global variable,
I'm going to render error.html, but complain differently,
you gave me an invalid sport somehow.
As if a hacker went into the HTML of the page,
changed it to add their own sport like volleyball.
Even though it's not offered, they submitted volleyball.
But that's OK, I'm rejecting it, even though they might have maliciously
tried to send it to me by changing the dom locally.
And then really, the magic is just this.
I remember that this person has registered
by indexing into the registrant dictionary
using the name the human typed in as the key and assigning it a value of sport.
Why is this useful?
Well, I added one final route here.
I have a /registrants route with a registrants function that renders
a template called registrants.html.
But it takes as input that global variable just like before.
So let's go down this rabbit hole let me go into templates registrants dot HTML.
Here's this template.
It looks a little crazy big, but it extends the layout.
Here comes the body.
I've got an H1 tag that says registrants, big and bold.
Then I've got a table that we saw last week.
This has a table head that just says name sport for two columns.
Then it has a table body where in, using this for loop in Jinja syntax,
I'm saying, for each name in the registrants variable,
output a table row, start tag, and end tag, inside of which,
two table datas, two cells, table data for name,
table data for registrants bracket name.
So it's very similar to Python syntax.
It essentially is Python syntax, albeit with these curly braces and the percent
sign.
So the net effect here is what?
Let me open up my terminal window, run Flask run.
Let me now go into the form that I premade here.
So gone is football.
Let me go ahead and type in David.
Let me choose, oh, no sport.
Register.
Error, missing sport.
And there is the grumpy cat.
So missing sport, though, specifically was outputed.
All right, fine.
Let me go ahead and say no name.
But I'll choose basketball.
Register.
Missing name.
All right, and let me maliciously, now, do this.
Now I'm hacking.
Let me go into this.
I'll type my name, sure, but let me go into the body tag down here.
Let me maliciously go down in ultimate frisbee, heck with that, let's
volleyball.
Change that and change this to volleyball.
Enter.
So now, I can register for any sport I want to create.
Let me click register, but invalid sports.
So again, that speaks to the power and the need
for checking things on backend and not trusting users.
It is that easy to hack websites otherwise if you're not validating data
server side.
All right, finally, let's just do this for real.
David is going to register for ultimate frisbee.
Clicking register.
And now, the output is not very pretty, but notice
I'm at the registrants route.
And if I zoom out, I have an HTML table.
Two columns, name and sport, David and ultimate frisbee.
Let me go back to the form, letting me pretend Carter walked up to my laptop
and registered for basketball.
Register.
Now we see two rows in this table, David, ultimate frisbee, Carter,
basketball.
And if we do this one more time, maybe Emma
comes along and registers for soccer register.
All of this information is being stored in this dictionary, now.
All right, so that's great.
Now we have a database, albeit in the form of a Python dictionary.
But why is this, maybe, not the best implementation?
Why is it not great?
Yeah.
AUDIENCE: You are storing [INAUDIBLE].
DAVID: Yeah.
So we're only storing this dictionary in the computer's memory,
and that's great until I hit Control C and kill Flask,
stopping the web server.
Or the server reboots, or maybe I close my laptop or whatever.
If the server stops running, memory is going to be lost.
RAM is volatile.
It's thrown away when you lose power or stop the program.
So maybe this isn't the best approach.
Maybe it would be better to use a CSV file.
And in fact, some 20 years ago, that's literally what I did.
I stored everything in a CSV file.
But let's skip that step, because we already saw last week,
or a couple of weeks ago now, how we can use SQLite.
Let's see if we can't marry in some SQL here
to store an actual database for the program.
Let me go back here and let me open up, say,
version four of this, which is almost the same but it
adds a bit more functionality.
Let me close these tabs and let me open up app.py now in version four.
So notice it's almost the same, but at the top,
I'm creating a database connection to a database called FroshIMS.db.
So that's a database I created in advance.
So let's go down that rabbit hole.
What does it look like?
Let me make my terminal window bigger.
Let me run SQLite 3 of FroshIMS.db.
OK, I'm in.
Let's do .schema.
and let's just infer what I designed this to be.
I have a table called registrants, which has one, two, three columns.
An ID column that's an integer, a name column that's text but cannot be null,
and a sport column that's also text, cannot be null,
and the primary key is just ID.
So that I have a unique ID for every registration.
Let's see if there's anyone in there yet.
Select star from registrants.
OK, there's no one in there.
No one is yet registered for sports.
So let's go back to the code and continue on.
In my code now, I've got the same global variable
for validation and generation of my HTML.
Looks like my index route is the same.
It's dynamically generating the menu of sports.
Interestingly, we'll come back to this.
There's a deregister route that's going to allow someone
to deregister themselves if they want to exit the sport
or undo their registration.
But this is the juicy part.
Here's my new and improved register route.
Still works on POST, so some mild privacy there.
I'm validating the submission as follows.
I'm getting the user's inputted name, the user's inputted sport,
and if it is not a name or the sport is not in sports,
I'm going to render failure.html.
So I kept it simple.
There's no cat in this version.
It just says failure.
Otherwise, recall how we co-mingled SQL and Python before.
We're using CS50's SQL library, but that just
makes it a little easier to execute SQL queries and we're executing this.
Insert into registrants name comma sport.
What two values, the name and the sport, that came from that HTML form.
And then lastly, and this is a new function that we're calling out
explicitly now, Flask also gives you access
to a redirect function, which is how safetyschool.org, Harvardsucks.org,
and all these other sites we played around with last week
we're all implemented redirecting the user from one place to another.
This Flask function redirect comes from my just
having imported it at the very top of this file.
It handles the HTTP 301 or 302 or 307 code, whatever the appropriate one is.
It does that for me.
All right, so that's it for registering via this route.
Let's look at what the registrant's route is.
Here, we have a new route for /registrants.
And instead of just iterating over a dictionary like before,
we're getting back, let's see, db.execute of select star
from registrants.
So that's literally the programmatic version of what I just did manually.
That gives me back a list of dictionaries,
each of which represents one row in the table.
Then, I'm going to render register and start HTML,
passing in literally that list of dictionaries
just like using CS50's library in the past.
So let's go and look at these-- that form.
If I go into templates and open up registrants.html,
oh, OK, it's just a table like before.
And actually, let me change this syntactically for consistency.
We have a Jinja for loop that iterates over each registrant
and for each of them, outputs a table row.
Oh, but this is interesting.
Instead of just having two columns with the person's name and sport,
notice that I'm also outputting a full-fledged form.
All right, this is starting to get juicy.
So let's actually go back to my terminal window,
run Flask, and actually see what this example looks like now.
Let me reload the page.
All right.
In the home page, it looks exactly the same.
But let me now register for something.
David for ultimate frisbee, register.
Oh, damn it.
Let's try this again.
David registering for ultimate frisbee, register.
OK.
So good thing I have deregister.
So this is what it should now look like.
I have a page at the route called /registrants that has a table with two
columns, name and sport, David, ultimate frisbee.
But oh, wait, a third column.
Why?
Because if I view the page source, notice that it's not the prettiest UI.
For every row in this table, I'm also going to be outputting a form just
to deregister that user.
But before we see how that works, let me go ahead and register Carter,
for instance.
So Carter will give you basketball.
Again, register.
The table grows.
Now, let me go back and let's register Emma for soccer.
And the table should grow.
Before we look at that HTML, let's go back to my terminal window.
Let's go into SQLite FroshIMS.
Let me go into FroshIMS, and let me open up with SQLite 3 FroshIMS.db.
And now do select star from registrants.
And whereas, previously, when I executed this there were zero people, now
there's indeed three.
So now we see exactly what's going on underneath the hood.
So let's look at this form now-- this page now.
If I want to unregister, deregister one of these people specifically,
how do we do this?
Clicking one of those buttons will indeed
delete the row from the database.
But how do we go about linking a web page with Python code with a database?
This is the last piece of the puzzle.
Up until now, everything's been with forms and also with URLs.
But what if the user is not typing anything in,
they're just clicking a button?
Well, watch this.
Let me go ahead and sniff the traffic, which
you could be in the habit of doing now.
Any time you're curious how a website works, let me go to the Network tab.
And Carter, shall we deregister you from basketball?
Let's deregister Carter and let's see what just happened.
If I look at the deregister request, notice that it's a POST.
The status code that eventually came back as 302,
but let's look at the request itself.
All the headers there we'll ignore.
The only thing that button submits, cleverly,
is an ID parameter, a key equaling two.
What does two presumably represent or map to?
Where did this two come from?
It doesn't say Carter, it doesn't say basketball?
What is it?
AUDIENCE: The second person who registered.
DAVID: The second person that registered.
So those primary keys that we started talking about a couple of weeks
ago, why it's useful to be able to uniquely identify a row in a table,
here is just one of the reasons why.
If it suffices for me just to send the ID number of the person
I want to delete from the database, because I can then have code like this.
If I go into app.py and I look at my deregister route now, the last of them,
notice that I got this.
I first go into the form, and I get the ID that was submitted, hopefully.
If there was, in fact, an ID, and the form wasn't somehow empty,
I execute this line of code.
Delete from registrants where ID equals question mark,
and then I plug-in that number, deleting Carter and only Carter.
And I'm not using his name, because what if we have two people named Carter,
two people named Emma or David?
You don't want to delete both of them.
That's why these unique IDs are so, so important.
And here's another reason why.
You don't want to store some things in URLs.
Suppose we went to this URL, deregister?ID=3.
Suppose I, maliciously, emailed this URL to Emma.
It doesn't matter so much what the beginning is,
but supposed I emailed her this URL, /deregister?ID=3, and I said, hey,
Emma, click this.
And it uses GET instead of POST.
What did I just trick her into doing?
What's going to happen if Emma clicks this?
Yeah?
AUDIENCE: Deregistering?
DAVID: You would trick her into deregistering herself.
Why?
Because if she's logged into this FroshIMS website,
and the URL contains her ID just because I'm being malicious,
and she clicked on it and the website is using GET,
unfortunately, GET URLs are, again, stateful.
They have state information in the URLs.
And in this case, it's enough to delete the user and boom,
she would have accidentally deregistered herself.
And this is pretty innocuous.
Suppose that this was her bank account trying
to make a withdrawal or a deposit.
Suppose that this were some other website, a Facebook URL,
trying to trick her into posting something automatically.
Here, too, is another consideration when you
should use POST versus GET, because GET requests can
be plugged into emails sent via Slack messages, text messages, or the like.
And unless there's a prompt saying, are you sure
you want to deregister yourself, you might blindly
trick the user into being vulnerable to what's
called a cross-site request forgery.
A fancy way of saying you trick them into clicking a link
that they shouldn't have, because the website was using GET alone.
All right, any question, then, on these building blocks?
Yeah.
AUDIENCE: What do the first thing in the instance of the SQL
[INAUDIBLE] where they have three slashes?
What does that mean?
DAVID: When three columns, you mean?
AUDIENCE: No, three forward slashes.
DAVID: The three forward slashes.
I'm not sure I follow.
AUDIENCE: Yeah, so I think it's in [INAUDIBLE]..
DAVID: Sorry, it's in where?
Which file?
AUDIENCE: It's in [INAUDIBLE] scroll up.
[INAUDIBLE]
DAVID: Sorry, the other direction?
AUDIENCE: Yeah.
DAVID: OK.
AUDIENCE: [INAUDIBLE].
So please scroll a little bit more.
DAVID: Keep scrolling more?
Oh, this thing.
OK, sorry.
This is a URI, it's typical syntax that's referring to the SQLite
protocol, so to speak, which means use SQLite to talk to a file locally.
:// is just like you and I see in URLs.
The third slash, essentially, means current folder.
That's all.
So it's a weird curiosity, but it's typical
whenever you're referring to a local file and not one that's
elsewhere on the internet.
That's a bit of an oversimplification, but that's indeed a convention.
Sorry for not clicking earlier.
All right, let's do one other iteration of FroshIMS
here just to show what I was actually doing too, back in the day,
was not only storing these things in CSV files, as I recall.
I was also automatically generating an email
to the proctor in charge of the intramural sports program
so that they would have sort of a running history of people registering
and they could easily reply to them as well.
Let me go into FroshIMS version five, which I precreated here,
and let me go ahead and open up, say, app.py this time.
And this is some code that I wrote in advance.
And it looks a little scary at first glance, but I've done the following.
I have now added the Flask mail library to the picture
by adding Flask mail to requirements.txt and running a command
to automatically install email support for Flask as well.
And this is a little bit cryptic, but it's honestly mostly
copy paste from the documentation.
What I'm doing here is I'm configuring my Flask
application with a few configuration variables, if you will.
This is the syntax for that. app.config is a special dictionary that
comes with Flask that is automatically created when you create the app appear
on line nine, and I just had to fill in a whole bunch of configuration
values for the default sender address that I
want to send email as, the default password I want to use to send email,
the port number, the TCP port, that we talked about last week.
The mail server, I'm going to use Gmail's smtp.gmail.com server.
Use TLS, this means use encryption.
So I set that to true.
Mail username, this is going to grab it from my environment.
So for security purposes, I didn't want to hard code my own Gmail username
and password into the code.
So I'm actually storing those in what are called environment variables.
You'll see more of these in problem set nine,
and it's a very common convention on a server
in the real world to store sensitive information in the computer's memory
so that it can be accessed when your website is running,
but not in your source code.
It's way too easy if you put credentials,
sensitive stuff in your source code, to post it to GitHub
or to screenshot it accidentally, or for information to leak out.
So for today's purposes, know that the OS.environ dictionary refers to what
are called environment variables.
And this is like an out-of-band, a special way of defining key value
pairs in the computer's memory by running a certain command
but that never show up in your actual code.
Otherwise, there would be so many usernames and passwords
accidentally visible on the internet.
So I've installed this in advance.
Let me see if I can do this correctly.
Let me go over to another tab in just a moment.
And here, I have on my second screen here, John Harvards inbox.
It's currently empty, and I'm going to go ahead and register
for some sport as John Harvard here, hopefully.
So let me go ahead and run Flask run on this version five.
Let me go ahead and reload the main screen.
Not that one.
Let me reload the main screen here.
This time, clearly, I'm asking for name and email.
So name will be John Harvard.
jharvard@cs50.harvard.edu.
He'll register for, how about soccer.
Register.
And if I did this correctly, not only is John Harvard, on his screen,
seeing you are registered, but when he checks his email on this other screen,
crossing his fingers that this actually works as a demonstration,
and I promise it did right before class.
Horrifying.
I don't think there's a mistake this time.
Let me try something over here real quick,
but I don't think this is broken.
It wouldn't have said success if it were.
I just tried submitting again, so I just did another you are registered.
Oh, I'm really sad right now.
AUDIENCE: [INAUDIBLE]
DAVID: What's that?
AUDIENCE: Check spam.
DAVID: I could check spam, but then it's--
not sure we want to show spam here on the internet that every one of us gets.
Oh, maybe.
Oh!
[LAUGHTER AND APPLAUDING]
Thank you.
OK.
Wow, that was a risky click I worried.
All right, so you are registered is the email that I sent out,
and it doesn't have any actual information in it.
But back in the day it would have, because I
included the student's name and their dorm
and all of the other fields of information that we asked for.
So let's just take a quick look at how that code might work.
I did have to configure Gmail in a certain way to allow,
what they call, less secure apps using SMTP,
which is the protocol used for outbound email.
But besides setting these things, let's look at the register route down here.
It's actually pretty straightforward.
In my register route, I validated the submission just like before.
Nothing new there.
I then confirmed the registration down here, nothing new there.
All I did was use two new lines of code.
And it's this easy to automate the sending of emails.
I apparently have done it too many times,
which is why it ended up in spam.
I created a variable called message.
I used a message function that I must have imported higher up,
so we'll go back to that.
Here's, apparently, the subject line as the first argument.
And the second argument is the named parameter recipients,
which takes a list of emails that should get the confirmation email.
So in brackets, I just put the one user's email
and then mail.send that message.
So let's scroll back up to see what message and what mail actually is.
Mail, I think, we saw.
Yep, mail is this, which I have as a variable
because I followed the documentation for this library.
You simply configure your current app with Mail support, capital M here.
And if you look up here now, on line seven,
here's the new library from Flask mail I imported.
Capital Mail, capital Message, so that I had the ability to create a message
and send a mail.
So such a simple thing whether you want to confirm things for users,
you want to do password resets.
It can be this easy to actually generate emails
provided you have the requisite access and software installed.
And just to make clear that I did add something here,
let me open up my requirements.txt file, and indeed, I
have both Flask and Flask-mail ready to go.
But I ran the command in advance to actually do that.
All right, any questions, then, on these examples here?
No?
All right.
So what other pieces might actually remain for us let me flip over here.
It turns out that a key component of most any web
application nowadays that we haven't touched on yet,
but it'll be one of our final flourishes today, is the notion of a session.
And a session is actually a feature that derives
from all of the basics we talked about today and last week,
and a session is the technical term for what you and I know as a shopping cart.
When you go to amazon.com and you start adding things to your shopping cart,
they follow you from page to page to page.
Heck if you close your browser, come back to the next day,
they're typically still your shopping cart, which is great for Amazon
because they want your business.
They don't want you to have to start from scratch the next day.
Similarly, when you log into any website these days,
even if it's not an e-commerce thing but it has usernames and passwords,
you and I are not in the habit of logging
into every darn page we visit on a website.
Typically, you log in once, and then for the next hour, day, week, year,
you stay logged into that website.
So somehow, the website is remembering that you have logged in.
And that is being implemented by way of this thing called
a session, and perhaps a more familiar term
that you might know as, and worry about, called cookies.
Let's go ahead and take one more five minute break here.
And when we come back, we'll look at cookies, sessions,
and these final features.
All right.
So the promise now is that we're going to implement
this notion of a session, which is going to allow us to log users in and keep
them logged in and even implement things like a shopping cart.
And the overarching goal here is to build an application that
is, quote unquote, "stateful."
Again, state refers to information, and something that's stateful
remembers information.
And in this context, the curiosity is that HTTP is technically
a stateless protocol.
Once you visit a URL, http://something, hit Enter,
web page is downloaded to your browser, like that's it.
You can unplug from the internet, you can turn off your Wi-Fi,
but you still have the web page locally.
And yet we somehow want to make sure that the next time you
click on a link on that website, it doesn't forget who you are.
Or the next thing you add to your shopping cart,
it doesn't forget what was already there.
So we somehow want to make HTTP stateful,
and we can actually do this using the building blocks we've seen thus far.
So concretely, here's a form you might see occasionally, but pretty rarely,
when you log into Gmail.
And I say rarely because most of you don't log into Gmail frequently,
you just stay logged in, pretty much endlessly, in your browser.
And that's because Google has made the conscious choice
to give you a very long session time, maybe a day, a week,
a month, a year, because they don't really
want to add friction to using their tool and making you log in every darn day.
By contrast, there's other applications on campus,
including some of the CS50 zone, that makes you log in every time.
Because we want to make sure that it's indeed you
accessing the site, and not a roommate or friend or someone maliciously.
So once you do fill out this form, how does Google subsequently
know that you are you, and when you reload the page even
or open a second tab for your same Gmail account,
how do they know that you're still David or Carter or Emma or someone else?
Well, let's look underneath the hood of what's going on.
When you log into Gmail, essentially, you initially
see a form like this using a GET request.
And the website responds like we saw last week
with some kind of HTTP response.
Hopefully 200 OK with the form.
Meanwhile, the website might also respond with an HTTP header
that, last week we didn't care about, this week, we now do.
Whenever you visit a website, it is very commonly the case
that the website is putting a cookie on your computer.
And you may generally know that cookies can be bad
and they track you in some way, and that's both a blessing and a curse.
Without cookies, you could not implement things like shopping carts and log-ins
as we know them today.
Unfortunately, they can also be used for ill purposes
like tracking you on every website and serving you ads more effectively and so
forth.
So with good comes some bad.
But the basic primitive for us, the computer scientist,
boils down to just HTTP headers.
A cookie is typically a big number, a big, seemingly random value,
that a server tells your browser to store in memory, or even
longer term, store on disk.
So you can think of it like a file that a server is planting on your computer.
And the promise that HTTP makes is that if a server sets
a cookie on your computer, you will represent
that same cookie or that same value on every subsequent request.
So when you visit the website like Gmail,
they plop a cookie on your computer like this with some session
equals value, some long random value.
One, two, three, A, B, C, something like that.
And when you then visit another page on gmail.com or any other website,
you send the opposite header, not set cookie, but just cookie colon,
and you send the exact same value.
It's similar to going to a club or an amusement park
where you pay once, you go through the gates once,
you get checked by security once, and then
they very often take like a little stamp and say, OK, now you can come and go.
And then for you, efficiency-wise, if you come back later in the day
or later in the evening, you can just present your hand.
You've been stamped, presumably.
They've already-- you've already paid, you've
already been searched or whatnot.
And so it's this sort of fast track ticket
back into the club, back into the park.
That's essentially what a cookie is doing for you, whereby
it's a way of reminding the website we've already done this,
you already asked me for my username and password.
This is my path to now come and go.
Now, unlike this hand stamp, which can be easily copied or transferred
or duplicated or kept on over multiple days,
these cookies are really big, seemingly random values, letters and numbers.
So statistically, there's no way someone else
is just going to guess your cookie value and pretend to be you It's just
very low probability, statistically.
But this is all it boils down to is this agreement between browser and server
to send these values back and forth in this way.
So when we actually translate this, now, to code,
let's do something like a simple login app.
Let me go into a folder I made in advance today called login.
And let me code up app.py and let's take a look in here.
So what's going on?
A couple of new things up top.
If I want to have the ability to stamp my users hands, virtually,
and implement sessions, I'm going to have to import from Flask support
for sessions.
So this is another feature you get for free by using a framework
and not having to implement all this yourself.
And from the Flask session library, I'm going
to import Session, capital S. Why?
I'm going to configure the session as follows.
Long story short, there's different ways to implement sessions.
The server can store these cookies in a database, in a file,
in memory, in RAM, in other places too.
We are telling it to store these cookies on the server's hard drive.
So in fact, whenever you use sessions as you will for problem set nine,
you'll actually see a folder suddenly appear called Flask_session,
inside of which are the cookies, essentially,
for any users or friends or yourself who've been
visiting your particular application.
So I'm setting it to use the file system,
and I don't want them to be permanent because I want,
when you close your browser, the session to go away.
They could be made to be permanent and last much longer.
Then I tell my app to support sessions.
And that's it for now.
Let's see what this application actually does before we dissect the code.
Let me go over to my terminal window, run Flask run, and then let me go ahead
and reload my preview URL.
Give it a second to kick back in.
Let me go ahead and open my URL.
Come on.
Oops, let me go ahead.
Too long of a break.
There we go.
So this website simply has a login form.
There's no password, though I could certainly
add that and check for that too.
It just asks for your name.
So I'm going to log in as myself, David, and click Login.
And now notice I'm currently at the /login route.
But notice this.
If I try to go to the default route, just,
slash, which is where most websites live by default,
notice that I magically get redirected to log in.
So somehow, my code knows, hey, if you're not logged in, you're going
to /login instead.
Let me type in my name, David, and click Login.
And now notice I am back at slash.
Chrome is sort of annoyingly hiding it, but this is the same thing as just
a single slash.
And now notice it says you are logged in as David.
Log out.
What's cool is notice if I reload the page, it still knows that.
If I create a second tab and go to the same URL, it still knows that.
I could even--
I could keep doing this in multiple tabs,
it's still going to remember me on both of them as being logged in as David.
So how does that work?
Especially when I click Log Out, then I get forgotten altogether.
All right, so let's see how this works.
And it's some basic building blocks.
Under my /route, notice I have this.
If there is no name in the session, redirect the user to /login.
So these two lines together are what implement
that automatic redirection using HTTP 301 or 302 automatically.
It's handled for me with these two lines.
Otherwise, show index.html.
All right, let's go down that rabbit hole.
What's in index.html?
Well, if I look in my--
let me look in my templates folder for my login demo and look
at templates/index.html.
All right, so what's going on here?
I extend layout.html, I have a block body,
and then I've got some other syntax.
So we haven't seen this yet, but it's more Jinja stuff, which
again, is almost identical to Python.
If there's a name in the session variable,
then literally say you are logged in as curly braces session bracket name.
And then notice this, I've got a simple HTML link to log out via /logout.
Else, if there is no name in the session,
then it apparently says you are not logged in and it leads me to an HTML
link to /login and then end diff.
So again, Jinja does not rely on indentation.
Recall the HTML and CSS don't really care about indentation,
only the human does.
But in code with Jinja, you need these end tags,
end block, end for, end if, to make super obvious
that you're done with that thought.
So session is just this magic variable that we now
have access to because we've included these two lines of code
and these that handle that whole process of stamping every user's hand
with a different, unique identifier.
If I made my code space public and I let all of you
visit the exact same URL, all of you would be logged out by default.
You could all type your own names individually,
all log in at the same URL using different sessions.
And in fact, I would then see, if I go into my terminal window
here and my login directory, notice the Flask session directory I mentioned.
And if I CD into that and type ls, notice that I had two tabs open,
or actually, I think I started the server twice.
I have two files in there.
I would ultimately have one file for every one of you.
And that's what's beautiful about sessions
is it creates the illusion of per user storage.
Inside of my session is my name, inside of your session, so to speak,
is your name.
And the same is going to apply to shopping carts, ultimately, as well.
Let's see how login works here.
My login route supports both GET and POST, so I could play around if I want.
And notice this, this login route is kind of interesting as follows.
If the user got to this route via POST, my inference
is that they must have submitted a form.
Why?
Because that's how I'm going to design the HTML form in a second.
And if they did submit the form via POST,
I'm going to store, in the session, at the name
key, whatever the human's name is.
And then, I'm going to redirect them back to slash.
Otherwise, I'm going to show them the login form.
So this is what's cool.
If I go to this login form, which lives at,
literally, slash login, by default, when you visit a URL like that,
you're visiting via GET.
And so that's why I see the form.
However, notice this.
The form, very cleverly, submits to itself,
like the one route/login submits to its same self, /login,
but it uses POST when you submit the form.
And this is a nice way of having one route but for two different types
of operations or views.
When I'm just there visiting /login via URL, it shows me the form.
But if I submit the form, then this logic, these three lines, kick in,
and this just avoids my having to have both an index route and a greet route,
for instance.
I can just have one route that handles both GET and POST.
How about logout?
What does this do?
Well, it's as simple as this.
Change whatever name is in the session to be
none, which is Python's version of null, essentially, and then redirect the user
back to slash.
Because now, in index.html, I will not notice a name there anymore.
This will be false.
And so I'll tell the user instead, you are not logged in.
So like it's--
I want to say as simple as this is, though I realize
this is a bunch of steps involved.
This is the essence of every website on the internet that
has usernames and passwords.
And we skip the password name step for that, more on that in problem set nine,
but this is how every website out there remembers that you're logged in.
And how this works, ultimately, is that as soon
as you use in Python lines like this and lines like this,
Flask takes care of stamping the virtual hand of all of your users
and whenever Flask sees the same cookie coming back from a user,
it grabs the appropriate file from that folder,
loads it into the session global variable
so that your code is now unique to that user and their name.
Let's do one other example with sessions here
that'll show how we might use these, now, for shopping carts.
Let me go into the store example here.
Let me go ahead and run this thing first.
If I run store in my same tab and go back over here,
we'll see a very ugly e-commerce site that
just sells seven different books here.
But each of these books has a button via which I can add it to my cart.
All right, well where are these books coming from?
Well, let's poke around.
Let me go into my terminal window again.
Let me go into this example, which is called store,
and let me open up about index dot ht-- whoops.
Let's open up index, how about, books.html
is the default one, not index this time.
So if I look here, notice that that route that we just saw
uses a for loop in Jinja to iterate over a whole bunch of books, apparently,
and it outputs, in an H2 tag, the title of the book,
and then another one of these forms.
So that's interesting.
Let's go back one step.
Let's go ahead and open up app.py, because that must be-- excuse me,
what's ticking all of this off.
Notice that this file is importing session support.
It's configuring sessions down here, but it's also
connecting to a store.db file.
So it's adding some SQLite.
And notice this, in my /route, I'm selecting star from books,
which is going to give me a list of dictionaries,
each of which represents a row of books.
And I'm going to pass that list of books into my books.html template, which
is why this for loop works the way it does.
Let's look at this actual database.
Let me increase my terminal window and do SQLite of store.db.schema
will show me everything.
There's not much there.
It's a book-- it's a table called books with two columns, ID and title.
Let's do select star from books semicolon.
There are the seven books, each of which has a unique ID.
And you might see where this is going.
If I go to the UI and I look at each of these buttons
for add to cart, just like Amazon might have, notice that each of these buttons
is just a form.
And what's magical here, just like deregister,
even though I didn't highlight it at the time,
there's another type of input that allows
you to specify a value without the human being able easily to change it.
Instead of type equals text or type equals submit,
type equals hidden will put the value in the form but not reveal it to the user.
So that's how I'm saying that the idea of this book is one,
the idea of this book is two, the idea of this book is three, and so forth.
And each of these forms, then, will submit, apparently,
to /cart using POST and that would seem to be what adds things to cart.
So let's try this.
Let me click on one or two of these.
Let's add the first book, add to cart.
Here's my cart.
Notice my route change to /cart.
All right, let's go back and let's add the book number two.
There we have that one.
And let's skip ahead to the seventh book, Deathly Hallows,
and now we have all three books here.
So what does the cart route do at /cart?
Well, let's look.
If I go back to my terminal window, look at app.py and look at /cart, OK,
there's a lot going on here, but let's see.
So the /cart route supports both GET or POST,
which is a nice way to consolidate things into one URL.
All right, this is interesting.
If there is not a, quote unquote, "cart" key in session,
we haven't technically seen this syntax.
But long story short, these lines here do ensure that the cart exists.
What do I mean by that?
It makes sure that there's a cart key in the session, global variable,
and it's by default going to be an empty list.
Why?
That just means you have an empty shopping cart.
But if the user visits this route via POST and the user did provide an ID,
they didn't muck with the form in any way and try to hack into the website,
they gave me a valid ID, then I'm going to use this syntax.
If session bracket cart is a list--
recall from a couple of weeks ago that dot append just
adds something to the list.
So I'm going to add the ID to the list and return the user to cart.
Otherwise, if the user is at /cart via GET, implicitly, we just do this.
Select star from books where ID is in.
And this might be syntax you recall from Pset six.
It lets you look for multiple IDs all at once,
because if I have a list of session--
list of IDs in my cart, I can get all of those books at once.
So long story short, what has happened here?
I am storing, in the cart, the books that I myself have added to my cart.
My browser is sending the same hand stamp again and again,
which is how this website knows that it's me adding these books to my cart
and not you or not Carter or not Emma.
Indeed, if all of us visited the same long URL and I made it public
and allowed that, then we would all have our own illusions
of our own separate carts.
And each of those carts, in practice, would just
be stored in this Flask session directory on the server
so that the server can keep track of each
of us using, again, these cookie values that are being
sent back and forth via these headers.
All right.
I know that's a lot, but again, it's just
the new Python way of just leveraging those HTTP
headers from last week in a clever way.
Any questions before we look at one final set of examples?
Yeah.
AUDIENCE: [INAUDIBLE] understand how a log in has to do with [INAUDIBLE]??
How does it use [INAUDIBLE],, how do you change [INAUDIBLE]??
Because in order to use a GET request dot [INAUDIBLE] equals,
there has to be an exchange in [INAUDIBLE]..
DAVID: So I think you're asking about using the GET
and POST in the same function.
So this is just a nice aesthetic, if you will.
If I had to have separate routes for GET and POST, I mean,
it literally might mean I need twice as many routes in my file.
And it just starts to get a little annoying.
And these days, too, in terms of user experience,
this is maybe only appeals to the geek in us, but having clean URLs
is actually a thing.
You don't want to have lots of words in the URL,
it's nice if the URLs are nice and succinct and canonical, if you will.
So it's nice if I can centralize all of my shopping cart functionality in /cart
only, and not in multiple routes, one for GET, one for POST.
It's a little nitpicky of me, but this is commonly done here.
So what this code here means is that this route, this function,
henceforth will support both GET requests and POST requests.
But then I need to distinguish between whether it's GET or POST coming in.
Because if it's a GET request, I want to show the cart.
If it's a POST request, I want to update the cart.
And the simplest way to do that is just to check this value here.
In the request variable that we imported from Flask up above,
you can check what is the current type of request.
Is it a GET, is it a POST, or is it something else altogether?
There are other verbs.
If it's a POST, that must mean, because I created the web form that uses POST,
that the user clicked the Add to Cart button.
Otherwise, if it's not POST, it's implicitly going to be logically GET.
Then, I just want to show the user the contents of the cart
and I use these lines instead.
So it's just one way of avoiding having two routes for two different HTTP
verbs.
You can combine them so long as you have a check like this.
If I really wanted to be pedantic, I could do this elif request.method=get.
This would be more symmetric, but it's not really necessary,
because I know there's only two possibilities.
Hope that helps.
All right, let's do one final set of examples here
that's going to tie the last of these features
together to something that you probably see
quite often in real-world applications.
And that, for better or for worse, is now
going to involve tying back in some JavaScript from last week.
The goal at hand of these examples is not
to necessarily master how you yourself would write the Python code, the SQL
code, the JavaScript code, but just to give you a mental model for how
these different languages work.
So that for final projects, especially if you
do want to add JavaScript functionality, much more interactive user interface,
you at least have the bare bones of a mental model for how
you can tie these languages together.
Even though our focus, generally, has been more on Python and SQL
than on JavaScript from last week.
Let me go ahead and open up an example called shows, version zero of this.
And let me do Flask run.
And let me go into my URL here and see what this application looks
like by default. This has just a simple query text box with a search box.
Let's take a look at the HTML that just got sent to my browser.
All right, there's not much going on here at all.
So there's a form whose action is /search.
It's going to submit via GET.
It's going to use a q parameter, just like Google it seems, and submit it.
So this actually looks like the Google form we did last week.
So let's see what goes on here.
Let me search for something like cat.
Enter.
OK, so it looks like--
all right, so this is actually a somewhat familiar file.
What I've gone ahead and done is I've grabbed all of the titles of TV shows
from a couple of weeks ago when we first introduced SQL,
and I loaded them into this demo so that you can search
by keyword for any word you want.
I just searched for cat.
If we were to do this again, we would see all the title of TV shows
that contain D-O-G, dog, as a substring somewhere and so forth.
So this is a traditional way of doing this.
Just like in Google, it uses /search?q=cat, q=dog, and so forth.
How does that work?
Well, let's just take a quick look at app.py here.
Let me go into my zero example here, show zero, and open up
app.py and see what's going on.
All right, very simple.
Here's the form, that's how we started today.
And here is the /search route.
Well, what's going on here?
This gets a little interesting.
So I first select a whole bunch of shows by doing this.
Select star from shows, where title like question mark.
And then I'm using some percent signs from SQL
on both the left and the right, and I'm plugging
in whatever the user's input was for q.
If I didn't use like and I used equal instead,
I could get rid of these curly brace, these percent signs,
but then it would have to be a show called cat or called dog as opposed
to it being like cat or like dog.
This whole line returns to me a list of dictionaries, each of which
represents a show in the database.
And then, I'm passing all of those shows to a template called search.html.
So let's just follow that breadcrumb, let's open up shows dot--
sorry, search.html.
All right, so this is where templating gets cool.
So I just passed back hundreds of results, potentially,
but the only thing I'm outputting is an unordered list
and using a Jinja for loop and li tag containing
the titles of each of those shows.
And just to prove that this is indeed a familiar data set
and I actually simplified it a bit, if I look at shows.db with SQLite,
I threw away all the other stuff like ratings and actors and everyone else
and I just have, for instance, select star from shows
limit 10, just so we can see 10 of them.
There's 10 of the shows from that database.
So that's all that's in the database itself.
So it would look like this is a pretty vanilla web application.
It uses GET, it submits it to the server,
the server spits out a response, and that response, then,
looks like this, which is a huge number of li tags, one for each cat
or one for each dog match.
But everything else comes from a layout.html.
All the stuff at the top and at the bottom.
All right, so these days, though, we're in the habit of seeing autocomplete.
And you start typing something and you don't have to hit Submit,
you don't have to click a button, you don't have to go to a new page.
Web applications, nowadays, are much more dynamic.
So let's take a look at this version one of this thing.
Let me go into shows one and close my previous tabs
and run Flask run in here.
And it's almost the same thing, but watch the behavior change a little bit.
I'm reloading the form, there's no button now.
So gone is the need for a submit button.
I want to implement autocomplete now.
So let's go ahead and type in C. OK, there's
every show that starts with C. A, there's
every show that has C-A in it, rather.
T, there's every show with C-A-T in it.
I can start it again and do dog, but notice how instantaneous it was.
And notice my URL never changed, there's no /search route,
and it's just immediate.
With every keystroke, it is searching again and again and again.
That's a nice UX, user experience, because it's immediate.
This is what users are used to these days.
But if I look at the source code here, notice that in the source code,
there's just an empty UL by default but there is some fancy JavaScript code.
So let's see what's going on here.
This JavaScript code is doing the following.
Let me zoom in a little bit more.
This JavaScript code is first selecting, with query selector,
which you used this past week, quote unquote "input, " all right,
so that's just getting the text box.
Then it's adding an event listener to that input for the input event.
We didn't talk about this last week, but literally,
when you provide any kind of input by typing,
by pasting, by any other user interface mechanism,
it triggers an event called input.
So similar to key press or key up.
I then have a function, no worries about this async function for now.
Then what do I do inside of this?
All right, so this is new, and this is the part
that let's just focus on the ideas and not the syntax.
JavaScript, nowadays, comes with a function
called fetch that allows you to GET or POST information to a server
without reloading the whole page.
You can sort of secretly do it inside of the page.
What do I want to fetch?
slash search question mark q equals whatever the value of that input is.
When I get back a response, I want to get the text of that response
and store it in a variable called shows.
And I'm deliberately bouncing around, ignoring special words
like await and await here, but for now, just focus on what came back.
A response came back from the server, I'm getting the text from it,
storing it in a variable called shows.
What am I then doing?
I'm using query selector to select my UL, which is empty by default,
and I'm changing its inner HTML to be equal to the shows that
came back from the server.
So let's poke around.
Here's where, again, developer tools are quite powerful.
Let me go ahead and reload this page to get rid of everything.
And let me now open up inspect.
Let me go to the Network tab and let's just sniff the traffic going
between my browser and server.
I'm going to search for C. Notice that immediately triggered an HTTP request
to /search?q=c.
So I didn't even finish my cat thought, but notice what came back.
A bunch of response headers, but let's actually click on the raw response.
This is literally the response from the server, just a whole bunch of li tags.
No UL, no HTML, no title, no body, nothing.
Just li tags.
And we can actually simulate this.
Let me manually go to that same URL, q=c, Enter.
We are just going to get back--
whoops, sorry.
slash search q equals c, we are just going to get back this stuff,
which if I view source, it's not even a complete web page.
The browser is trying to show it to me as a complete web page with bullets,
but it's really just partial HTML.
But that's perfect, because this is literally
what I essentially want my Python code to copy paste
into the otherwise empty UL tag.
And that's what this JavaScript code then, here, is doing.
Once it gets back that response from the server, it's using these lines of code
to plug all of those li's into the UL after the fact.
Again, changing the so-called dom.
But there's a slightly better way to do this because, honestly, this
is not the best design.
Because if you've got a hundred shows or more, you're sending all of these tags
unnecessarily.
Why do I need to send all of these stupid HTML tags?
Why don't I just create those when I'm ready to create them?
Well, here's the final flourish.
Whenever making a web application nowadays,
where client and server keep talking to one another, Google Maps does this,
Gmail does this, literally every cool application
nowadays you load the page once and then it
keeps on interacting with you without you
reloading or having to change the URL.
Let's actually use a format called JSON, JavaScript Object Notation, which
is to say there's just a better, more efficient, better
designed way to send that same data.
I'm going to go into shows two now and do Flask run.
And I'm going to go back to my page here.
The user interface is exactly the same, and it still works exactly the same.
Here's C, C-A, C-A-T, and so forth.
But let's see what's coming back now.
If I go to /search?q=cat, Enter, notice that I get this crazy-looking syntax.
But the fact that it's so compact is actually a good thing.
This is actually going to-- let me format it a little nicer, well,
or a little worse.
This is what's called JavaScript Object Notation.
In JavaScript, an angle-- a square bracket means here comes an array.
In JavaScript, a curly bracket says here comes an object, AKA, a dictionary.
And you might recall from--
did we do?
Yes, sort of recall that you can now have keys and values in JavaScript
notation using colons like this.
So long story short, cryptic as this is to you and me
and not very human friendly, it's very machine friendly.
Because for every title in that database,
I get back its ID and its title, its ID and its title, its ID and its title.
And this is a very generic format that an API, an application programming
interface, might return to you.
And this is how APIs, nowadays, work.
You get back very raw textual data in this format, JSON format,
and then you can write code that actually programmatically turns
that JSON data into any language you want, for instance, HTML.
So here's the third and final version of this program.
I, again, select my input.
I, again, listen for input.
I then, when I get input, call this function.
I fetch slash search q equals whatever that input was, C or C-A or C-A-T.
I then wait for the response, but instead of getting text,
I'm calling this other function that comes with JavaScript these days,
called JSON, that just parses that.
It turns it into a dictionary for me, or really a list of dictionaries for me,
and stores it in a variable called shows.
And this is where you start to see the convergence of HTML with JavaScript.
Let me initialize a variable called HTML to nothing, quote unquote,
using single quotes, but I could also use double quotes.
This is JavaScript syntax for a loop.
Let me iterate over every ID in the show's list
that I just got back in the server, that big chunk of JSON data.
Let me create a variable called Title that's equal to the shows--
the title of the show at that ID.
But for reasons we'll come back to, let me
replace a couple of scary characters.
Then let me dynamically add to this variable, an li tag, the actual title,
and a close li tag.
And then very lastly, after this for loop,
let me update the ULs in our HTML to be the HTML I just created on the fly.
So in short, don't worry too much about the syntax
because you won't need to use this unless you start playing
with more advanced features quite soon.
But what we're doing is, with JavaScript, we're
creating a bigger and bigger and bigger string of HTML
containing all of the open brackets, the li tags, the closed brackets,
but we're just grabbing the raw data from the server.
And so in fact in problem set nine, you're
going to use a real world third party API, application programming
interface, for which you sign up.
The data you're going to get back from that API is not
going to be show titles, but actually stock quotes and stocks
ticker symbols and the prices of last--
at which stocks were last bought or sold,
and you're going to get that data back in JSON format.
And you're going to write a bit of code that's then going to convert that
to the requisite HTML on the page.
So the final result here is literally the kind of autocomplete
that you and I see and take for granted every day,
and that's ultimately how it works.
HTML and CSS are used to present the data, your so-called view.
Python might be used to send or get the data on the backend server.
And then lastly, JavaScript is going to be used
to make things dynamic and interactive.
So I know that's a whole bunch of building blocks,
but the whole point of problem set nine to tie everything together,
set the stage for hopefully a very successful final project.
Why don't we go ahead and wrap up there, and we'll see
you one last time next week for emoji.
[MUSIC PLAYING]