[MUSIC PLAYING] DAVID J. MALAN: All right, this is CS50. And this is already week nine. And this means this is the
week where we synthesize the past several weeks, from Python
to SQL to HTML to CSS to JavaScript. Each of those languages that we've
rather looked at in isolation now rather come together
toward an end of just making more modern applications, be them web
based or be they mobile based as well. So, up until now, we've been using,
to serve all of the web stuff that you've done, this
program called HTTP server. Now, this isn't a CS50 thing. It's just a standard program that
we've installed into Codespaces. So it's a Linux program that
just runs your own web server. And it allows you to run
it on nonstandard ports. For instance, you've been
using it on port 80, 80 only because Codespaces is using 80 and
443, recall, which were the defaults. But, up until now, this
program's purpose in life was just to serve static content,
so like web pages written in HTML, maybe some CSS, maybe some
JavaScript that you wrote in advance that just never really
changes until such time as you log back into the server, save
the file after making some edits, and then someone reloads their page. But, of course, the web that we all
use today, be it Gmail or any website, is so much more interactive. When you search for
something on Google, there's no Google engineer that, in
advance, has written up an HTML page containing a list
of 10 cats or 10 dogs or 10 birds just waiting for someone
to search for that particular keyword. Rather, there's some database involved. The HTML's being dynamically generated. And it's all indeed very dynamic. So whereas, last week,
we focused on websites, this week we'll focus
really on web applications. And, really, the key difference
is just that applications take input and produce output whereas
websites are really generally thought of as static. But it's a blurry line. It's not necessarily a
technical distinction. But today things start to
get much more interactive. So we're not going to be able
to use HTTP server alone. We're going to need something
a little smarter that knows how to take input from users via the URL. So, in fact, let's look at some sample
URLs that we looked at last week just to remind ourselves of some of
the jargon and really the syntax. So here's a standard
URL and Flask, recall, is just the default page on the server. And, usually, the file is
called index.html by convention. But, depending on the operating
system or web server you're using, it could be a different file. But index.html is probably
the most common default. So you could explicitly state
the file name on most web servers like file.html,
index.html, whatever it is. You can have folders or
directories on a web server. And this would imply that
your index.html file is in a folder called folder in this case. Or you can have a folder/file or even
folder/folder/folder/file and so forth. So this is a very direct mapping
from the URL to the file system, so to speak, like the
hard drive on the server. But, today, we're going to do, like
most computer scientists tend to do, is more of an abstraction of this. This is how URLs look. But it turns out, once you
control the web server with code, you don't need things to line up
with actual file names and files. You can really put your
content wherever you want. And so, in general, we're
going to focus today on just thinking of everything
after the domain name as a path, generally speaking. And a synonym for this, in the context
of the web, would also just be a route. So a route is just some number of
letters, maybe some slashes, maybe a file extension that refers to
some part of your application. But, more interestingly-- and this
is what makes things an application and not just a static website-- recall, that this is how
websites can take input in the form of URLs, a question
mark, then key equals value pair. Or if you want two or
maybe three or four, you separate them with ampersands. But that's kind of it. The web today is going to work
just like it did last week, but we're going to start leveraging
these primitives in a much more powerful, more interactive way. So here, recall, is what might
be inside of the virtual envelope when you search for
something on google.com. It's going to request of
the web server/search, which is the name, by convention,
of Google's search application because they've got a lot of
businesses running on their servers. And if you have ?q=cats, this
is essentially the message, the HTTP message that would've been
in last week's virtual envelope when I searched for cats. Hopefully, you get back a
response from the server containing those actual cats. But, again, there's probably a lot
more logic going on with a database and somehow generating that HTML
that Google is doing for us. So we, today, are going
to introduce really what's called a framework or
technically a microframework, which means it's relatively small versus
alternatives that are out there. And it's called Flask. So Flask is really a
third-party library-- and it's popular in the
Python world-- that's just going to make it easier to
implement web applications using Python. There are alternatives. A very popular one is called
Django, which some of you might've heard of before. And there's dozens of others in
decreasing popularity daresay. But Flask is among the most
popular microframeworks, which means you can really just
solve a few problems pretty simply without feeling like you're
learning some third-party library as much as you are learning
concepts that transcend one particular implementation. So we're going to introduce you
to a framework called Flask. And that's going to
allow us, ultimately, to start web applications not
by running HTTP server today but literally running flask space run. So this framework literally
gives you a command, on your Mac, your PC, your
codespace once it's installed, that allows you to start
a web server by running flask run instead of HTTP server. So, again, last week, it was
all about static websites. This week, it's all about
dynamic websites instead. And, by framework, we really
generally mean-- not just using some third-party code
but third-party conventions that just some human or
humans decided is the way you will build your applications. Usually, this is just based on lessons
learned after making application after application. Humans around the internet
realized, you know what? We should probably standardize the names
of our files, the names of our folders, and how things are laid out. And so, even though this is
not the only way to do things, the way that Flask prescribes that
we programmers do things is this. We'll, starting today, always have
a Python program called app.py by convention, like in our folder. And we're going to have a folder
called templates, inside of which is any of the actual HTML, maybe
CSS, maybe JavaScript that we write. But you can actually put some
of that content elsewhere. And, in fact, you'll see
there's going to be generally two other files or folders you'll
see me create or me use today. One is called requirements.txt,
which is literally just a simple text file wherein you specify one per
line what third-party libraries you want to use. This file just makes it easy to
install those libraries with a command. And then, lastly, a static folder. And it's in this folder that you put
images or .css files or .js files, literally files that are meant to be
static that you might change them once in a while, but they're
not changing every day. And so you just want to isolate
them to a particular folder. Why is it this way? Eh, a bunch of humans decided
this feels like a clean solution. But reasonable people will disagree
and different frameworks lay things out differently. So, when using a framework for the first
time, you take a class or read a book or read the documentation. And it will essentially guide you to
how you should lay out your application. So let's go ahead and do exactly that
and make an application that quite simply and, by design, underwhelmingly
implements Hello, world. But, rather than do
this statically, let me do it in a way that starts
to use this framework so that, in the next version of it,
we can actually take user input and have it say not Hello,
world but maybe Hello, David, or Hello, Yulia or anyone else. All right, so let me go
over here to VS Code. And let me go ahead, and, initially,
let me start with the familiar. And let me go ahead and start by simply
creating the HTML page that I really want to show to my visitors
when they visit my application. So I'm going to go ahead and,
somewhat incorrectly, initially, but just to make a point,
I'm going to go ahead and do code index.html to open up a new tab. I'll hide my terminal window just
to give myself some more room. And then really fast I'm going to
type out some boilerplate HTML. So DOCTYPE html, just like last week,
open bracket html Lang equals en just to tell the VS Code that I'm-- to tell the web that I'm
largely using English here. In the head of my page, I'm going to
have, of course, the title of the page. And I'll keep it simple and
just say something like hello. But just so that this website actually
renders nicely on mobile devices, I'm going to use one of those
meta tags we talked briefly about last week whereby if I
say meta name equals viewport-- and viewport refers to just the big
rectangular region of your browser-- the content of this meta tag
is going to be initial scale equals 1 and width equals device width. I always have to copy-paste
this or look it up myself. But this line here
essentially tells the browser that, no matter how wide
the device is, whether it's a laptop or desktop or maybe a
vertical cell phone or tablet, size the viewport to that device. Otherwise, your website might
look super small on mobile devices if you don't use this tag to tell
the browser, take into account the actual device width rather
than shrinking the 12-point font to something that's
hard for folks to read. So, for now, I'm just going to
generally copy-paste that or type it out from my printout here. All right, beyond that, we
need the body of the page. We'll keep that simple. So body, and then, in
here, hello comma world. So that's it for my website thus far. It's static. Nothing about this is going to
incorporate my name or anyone else's. So I could technically use HTTP
server to serve up this web page, open it in a browser, and I
would see the actual contents. But let's instead create
a bit of work for us and sort of overengineer this problem
but to set the stage for actually taking in dynamic input
like a user's name. So let me go ahead and to do this. I'm going to go ahead and
open my terminal window again. I'm going to close the index.html file. I'm going to make a new
directory called templates, which, again, was the
default folder name I mentioned that this framework expects. And I'm going to move index.html into
that templates folder using mv, a Linux command. If you're more comfy, you can open up
the file, the File Explorer at right. You'll see, in advance,
I downloaded a directory. I'll occasionally borrow
content from today called src9, but there is my templates folder. And you could click in
the GUI in order to do what I just did at the command line. All right, and, after this, let's
go ahead and create one other file, app.py. And, in app.py, let me
go ahead now and do this. I'm going to import some functions that
come with this framework called Flask. So I'm going to say from flask, in
lowercase, import Flask, capital F, and also a function called render
template and an object called request. Now, how to do this? You literally read the documentation,
or you listen to someone like me tell you to begin
your program this way. In the Flask framework comes
three pieces of functionality that are just useful to incorporate into
my own program as we're about to see. Here's the line of code via
which I can tell this framework to treat my file, app.py,
as indeed a web application. I create a variable,
typically called app. I set that equal to the
return value of calling this Flask function and pass
in it, somewhat weirdly, the name of this file. So this is the only weird thing for
now in that we haven't used this much, if at all. __name__ is a special
variable in Python that literally refers to the current file's name,
no matter what file name you gave it. So it's a nice way of
referring to yourself without manually typing the file name,
which might change down the line. And then, lastly, I'm going to do this. And this is one other piece
of new syntax for now. I'm going to use an @ and say app.route. And then in quotes, as an
argument to this route function, I'm going to specify the route for
which I'm implementing some code / being the default, by convention. I'm going to define immediately
below that a function that I can technically
call anything I want, but I'm going to get in the habit
of using reasonable defaults. So I'm going to call this
function index by default. But that's not a hard requirement. And then, inside of this
function, I'm simply going to return this, return
"hello, world", quote, unquote. And that's it. This I now claim is a beginning
of an actual web application. It looks a little magical or
cryptic as to what's going on. But, per the jargon I introduced
earlier, this function here app.route is defining a route for
this application that implies that whenever
a human visit slash on this application,
what should happen is this function index should get called. And that function's purpose
in life, at the moment, is just to return, quote, unquote,
"hello, world", and that's it. So let me go ahead and do this. Let me open my terminal
and just to keep everything clean because we're going to
have a bunch of applications today in the works. I'm going to create one
other folder called hello. And I'm going to move app.py and
templates into that hello folder. So if I now type ls in
my own personal account, I've got that folder hello and also
src9, which I brought with me today. So if I now cd into
hello and type ls again, I'll see the two things we
just created together, app.py and the templates folder. And if I go one step further
in ls templates itself, I should see, of course, index.html. All right, so a lot of steps to
go through just to get started. But you'll see that this is
fairly boilerplate eventually. I'm not going to run
an HTTP server, but I am going to run flask run, which will
turn this app.py into a working web application. The output after I hit Enter is
going to look a little more cryptic. It's going to warn me this
is a development server. You should not use that same
command in the real world. You should actually configure Flask
a little differently if you're going to use it in the real world. But it does show me the random
URL that GitHub created for me. And I'm going to go
ahead and open this URL. It's going to open in a new tab. And, voila, that is my web application. Completely underwhelming,
but you'll notice that, even though Chrome
is hiding this, this is the equivalent of my having visited
at the end of this URL simply a slash. All right, if I zoom out here, though,
and maybe right-click or Control-click, and I choose View page source-- recall, this is available
in most every browser-- you'll see that this isn't actually
HTML because, at the moment, I'm literally just returning,
quote, unquote, "hello, world". So, yes, it's text. It's being rendered as a web page. But it's not technically a
web page that has valid HTML. So what I'm going to do here is this. I'm going to go back into VS Code. I'm going to open a second
terminal by clicking the plus icon toward the
bottom right of my screen, just so I can keep the server running. But-- actually, nope, let
me go ahead and do this. Let me kill this terminal. Let me actually-- oops,
I killed the wrong one. Let me instead go into
my hello directory again. Let me open up app.py. And this time, instead of saying
hello, world, let me do this. I want to return the
contents of index.html, which is that whole file I created,
but I can't just specify its file name. But I can do this. I can call a function
called render_template, which comes with Flask. And its purpose in life is to
go get that file from a folder called templates, open
it up and then send all of those bytes,
all of those characters to the user's actual browser. So let me go ahead and do this. Let me open my terminal again. Let me do flask run inside
of this same folder hello. I'm now going to go back to
my tab here, click Reload, and nothing appears to have changed. But if I right-click and choose View
source this time after reloading, now you'll see all of
the HTML that I composed. All right, so this has
taken way more steps to do what we achieved by just
running HTTP server last week. But here's where now things can
get a little interesting, whereby now that we have this
framework laid out, we can actually introduce
other features of the framework to make things more dynamic. So, for instance, what
I'm going to do is this. I'm going to now introduce
a feature of that variable that I also imported called request,
which refers to any HTTP request. And it turns out,
there's a property inside of there called args,
which is actually going to be a dictionary of all
of the key value pairs that the human might've
provided via the URL. So I don't have to figure out, how do I
find the thing after the question mark? I don't have to worry about
parsing the ampersands. Flask does all of that
for me and just hands me anything after the URL in
a Python dictionary instead. So let me do this. Let me go back to VS Code here. Let me go ahead and hide the terminal. But, in app.py, let me go ahead and
make a relatively simple change. Let me go ahead and do this. Let me go ahead and open up in my hello
folder, let me open up index.html. And let me go ahead and get rid of
world and just put a placeholder here. Using curly brackets, two of
them, on the left and right, I'm going to go ahead and
plug in a variable like name. So here's now where I'm treating
index.html not as literally an HTML page anymore, but more as
a template in the literal sense. So a template is kind of
like a blueprint whereby you can construct most of what
you want the human to see but then leave little placeholders
in, a la a blueprint where you can fill in certain blanks. The double curly quotes
here is actually a feature of technically another
language-- it's not a programming language-- called Jinja. But Jinja is simply a language
that a bunch of other humans came up with that
standardizes what syntax you can use for these placeholders
and some other features as well. So this is going to happen more
and more as you progress more in programming and CS. It's not going to be as simple as, oh,
I'm implementing something in Python. Or, oh, I'm implementing something
in C. It's generally going to be, oh, I'm implementing something
with this full stack of software, including HTML, CSS, Python, some
SQL, some Jinja, and so forth. So into your vocabulary is now going to
generally come a list of technologies that you're using when
solving some problem, no longer individual languages. So, by this, I just mean this. The Flask framework took a look
around the internet and saw, OK, the humans at the
Jinja group came up with a nice simple syntax
for putting placeholders in. Let's support that syntax in Flask. So it's sort of one framework
collaborating with another. So if I go back to app.py now, how do
I actually pass from my application to that template whatever
the human has typed in? Well, it turns out I can
go ahead and do this. Let me go ahead and in my
index function, which, again, is what's going to get called anytime
someone visits that slash route, I'm going to go ahead and
create a variable called name. I'm going to set it
equal to requests.args. And then I'm going to go ahead and
say, how about, quote, unquote, "name". I claimed, a moment ago, that
args is a dictionary that just comes automatically
with Flask and whenever a human makes a request
to the server, and it puts in that dictionary all of
the key value pairs from the URL. And the last thing I'm
going to do here is this. I'm going to actually say--
and, actually, just let me make this more explicit. Let me call this placeholder
literally placeholder. And what I'm going to do
now is, in render template, I'm going to take advantage of one other
feature that comes with this function. It can take one or more arguments. And if you pass in more,
you can specify variables that you want the function
to have access to. So I can literally do something
like this, placeholder equals name. So recall that Python supports
named parameters, which just means that you can pass in
multiple arguments to a function. But you can specify them by name. So I am calling one of
these arguments placeholder. And I'm setting the
value of that argument equal to name, which
itself is a variable that I defined just a moment ago. So, now, what's going to happen? Well, let me actually
go back to my VS Code. I'm going to go ahead and
run Flask as I did before. The URL is not going
to change in this case, I'm going to go back to my other tab. I'm going to go ahead now and change
the URL manually-- let me zoom in here-- to be /?name=David. I'm not going to hit Enter yet. Let me zoom out. But, when I do zoom out here, I think
we should see now, hello, David. So here we go. Enter. And, voila, we see hello, David. But, more interestingly,
if I view source here by right-clicking or
Control-clicking on the page or opening developer tools and so forth
and go to View page source, it appears that what the
server sent to my browser is literally a web page
that says hello, David. There is no more placeholder. There is no more curly braces. Literally, the content that came
from the server is that string. And so this is the
distinction now between being a static application versus dynamic. What I wrote statically was
literally this index.html file. But what got served
dynamically is that file plus the substitution or interpolation
of that placeholder variable. So we have the beginnings, it
would seem, of a web application. And think back to Google. Google is essentially implemented
the same way, /search?q=cats. So they're doing more with the key value
pair than just spitting it back out, but we have the beginnings
now of a dynamic application. Any questions on any of this
code or this framework thus far? Any questions thus far. No, all right, well,
let's see what happens if I don't cooperate like a human. Let me actually go ahead and get rid
of that parameter, hit Enter again, and I actually now
got an HTTP 400 error. So this actually seems bad. And it's a little subtle, but if I zoom
into the tab here, it indeed says 400. That's one of the HTTP status
codes that you generally shouldn't see unless something goes wrong. 200 meant OK. 404 meant file not found. 400 means that something went wrong. I guess I didn't pass in a
name as I was supposed to. But that's only because I was sort
of blindly expecting this placeholder to exist. So let me be a little smarter and code a
little more defensively now as follows. Let me say this. How about if there is a name
parameter in the requests arguments, then go ahead and create
a variable called name, set it equal to request.args,
quote, unquote, "name". So treat it as a
dictionary with that key. Else, if there is no
name in the URL, let's just default this variable to
being something sensible like, quote, unquote, "world". So, now, let's proactively, using
some sort of "week one style" conditional code, albeit in
Python now from week six, let's just check is name in
the URL, if so grab its value. Otherwise, default to world instead. So I'm still going to
leave this code as is, passing in the placeholder for this
template to get plugged in here. But, now, if I go back to the browser
and I just reload without passing in name=David or anything else,
now we have a sensible default. And if I go back to my View source tab
and reload, I'll see that I have hello, world in this case. However, if I go back up to that URL
and do /?name=David, now we have David. If I change the URL to be
name=Carter, now we have Carter. So, indeed, we do have the beginnings
of something that's more dynamic. Of course, this is a
little tedious to have to write out this if and this else. There's ways to condense this code to
be a little tighter and a little faster to actually implement. And, in fact, let me go
ahead and propose that. We just do this. Instead of treating
args as a dictionary as we did with square brackets--
which can cause problems if that key does not exist. In fact, let me go back to that version. Let me undo, undo, undo,
whereby I'm just blindly going into request.args to get name. In fact, instead of just blindly
indexing into this dictionary called args, which turns
out we can do this instead. Let me go ahead and
say request.args.get, which is a function that
comes with a dictionary, and I can specify the name of
the key that I want to get. And, by default, if there is no
key called name in a dictionary, you're going to get back
a default value of none, which is kind of like
Python's equivalent of null, but it's none, capital N, in Python. But if you want to give
it a different default, it's handy to know
that this get function, which you can use with
dictionaries in general can take a second argument,
which will be the default value that you do get back if, in
fact, there is no such key called name. So what this means is I can actually
now keep all of the code the same, but it's a little tighter. There's no if or else. I can go back to my other
browser window, click reload, and it still works for Carter. But if I get rid of that,
I can now have hello, world still working just as before. And just to add one more potential
point of confusion to the mix, it's a little dopey to say placeholder
literally in your template, especially if you're going to be using
multiple pairs of curly braces for one variable or another or another. So better style would
be to actually call the variable what makes sense for
what it is you're plugging in. So hello, name, with the
name in curly braces. This is-- I show this, though,
because it gets a little confusing if your variable's called
name, and that's still going to be the value you pass in. You're going to very often
see in the world of Flask this convention, where you literally
write something equals something where the names there are exactly the same. And the only thing to keep in
mind here is that the name-- this is the name of this parameter. This is the value of this parameter. The fact that everything seems
to be called name in this program is because these technical terms
are colliding with the English word that you and I know as name for my
name, Carter's name, and so forth. But get past that only because it
will be very common to literally see something equals something,
where just for visual convenience the variable's name is exactly the
value that you want to pass in. So that too here is conventional. All right, a little cryptic. But common boilerplate that you'll
start to see again and again. Any questions now about this? No? OK, so let's make things a little
more interesting and representative of a real-world app. Let me propose that, now,
we go about implementing maybe a second template altogether. In fact, let me go ahead and do this. It'd be nice if the human doesn't need
to be technologically savvy enough to say that, oh, if you want
to be greeted by this website, you have to literally change the
URL yourself, type in name equals-- no one does that. That's just not how the web
works in terms of user interface, but that is how browsers
and servers do work. But, of course, on the web,
we almost always use forms. So let me go ahead and do this. Let me go into index.html, and let
me get rid of just this hello, body. And, instead, let me
actually create an HTML form. This form is going to use the
get method if only so that we can see what's going on inside of the URL. This form is going to have an input
where I'm going to turn autocomplete off, just like last week. I'm going to do auto focus just to move
the cursor there nicely by default. Somewhat confusingly, I'm going
to give this input a name of name because I want Carter's name, my
name, or someone else's human name. But I'm going to give
it placeholder text of, quote, unquote, "Name",
capitalized, just to be grammatically clear as to
what we're prompting the user for. And the type of this
field is going to be text. Although, that's the implied
default if I don't give it a type. I'm going to lastly have a button
in this form, the type of which is submit so that the browser
knows to submit this form. And the label I'm going to
put on this button is Greet. So I'm going to type in
my name, click Greet. And I want to see hello,
David or the like. But I need to specify
the action for this form. And recall that, when we implemented
"Google", quote, unquote, we did a little something like this. Https://www.google.com/search,
well, we're not going to punt to Google today. We are implementing
our own applications. So if this were a search application,
I could literally have an action of /search, but let's do something
a little more semantically sensible. Let's create our brand
new route called /greet. This is not the name of a folder. It's not the name of a file. It's more generically a path or a route
that is now up to me to implement. If I go back, though, to this
application and reload this page, notice that I have the beginnings
of a more user-friendly form that's asking me for this name. However, if I do type in
David and click Greet-- I'll zoom in just a moment-- notice that the URL does change to
/greet?name=David just like google.com works. But, of course, we're getting
a 404 not found because I haven't implemented this route yet. So let me zoom out. Let me go back to VS Code. Let me open app.py and make a
little bit of a change there. Let me make a little bit of a
change and say this in app.py. In app.py, instead of just getting
the user's name and this default, let's simplify the index route and
just have it sole purpose in life be to render index.html, the
thing that contains the form. I'm going to now, though, create a
second route, so app.route, quote, unquote, "/greet". And I could call this
route anything I want. But greet seems sensible. I'm going to call the function
below it anything I want. But, just to keep myself sane, I'm going
to call it the same thing as the route even though that's
not strictly required. And then, in this route, I'm
going to go ahead and do this. I'm going to create a variable called
name, set it equal to request.args.get, quote, unquote, "name", then,
quote, unquote, "world", so the exact same line as before. And then I'm going to return
render_template, which is the function that comes with Flask. I'm going to specify this time, though,
render a template called greet.html-- which doesn't exist yet, but that's not
going to be a hard problem to solve-- and pass in that variable. So the last thing I need to do is this. And I'm going to cheat
and copy and, in a moment, paste the contents of index.html into a
new file called greet.html as follows. Let me open up VS Code
here in my other terminal. Let me go ahead and write
code templates/greet.html. Notice that I'm making sure to put
the new file in the templates folder. Or I could cd into it and
then run the code command. That's going to give me a new file. I'm going to hide my
terminal, paste that code, and I'm going to get rid of the form. And, frankly, I should've just
copied and pasted this earlier because the only thing I'm going
to put in greet.html is hello and then, in curly braces,
my placeholder, which we started to call name a moment ago. So, to recap if I go into my
terminal again, if I type ls, I've still got app.py. I've still got templates. But if I look inside of templates now,
I've got two templates-- index.html and greet.html. index.html is the thing
you see when you visit my website. greet.html is the thing you see when
you submit that form, it would seem. So, indeed, if I go back to
my browser and hit back-- so I get back to that form. For good measure, I'm
going to reload because I want to make sure I have the
latest version of everything. I'm going to now try
typing my name David. I'll zoom in. You'll see that the URL will again
change to /greet with the question mark and my name. But, hopefully, we now
indeed don't see a 404 because the new route actually exists. And if I zoom out,
right-click or Control-click and go to View page
source, you still see what appears to be a HTML file just
for me even though it was dynamically generated instead. All right, if you're on board that
this seems to be a correct application insofar as it does
what I wanted it to do, let's critique the design as
we've been in the habit of doing. I've now got three files, app.py,
greet.html, and index.html. What might you not like about the
design of this web application even if you've never
done this stuff before? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, greet and
index.html have the same contents, except for that one or few
lines in the middle of the body. I mean, I literally copied
and pasted, which, generally, even though I do it sometimes
in class to save time, if I end up with the
same code in my files, that's probably cutting some
corner and not the best design. Why? Well, what if I want to go in and
change the title of this application from hello to something else. It's not a huge deal. But I have to change it in two places. What if I've got some
pretty CSS that I've added? And so I've got some
CSS up here in one file. I need to copy it into another
file, change it both places. Generally, having duplication
of anything is bad design. So it turns out, there
is a way to solve this. And this is one of the
features that you really start to get from a web framework,
be it Flask or anything else. You get solutions to these problems. So what I'm going to do now is this. I'm going to create a third and
final file for this application by starting by copying
what I have already. Let me go back to my
terminal window here. And let me create a third template
in the templates folder called layout.html. It doesn't have to be called
that, but that's the convention. So I'll always do that here. And, when I create this
file and hide my terminal, I'm going to go ahead and
copy-paste all of that content. But I'm going to go inside of the
body of this file, called layout.html, which is otherwise identical
across all of my files. And what I'm going to do is
use slightly weird syntax. This is more of that Jinja syntax
that some other humans came up with years ago. And I'm going to do this. Single curly brace and a percent sign. I'm going to specify the word block and
then any name I want for this block. And, by convention, I'm
going to keep it simple and just use the exact same
thing as the name of the tag that I'm inside, so block body. And then I'm going to put a percent
sign just before the close curly brace. I don't need anything inside of this. So this too's going to
look weird at first glance. But it's a convention. I'm going to do another curly
brace with the percent sign and then literally the
word endblock, no space. And I'm going to open and
close what isn't an HTML tag. It's a Jinja tag, if you will. So, again, you see yet more
evidence of reasonable humans in the world kind of
disagreeing on syntax or at least using syntax
that's similar in spirit but doesn't clash with the syntax, the
angled brackets that HTML already uses. Long story short, this
is a way of specifying that you want a placeholder, not
just for a single variable's value but for a whole block of code. Maybe it's simply a sentence. Maybe it's a whole-- a
web form element or more. This is a placeholder
now for a block of code. And the way I can do this-- or
the way I can use this template-- and this is where template now
is getting all the more literal in the sense of what templates do. I'm going to go ahead and do this. I'm going to go into my two
other files, like greet.html. The only line that's different
in this file vis-a-vis index.html is which line number? 9 is the only line that is unique. So what I'm going to do is this. I'm going to highlight that and copy it. And then I'm going to delete
everything else in this file because it's just redundant. Everything I need is in layout.html. At the top of this file, I'm going to
use another curly brace and a percent sign, but I'm going to
use a special keyword that comes with Jinja called extends. And I'm going to specify in quotes
here the name of the template that I want to extend, so to speak. So this is an example of what's in
computer science known as inheritance. I want to take everything from
that layout and inherit from it all of its lines but
plug in some of my own, sort of from a
parent-child relationship. Inside of this file,
now, I'm going to specify that the custom body that I want is
this block body just like before. And then, down here, I'm going
to preemptively say endblock just to finish my thought in advance. And then, inside of this block body, I'm
going to simply paste that line of code that I Stole from the original version. So I'll concede that
this is pretty ugly. I've added three cryptic-looking
lines, all of which relate to Jinja
templating, again, syntax that humans invented to give you
the ability to write templates, or blueprints. But the point is that this
single line, now line 5, is going to get plugged into that
template, called layout.html, wherever that body block is meant to go. Lastly, I'm going to
go ahead and do this. The only lines in index.html that are
unique are these here, 9, 10, 11, 12. So I'm going to highlight those
and delete everything else. And then I'm going to do the exact same
thing, extends layout.html at the top of this file, then,
below that, block body. Inside of the block body, I'm going
to then say endblock at the end. And, in the middle of that, I'm
going to paste those lines of code. Just stylistically, I'm going to indent
them just so I'm super clear visually on what is inside of what. But that's it. So ugly? Yes, but, as soon as your web
pages get longer and longer, this ends up being a drop in the bucket. It's three ugly-looking lines
relative to a lot of HTML that you might be plugging in and
customizing for your application. So now index.html looks like this. greet.html looks like this. And the only way they differ is the
actual contents of that block of code. layout.html is the main
blueprint that's going to govern what the whole website looks like. I can change the title in one place. I can add some pretty CSS
in one place and so forth. It's going to apply to
each of those files. And, now, somewhat
underwhelmingly perhaps, if I go back to this
application and I click reload, nothing is different
because it still just works. But I've made arguably a
better design because now, when I change things to Carter
here or I get rid of it altogether and just visit the default-- rather, if I just visit slash
there, I'll get the form. I've at least handled
the situation where-- I've eliminated the situation
where I've just copied and pasted the same boilerplate code. So odds are someone like Google
is doing something like this. It's probably fancier
certainly than this example. But any time you search for something on
Google, generally, the top of the page looks the same. Maybe the bottom of the
page looks the same. There's maybe some
ads always at the top. And then there's 10 search results. So, probably, what they've done is they
have some template that looks roughly like this with all of
the boilerplate stuff that they want every human to see
on every page of search results. And then they're just somehow
customizing the block-- a block of code somewhere
there in the middle. All right, questions on any of
this actual templating technique? Anything at all? All right, how about another
question about design? If I go back to this URL here and
I search for something like David, it's not that big a deal
that it ends up in the URL. And, in fact, what's nice about
HTTP parameters ending up in the URL is that URLs are therefore stateful. If you copy this URL and
paste it into an email, assuming the web server is still up and
running at that URL, it will just work. And the human to whom
you send that link, they will see David or
Carter or whatever name's actually in that form, which
may be as useful behavior. Not so much for this
application, but imagine now that you want to send someone a
link of Google search results. It's a good thing that Google puts
q=cats or dogs or birds or whatever in the URL because then
the URL itself is stateful. What you see is what
the recipient will see because all of the inputs of the
server that's requisite is in that URL. But suppose that this
form field, if I go back, wasn't asking for my name but my credit
card number or my password up here. That should start to rub
you the wrong way because it feels like no good will come from
exposing private information in the URL because if you have a nosy
sibling look over your shoulder. There it is in your search history. A roommate goes through your
autocomplete and finds the data there. Or if you do, for whatever
reason, copy-paste it, you're accidentally including
private information in these URLs. So I said last week that there is
an alternative to sending things in the URL and that
alternative is to use something that's not called get but
a verb in the world of HTTP that's called post instead. And it's actually a
relatively simple change. If I go into index.html, I can simply
change the method from get to post. Get is the default.
Post is an alternative. Even though, in some contexts,
you'll see capitals, in HTML, it should be lowercase, another example
of left hand not talking to right. But, in this case, if I go now
to my other tab with the browser, reload the page because I want to
get the latest version of the form, if I now type David-- and I'll zoom in-- before hitting
Enter, if you watch the URL now, you should not see that
?name=David is up there, nor would be your credit
card or your password. Unfortunately, we're
seeing another HTTP status code we haven't seen yet,
405, Method Not Allowed. Well, why is that? That's because now that I
fully control the web server, I need to tell the web server that
I do want to support not just get which is the default but post as well. The method the user is
using is not supported. So this is an easy fix
even though it's going to look a little cryptic at first. If you want your greet method to
support not just get but post, you can specify another
argument to this route function. So the default is literally this,
methods= and then in square brackets, quote, unquote, "GET". So what is this? Methods is apparently a named argument
being passed into the route function. I claim its default value is this. What do the square brackets
indicate in Python? Not a dictionary. Square brackets. A list, so it's a list of
strings or strs in this case. This is the implicit default. So you don't have to type this. It's just what works out
of the box automatically. But if you want to change
this from get to post, you have to include methods
equals a list of the methods that you do want to support. For another time, there's
other HTTP methods. There's delete. There's put. Those are the two biggies
that you might use as well. Those are generally not supported
as easily in the world of browsers, but get and post certainly are. If you wanted to support
both for whatever reason, you can literally have a comma
separated list of those methods instead. But we don't really need
both for privacy's sake. I claim I'm only going to use post now. So now if I go back to
my other tab, go back to the form, reload to make
sure everything is as expected, and now type in David and zoom in,
you won't see my name in the URL. But you will-- or you won't see
it-- oh, good, not intended. But nor will you see it even
in the body of the web page. So it's super secure. Why? I screwed up, but why? Yes. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yes, so good intuition. Even if you knew that before, you
might think through rationally, how might this be-- why might this be behaving this way? Well, if I go into app.py, it seems
that if world is the value of the name placeholder, well, it must be
the case that there is no name key in request.args in this case. However, there's an
alternative to request.args, and it's called request.form. This is another example of visible and
hidden being opposites of one another, request.args and
request.form, at least for me, are not obvious mappings to
GET and POST, respectively. But that's what the Flask folks did. And so the simple fix now,
if I go back to VS Code, is to change request.args
to request.form if you want to use post instead of get. This is a weird misnomer because
they're both coming from forms, whether you're using GET or POST. But this is what some folks decided. So let me go back to my browser, go
back to the original form, reload to make sure I get the fresh
HTML, type in my name now, David, zoom in, and click Greet. And, this time, you won't
see my name in the URL, but you should see it
in the body of the page. So we've achieved some form
of privacy, if you will. Better applied to things like credit
card numbers, passwords, and the like. Phew, other questions? On any of this thus far? Anything yet? No, all right, yes, in the middle. AUDIENCE: [INAUDIBLE] post and
get, the request [INAUDIBLE]?? DAVID J. MALAN: A good question. To repeat if you were
supporting both GET and POST, should we have a second line
that's also checking request.args? Yes, if you were. I, though, decided, at the last
minute, only to support POST not GET. So I don't have to bother with that. But your question's a perfect segue to a
final example of this Hello application where you can actually consolidate
different types of functionality into individual routes. Why? Well, at the moment, this
application is super simple. It's literally got one form
and then one resulting page. But it's implemented, therefore, with
a pair of routes, a pair of functions. No big deal for small applications. But if you imagine a more
complicated application, be it Google or anything else
that has many different web forms on different pages,
it's a little annoying if every form needs to separate
routes if only because you now have to keep track of literally
twice as many functions. Your colleagues, your
teaching fellow needs to know which one is related to which. So there's something
to be said design-wise about consolidating related
functionality into one single route so that everything is together. Well, we can achieve that
relatively simply as follows. So let me go ahead and completely
eliminate this greet route and simply have everything exist in the /route. And I'm going to go ahead and highlight
and cut these lines out of there altogether. But if I want my single /route
to support multiple methods, I indeed need to use methods equals and
then, in square brackets, GET and POST. Order doesn't matter. But I'll keep them
alphabetical in this case. Inside of my index route, I need to
in advance is the user visiting me via GET or POST? Because if it's via GET, I
want them to see the form. If it's via POST, I want to
process, the form, that is, do something with the user's input. So it turns out it's relatively simple. If request.method equals equals
"POST", then I can do the following. So you can literally check the request
object, which comes with Flask, to figure out, was the word GET or the
word POST in that virtual envelope? And, depending on the answer,
you can do something like this. I can paste those lines
from earlier, whereby I get the variable name from request.form. And then I render the template
greet.html, passing in that name. Otherwise, you know what? I could just do else,
return the template itself. So if the method is POST, go ahead and
process the form just as we did before. Else, go ahead and just render the
index template which contains the form. Strictly speaking, I
don't even need the else. I can get rid of that, just to
tighten this up a little bit, and unindent my last line. Why? Because recall that, from C, from
Python, as soon as you return a value, nothing in that function is
going to get executed thereafter. So you might as well kind
of tighten up the code so that you don't bother adding
undue indentation if not needed. So notice, now, if I go back
to my browser, reload here, it's not going to work yet. But let's see if you
can diagnose the issue. If I type in David here
and click Greet, now I'm back to getting a 404 but
for different reasons. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Good, I haven't
changed-- not the method. But I haven't changed the
action in the form itself. So if I go back to VS Code here
and I go into the web forms, the HTML, POST is still fine. But there is no /greet route anymore. So I actually can just specify slash. Or it turns out if you omit that
altogether, the form will assume that you want to submit it to
the very route from which you came so that is fine as well. I'm going to go ahead now and go
back to that other tab and go back. I'm going to reload the page. And, just for good
measure, this time, I'm going to Control-click or
right-click View page source. And, here, yep, the
action has indeed updated. So I think I fixed the bug. Now if I type in David and click greet,
we're back in business with it working. So notice that this still
allows me the convenience of having two separate templates,
one for the form which shows the-- which collects the user input
and one for the actual greeting which displays the user input. So I'd argue that it still makes
sense to keep those separate. But I can avoid bloating
my app.py by having two methods for every single feature
that I might want to implement. Now, there is still a bug
in this implementation even though it's a little bit subtle. So recall that, previously, we
introduced this default value of world just in case the form doesn't
actually contain the word world as might've happened if I didn't-- if
I [INAUDIBLE] into the URL that I was requesting manually as I did before. But it turns out that if you're
using an actual form and not, of course, expecting the human to
type anything into the URL bar, which no human would do, it turns
out that the browser is still going to submit a name parameter
even if its value is blank, that is, empty, the
so-called empty string. And so even if it's
the empty string, it's still going to be
considered to be a value and, therefore, not worthy of having
the default value of world plugged in. In other words, if I open up my
terminal window here, rerun flask run, and go back over to my browser, and
load this example, if I type in David, as before, I'm going to be
greeted with hello, David. But if I try this again and don't
provide an actual name but just click Greet, it turns out the
name parameter's still going to be submitted to the server,
in which case request.form.get is not going to rely on the default value
but rather that empty string value. And so we see what appears to be a bit
of an aesthetic bug hello, nothing. So how can we go about fixing this? Well, perhaps the simplest way is
to no longer rely on this default value here inside of app.py. So, in fact, let me go ahead and
delete that default value altogether and pass name in as the variable it
still is into greet.html, our template. But, in greet.html,
let's add a bit of logic there whereby we conditionally display
the name if and only if it's not empty. In other words, before I output blindly
name inside of these curly braces, let me borrow some syntax from Python
and actually use, within my Jinja template, a conditional like this. Open curly brace and then a
percent sign because, this time, I want logic, not just
interpolation of a variable. And I'm going to say if name. And then I'm going to do another
percent sign and a single curly brace. And then, after that, I'm going
to still use my variable name name inside of two curly braces. But, after that, I'm going to do again
a single curly brace, a single percent sign, and then I'm going to say else
followed by one more percent sign. And then, after that, I'm going to go
ahead and actually put my default value world and then close this if
conditional with a single curly brace, a single percent sign, and endif. And then I'm going to go ahead
and close that tag there. So, in Jinja, it turns out that we
can use it not only to plug in values. We can also do a bit of
lightweight conditional logic using an if and an else and an
endif in this case, which isn't quite like Python-- indeed
the endif is a little bit different. But this is particular
now to the Jinja template. And I've done it all on
one line just because this is a fairly bit-sized conditional,
either print out the name or print out world. Otherwise, I could actually put these
template tags on their own lines in order to spread
things out all the more. We'll see now, before
long, that there's actually some other control flow capabilities
of Jinja including loops and more. But, for now, this is a nice way to
solve that one problem because now, when I go back into my application
and I go back to the form and type in D-A-V-I-D, it's still going to
work as expected, hello, David. But if I go back one final time,
type nothing in thereby sending an empty value to the server and click
Greet here to demonstrate as much, now we do, in fact, see hello, world. All right, any questions on this
final example of just saying hello? From those basics come pretty much
all of dynamic web applications today. No? All right, so if you'll indulge me,
here's an actual web application that I made back in the day. So, when I was a sophomore, I
think I was not very athletic, so I didn't so much do freshman
intramural sports as I did run them with a roommate of mine. So we were sophomores in Mather House. He was the athlete. I was the aspiring computer scientist. And so this was actually a screenshot
of the very first web application I ever made. And this will sound old too. But, back in my day,
freshman year, when we registered for Frosh IMs, or
Freshman Intramural Sports, you would literally
walk across Harvard yard to Wigglesworth, where a certain proctor
or RA lived who was running Frosh IMs. And you would literally
slide a sheet of paper under the door with your name
on it and your choice of sports that you want to register for. So that was the state
of the art in 1995. This was ripe for disruption
as people would now say. And, once I actually took CS50 in
the fall of 1996, which did not teach, funny enough, web
programming at the time, I think I spent that winter
or spring figuring out how to do stuff with web programming,
not using C and not even using Python. At the time, I was using a language
called Perl, which is still with us but not as popular as
it was back in the day. But what you're seeing here
is a hideous screenshot of what the user interface was. This was me learning how to repeat
background in images infinitely, no matter how big the page was. Back in the day, there was no
CSS, I think, even at the time. So every one of these menu
options was actually an image. And even though-- this is a
screenshot, so it's not animated. If you would hover over
any of these words, what I would do using JavaScript,
which did exist in an early form, was just change the image from
a blue image to a red image, creating the illusion of the trickery
we did last week with text decoration, as you might recall in hover. So the web's come a long way. But this is still representative,
amazingly, some 20 years later of how web applications still work. I used a different language. I used a different backend
for my data or database. But everything I did then we
will now do effectively today and beyond because the
principles have not changed. It's all based ultimately
on HTTP and all of the stuff we discussed thus far this
past week and now this. So let's go ahead and make the
beginnings of this website, though, perhaps without as many of
the hideous images underneath it. In my VS Code, I'm going to go ahead
and close all of my prior tabs. I'll open up my terminal, and
I'll hit Control-c to exit out of Flask just like you can hit Control-c
to exit out of the HTTP server. I'm going to go ahead and hit cd
to go back to my main workspace. And I'm going to create a new
folder with mkdir called froshims so that all of my new application
is inside of this folder. I'm going to cd into froshims. And let's go ahead and make
a very simple application that essentially pretends to let
first years register for a sport. So I'm going to need to do
a bit of typing up front. But I'll do the first one from scratch. And then we'll start just
evolving that same example. Let me go ahead and do this. Let me go ahead and-- actually, we'll do this. We'll cut one corner. I'm going to go ahead and copy, from my
hello example, app.py into this folder. I'm going to go ahead and
copy from my hello examples templates my layout into this folder. I'm going to create a new
folder called templates. I'm going to move that
copied layout into templates so that, at this point in the story,
if I clear my screen and type ls, I've got the beginnings
of a web application, even though it's specific
to just saying hello. But I'm going to go ahead
and into the templates folder and go into layout.html. Let's just change this ever so
slightly to say froshims as the title just so we know we're looking
at the right application. And, now, let me go ahead and
create a new file called how about index.html inside
of templates that, just as before, is going to extend that
there template, so extends layout.html. Inside of here, I'm going to
say block body just as before. Preemptively going to say endblock. And then, inside of
here, I'm going to make the beginnings of a super simple
web page for first-year intramural. So I'm going to use an h1 tag
that's sort of big and bold that just says register at the top of
the page sort of like a title. Below that, I'm going to have a form. The action of this form I'm going
to say proactively is going to say to /register. So that's a to do. We're going to have to go
implement a register route. The method I'm going to use is
post just for privacy's sake so that if roommates are
sharing the same computer, they don't see, in the autocomplete,
who's registered for what. Inside of that form, I'm going to
have a single input first where autocomplete is off. Autofocus is on. The name of this field
will be name because I want to ask the humans for their human name. The placeholder, just
to be self-describing, is going to be, quote, unquote,
"Name", capital N grammatically. And then, lastly, the type of this
field, though it's the default, is text. So, so far, this is actually pretty
darn similar to the hello example soliciting someone's name. But now I want to maybe
implement a dropdown menu via which you can select a sport. And, back in the day, I think
the first version of froshims, students could only register
for basketball, soccer, and ultimate Frisbee. So those were three of the fall sports. So let me do this. It's a little weirdly named,
but a dropdown menu in HTML is called a select menu because
you select something from it. The name of this input, which is really
what it is, is going to be sport. Though, I could call the
input anything I want. And, inside of this select element,
I'm going to have a few options. I'm going to have one where the
option is how about basketball? Another option, the
value of which is soccer. And, lastly, a third option, the
value of which is ultimate Frisbee. So just those three sports. But suffice it to say
we could add even more. And then, outside of
this select menu, I'm going to have a button just like
the hello example, the type of which is submit, just to be super
explicit even though that's not strictly necessary. But it's another attribute
you'll see in the wild. And then the name on the value
of this button will be register. So it's clear that
you're not being greeted, but you're actually
registering for sports. Now, we're not quite good to go yet,
but let me go into VS code's terminal again. Let me open up app.py and
close my terminal again. And let's just whittle this
down to something super simple. I don't want to get
overwhelmed just yet. I don't want to support even POST. So let's just whittle this
down to the essence of this. So I can do a quick check
mentally and make sure now, when I run flask, that I'm
serving up that registration form. So, in my terminal, I'm going to
run flask run in my froshims folder. So far, so good. It's going to be by default the same
URL unless I've rebuilt or created a brand new codespace. So let me go back to my other
tab and reload that URL. And, OK, we've got the beginnings
of a more interesting form now. So it's got place for my name. It's got a dropdown
for the three sports. So let's see what happens,
D-A-V-I-D. We'll say soccer. And, when I click Register, just
to be clear, what route will I find myself at per my URL? Slash. What was it to be? If I go back into my index. /register. But what error will I see
presumably at this point in time, given that app.py has only been
implemented to this extent? So probably 404 because
the route won't be found. So if I click Register, I
indeed end up at /register. But if I zoom in up top
here, 404 not found. All right, so it's the
beginnings of an application. But I've not-- I've implemented
the front end, so to speak, the user interface but not the back
end, the business logic that actually does something with the user input. But a couple of enhancements here. But these are largely niceties in HTML. It's a little bad user
experience that by default you're registering for basketball. I mean, that's fine. But, arguably, you're biasing people
toward registering for basketball. Or they might not realize that they're
registering for basketball because they didn't explicitly choose a sport. So having a random, an
arbitrary default that just happens to be the
first word alphabetically is a little weak when
it comes to design. So there's different ways to fix this. But one way is to do this. Add a new option at the very top. But go ahead and disable it so
that the user can't themselves select it because you want
them to select an actual sport. By default, you can specify
that it's indeed selected. And it has no value. So not to judge any sport, but this
particular option has no value. But what the human sees is
the word sport, for instance. So this is kind of a hack. Ideally, the Select menu would
just have a placeholder attribute like the actual input boxes does. But that does not exist. So if I reload now, it looks
a little more user friendly. So it says sport. I can't select sport ever again. But it is the default, but I can
select one of these three sports which just increases the
probability that the human does what you might expect. Of course, there's something
else I can add here. Suppose I don't even give my name. It still went through. It didn't work. It's still a 405, but the-- 404. But the browser didn't stop me. So recall that we do
have some other tricks. For instance, I can say that
this dropdown, this select menu is itself required--
or, sorry, not this one. The text box is itself
required, for instance. So now if I go back
to the form and reload and I just ignore the name
question and click Register, the browser's going to yell at me. Now, recall that this is not robust. Client-side validation is not good. Why? What'd we learn last week? Yeah, I mean, I can literally
right-click or Control-click and open up Developer Tools. I can go into that form
using the Developer Tools. I can literally find the word
required, delete it, and voila. This form will now go
through because the browser's going to do what I change. So it's useful for user experience,
making just things a little prettier and faster to validate. But it's not going to be robust defense. All right, so let's go back now
to VS Code into my actual route and implement at least something
here that resembles registration. So I'm going to go into app.py. And, in app.py, let's
create this second route. So, at app.route, quote,
unquote, "/register" to match what is in my HTML. Let me define a function. I can call it anything I want. But, again, good convention to just
call it the same thing as the route name so you don't get out of sync. And then there's a couple of
things I might want to do. When you register for
this particular form, what are the two things that the
server should probably check for? What kind of logic should I have here? Yeah. AUDIENCE: [INAUDIBLE] at
anything for [INAUDIBLE].. DAVID J. MALAN: OK, so let's make
sure that the name is present and the sport is present, ideally. So let's actually validate the
user's input just like get int did back in week one. Just like get string, get
float, and all of those, they made sure that
you actually got input. So there's a bunch of
ways I can do this, but I'm going to go ahead and take
a relatively canonical approach. If not request.form.get,
quote, unquote, "name", I'm going to go ahead and then
return, how about let's just see, failure, quote, unquote, "failure"
just as a quick and dirty solution. So if it is not the case that there
is a value for the name field, just assume that there's a failure. So how can I test this? Let me go back to the other tab. Let me go ahead and not type
in my name and click Register. And notice-- well, OK, I need
to get rid of the required if I actually want to see
this thing go through. So you know what? Let's just change the template. Let's get rid of that so I
don't have to hack into it and delete things manually. So let me reload the form. Let me not type a name. Click register. And, oh, dang it. 405, Method Not Allowed. What's the fix for this in my app.py? What line number needs to change? Yeah, over there. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Yeah, I need to allow
both or at least POST at this point. So I'll keep it more restrictive. So methods equals and then
in a list, quote, unquote, POST because that's what I'm using
in my template as the method. All right, let's try again. I'm going to go back. I'm going to not type a name,
and I'm going to click Register. OK, so we caught the fact that
the name was not provided. Let's now go back and try
again and actually cooperate. David, Register, OK, now
internal server error. So something's gone even worse here. And, unfortunately, you're
going to start to see this over the next couple of weeks. This is like Python and the web's
equivalent of segmentation fault. It's a different issue, but it's going
to hurt just the same, unfortunately. So let's go back to VS Code here. Nothing seems to have gone wrong, but
that's because I've hidden my terminal. Let me open my terminal
window, and, oh, OK, so it looks like I made a crazy
number of mistakes here somehow. But let me go ahead and focus on-- and the formatting's a
little weird for some reason. Here we go. It's a little cryptic at
first glance, but here's the most important line of output. The view function for, quote,
unquote, "Register" did not return a valid response. So you're not going to
see this one too often most likely unless you do what I did,
which was you didn't have an else. You didn't handle the
situation where there is a name and something should've come back. So maybe I could do this. By default, I could just say
something like success as a catch all even though I've not
done anything useful yet. Let me try this again. Let me go back. David is typed in. No sport, Register. OK, so now I'm making progress again. So just like week one stuff, I make
sure I'm always returning some value, whether it's success or
failure in this case. All right, let's do something a
little more interesting, though. I could do this. How about elif not
request.form.get sport. I could similarly return failure. But this is a little silly to have
two nearly identical conditionals. So, actually, let me
just tighten this up. Let me go ahead and, instead,
get rid of those two lines and maybe just do something like this
in Python or not request.form.get sport. This is maybe the tightest way just to
ask two questions that are essentially the same but for two different keys. But returning, quote, unquote,
"failure"'s a little weak. That's not a valid web page. It's literally the word failure. So maybe we do this, render_template,
quote, unquote, "failure.html". And you know what? Down here, render_template success.html. So we actually send the browser
a valid web page, not just a single English word. Of course, we're going
to need those templates. So let me go in and do
something like this. If I go into, how about,
my terminal window. I need another terminal because
Flask is still running in that one. Let me go into froshims and let me
do code of templates success.html. I'm going to save a few keystrokes and
copy-paste all that stuff from index. But I'm going to delete most of it. And I'm just going to keep
it super simple today. You are registered. And then-- well, really,
not really because we're not going to bother doing anything
yet with the user's input. Let me do something
similar now for failure. So code templates failure.html. I'm going to copy-paste the same thing. And now I'm going to say the
opposite, You are not registered. But I'm not going to be
very useful, and I'm not going to even yet tell the
user what they have done wrong. But at least now we have the
beginnings of a froshims app. So let me go back, reload everything. Let me not cooperate at
all and click Register. OK, so you are not registered
because of some failure. I'll type in my name. OK, let's at least do that much. I'm still not registered. Let's go back. Let's leave David and choose soccer. Now, OK, now you are registered. So I got the success template instead. All right, so that seems to be better
progress or at least the beginnings of an actually useful application. But let's actually do more validation. Why? Because notice what the
human could still do. Suppose that, out of
principle, you really want to register for a different sport. So you're not a fan of soccer. You want American football. So let's right-click or
Control-click on that. Choose Inspect. And you can even do this client side. Let me write click on the Select menu. In Chrome, let me select Edit as HTML. You can start adding any HTML you want. So let me add an option
football close option enter. And, aha, now you have to
support football as well. Of course, this is going to work
because if I type in David and football and Register even though I'm not
doing anything with the response, I got through that validation
filter because I was just checking that there's an actual value. So this is now no longer really correct
because some annoying first year who's just taken CS50 is now
going to do something like this to my web application. And we're going to have bogus
data in the database, ultimately. So how do you defend against this
properly when it really is that easy? And, honestly, as soon as you put
a web application on the internet, bad things will happen to it because
people with too much free time. So how do we defend against it? What would be a better approach? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Nice, so
add another conditional such that the only things allowed
are the sports we actually are offering this semester. And, in fact, you know what? We can take this one step further. The fact that I hardcoded into my form,
my select menu, those three sports-- it'd be nice to maybe factor
out those sports altogether so that I have one authoritative list
that's used for generating the form and also validating the user's input. So let me do this. In app.py, let me go in here. And I can put this, how about, the
top of my file, to be conventional. I'm going to create a global
variable called sports. By convention, in Python, I'm going to
make it all uppercase even though that doesn't mean anything functional. There's no const keyword in Python. So it's more on the honor system
that no one else should touch this. But, inside of my list
here, let's go ahead and do only the official
three, basketball, soccer, and ultimate Frisbee. So now I have a Python
list of values that it would be nice to use to
generate that other form. So this is maybe nonobvious. But I think it's just an
application of past ideas. What if I do this? What if I pass into
my index.html template a placeholder called sports
and set it equal to the value of that global variable sports. Now, I'm trying to
adhere to best practices. The placeholder is called
sports in lowercase. But the actual variable I
called all uppercase just to make clear that it's a constant
even though that's on the honor system. But this too is conventional. This is a Pythonic way or a
Flask-centric way to do this. But now, in index.html, this is
where Jinja gets interesting. This lightweight syntax
for using placeholders gets interesting because I can
now do something like this. I'm going to delete
all three of the sports but not the disabled option, which
is just the placeholder text, inside of this select menu. Now I'm going to do this. Just like Python, I'm
going to say for sport in sports using the curly brace
notation and the percent signs, which are Jinja specific even though Jinja
and Python use almost the same syntax. And that's one of the upsides of it. You're not learning two things. You're learning 1.1 new things. endfor, which looks stupid,
but this is a convention in a lot of languages to literally
say end and the name of the keyword that you are ending with no space. Inside of this Jinja loop, I'm going to
write an option element once, option. And then, inside of the
two option tags, I'm going to do my placeholder
syntax with two curly braces and just say sport like this. And if I now go back into my
browser tab and hit back here and I reload the page,
notice that I still have a dropdown that's still
automatically populated because indeed if I go to View page
source and look at the actual HTML, there's some extra weird
whitespace, but that's because I hit Enter in my template. And it's generating literally what
I put inside of that Jinja tag. It's generating that list of sports. And it turns out-- I'm going to do this
just to be thorough. It turns out that the option
element technically lets you specify a value for that sport. Often, they're one and the same. What the human sees is what
the value of the option is. It's kind of like the a href
thing in the world of URLs. But this is not going to
change the functionality. But it's going to do this. If I reload now and I View page
source, this is maybe a more common way to see options where, in
orange, in my browser, is what the server is going to receive. In white is what the
human's going to see. They don't have to be one and the
same for reasons we'll soon see. But what's nice now is
that if I do actually want to officially support American
football, I can go in here, add "football", quote,
unquote, to my list, go back to the form, reload, and voila. Now I have a list of all four. But I haven't done the second
side of what you proposed, which is actually validate those sports. So let me do that. Let me go over to app.py. And, in app.py-- and we'll no
longer support football there-- let's do this in my registration route. So, instead of just
checking, is there a value? And the whole point of using
not is kind of like in C where you use an exclamation
point to invert the meaning. So if it's empty but it's not, then
it's-- the whole value is true. Let's get rid of this line. And let's instead do
something like this. How about if not request.form.get name. So let's still just check for a name. Or request.form.get, quote, unquote,
"sport" is not in the sports list. Now go ahead and say there's a failure. So what does this mean? If I go back to the browser and
reload, I now see only three sports. And I think this will work, OK, David. We'll register, say, for soccer,
Register, and it seems to work. But if some hacker
comes along and really wants to register for American
football, I'll right-click there. I'll inspect this. I'm going to hack the form
and add a bogus option at the very end just for myself. And, down here, I'm going to say
option value equals, quote, unquote, "football". And then, inside of
the option, I'm going to say football just to be consistent
even though they're one and the same. Save that. Close the developer tools. Choose the hacked option. Register, but, no, we
caught it this time. So this is hugely important. And there are so many darn
websites in the real world where the programmers either
don't know or don't care to actually validate stuff server side. This is how servers
quite often get hacked. You might have client-side
validation using HTML or JavaScript. And it looks nice. It's immediate. It's very pretty and graphical. But if you're not also paranoically
checking on the server, this is indeed how servers get hacked. Or, at least in the best
case here, your data set is sort of polluted with sports that
you're not actually going to offer. So this is not a very
harmful attack, but it's representative of what kind of
actions can be taken on your server if you don't distrust the user. So, unfortunately, this
is kind of a negative day. Never, ever trust user input. We saw that already with
SQL and injection attacks. All right, any other questions? Any questions thus far on this? Otherwise, we'll add a bit
of spice in just a moment. No? All right, well, just to show
you an alternative to this, let me change the GUI, the
Graphical User Interface, slightly. Drop-down menus pretty compelling here. But there's other techniques. And we won't dwell on HTML tags,
which you can pick up largely online. But let me go into maybe
index.html just to show you one different approach here. And if you really like radio
buttons, the little circles that are mutually exclusive-- this is a
throwback to radios, before my time, in cars where, when you pushed
the button for one radio station, it would pop out the buttons
for another, essentially, for your favorite channels. Radio buttons are, by definition,
therefore, mutually exclusive. So if I want to see those radio
buttons and not a select menu, let me go into index.html. And, instead of this select menu,
let me actually delete that. And even though this isn't going
to be super pretty, let me do this. for sport in sports, just as
before, endfor, just preemptively. Inside of this Jinja loop,
I'm going to do this. I'm going to do an actual input tag. But it's not going to be text. But the name of this tag-- of this element is going to be sport. The type of this element is going
to be radio for radio buttons. And the value of this button is
going to be whatever that sport is. But what the human is going to see
next to the radio button to the right is the same thing,
the name of the sport. So this is going to
look a little different. And it is going to look ugly in my black
and white viewport here with no CSS. But it does speak to how you can
change the user interface just using different building blocks. Let me reload. And, OK, it's probably
not the right call here because it's just kind
of making things ugly. But it's as simple as that because if
I now click on this or this or this, they're indeed mutually exclusive. However, suppose that you want to allow
the particularly athletic first years to sign up for not one but
two sports or all three. In no case now can you
support that right now. The workaround now for
a bad website would be, oh, just go register twice,
or go register three times. It's not a huge deal
because you just hit back. And then you change the
dropdown and submit. You hit back you change
the dropdown and submit. But that's just bad design. Surely, we can do better than that. So, in fact, let's make one
change here and use checkboxes. And if you've never really thought
hard about this in the web, radio buttons and checkboxes
have this distinct property where the former is mutually
exclusive, and the latter is inclusive whereby you can check 0
or more of those boxes collectively. So if I actually just go
into that same template and change the type of this
input from radio to checkbox and then go back to
the browser and reload, you immediately get what you and I
see in the real world as checkboxes. And the upside of this is that you
can check now 0 or more of them. But the catch-- and this is subtle-- the catch with our code right now is
that we're only expecting one value. So it's a minor fix, but
it's a useful thing to know. If I go back to app.py, if I actually
want to get all of the sports from the users, I'm going to have
to change my validation slightly. So I'm going to do this. I'm going to check for the
presence of a name as before. But then I'm going to use a loop
to validate the sports because I don't want them to slip, like
football, back into the list even if it's not there. So I'm going to say this in Python. for each sport in request.form.getall. If you know it's a checkbox, you want to
get all of the checked values, not one, for the sport parameter,
then go ahead and do this. If the current sport is not
in that sports list up top, then go ahead and return
render_template failure.html. Did I make a mistake here? I think we're good there. So we're checking against every
value that was checked on the form. Is it actually valid? And so now if I go in here, reload,
type in my name David, and I'll just check one of them, for instance,
because I've not hacked the form and added something bogus like football. Maybe someone was alluding to this. I see now an error. So let's do this together. Not sure what I did wrong. I'm going to open up my
terminal and go to here. And, oh, interesting, my
spacing's a little weird here. But attribute error. Immutable dictionary
has no attribute getall. So this is me lying to you. I don't think so. But [INAUDIBLE], are you here? Did Flask change since I last did this? No. OK, so Flask post form getall. All right, here we go. About 2012, this is
probably out of date. But ah. You know, that's not a bad idea, OK. All right, OK, in Flask, how
do I get all of the values from an HTML input of type
checkbox from request.form? Well, this is horrifying. getlist! Damn it, OK. What a good duck. All right, so-- all right,
so we'll rewind in time. So thank you. [APPLAUSE] So that's a good lesson. Just do as I do. All right, so getlist will get
you a list of all of those values. So now if I go ahead
and register as David, click just soccer without
injecting something like American football
and Register, now I'm, in fact, registered but not
really, not really in the sense that we haven't actually
done anything with the data. So this is to say, ultimately, that
there's a lot of these building blocks, not only in HTML, which is mostly a
throwback to last week but also now, in Flask, where you can process
all of those building blocks and take control over
what up, until now, is usually the domain of Google or
the websites that you actually use. Now you actually have more of
the building blocks via which to implement these things yourself. So let's go ahead and add some
final features to froshims here where we're actually doing
something with the results. And, for this, I'm going to
open up a version in advance. So I'm going to go over to VS Code here. And let me go ahead and close these tabs
but go into my second terminal window. And I'm going to go into
today's src9 directory. And I'm going to go into
version 4 of froshims, which has everything we just did plus a bit more. In particular, I'm going
to go ahead and do this. I'm going to show you
app.py, which, additionally, has some comments throughout. But, in app.py, what you'll notice
is that, after all of my validation, I'm actually got a couple
of new features here. It's a little weak in terms of
UI to just tell the user failure. You are not registered. That's all my template previously did. But what if I borrow an idea from my
index template where all of this time, for hello and froshims,
I've been passing in input. So what if I do this? Let me show you. In templates, failure.html-- or, rather,
let's see, in templates, error.html. So notice this, I can
make the beginnings of a common format for an error page. So, in error.html of
this fourth example, I've just got some big, bold
error message at the top. But I have a paragraph
tag inside of which is a placeholder for an error message. And then I've gone one step further just
because and put a happy cat or grumpy cat as an image to let you down
easy that something has gone wrong. But this is like now every website where
there's generally some customized error message when something has
gone wrong or when you have not cooperated with the rules of the form. So what am I doing instead? Instead of rendering
failure.html very generically, I'm rendering this new
template error.html. And I'm passing in a custom message. Why? Because now, in my app.py,
my logic, I can actually say, you're missing your name. You're missing a sport. Or I can tell the human what the error,
which is much better user interface. Down here, though, on
this new line, here's where I'm now beginning to
actually register registrants. What's the easiest way to do this? Well, let me scroll to
the top of this file. And you'll see that, in addition,
to a big list of sports, I also have an empty dictionary
initially of registrants. Why? Well, dictionaries are this nice Swiss
army knife, key-value pair, key, value, key, value. Names could be keys. And maybe sports could
be values, at least if I'm supporting just single sports. So I could have a fancier
structure, but this seems sufficient, two columns, key,
value, for name, sport, name, sport, and so forth. So how do I put a person's name
into that global dictionary? Well, I'll use the syntax from
week six, registrants bracket name equals sport that associates
that value with that key. And, now, what you'll see in that
I've added a new route /registrants. And this is where
things get interesting. If I look at this premade
route as you will too, as you look at code that's been
written for you in the weeks to come, well, this sort of invites me
to look at registrants.html. Why? Apparently, this
registrants.html template is being passed this global dictionary. How might I use that? Well, let me go into VS Code's terminal. Let me take a look at registrants.html. And, interesting, we
haven't used this HTML much. I used it super briefly last week. This is an HTML table. It's not going to look
super pretty because I'm not using bootstrap or CSS more generally. But notice that, in the
table's head, there's name and sport from left to
right in the two columns. And then, in the table
body or tbody, notice that I have a whole bunch of tr, tr, tr,
one for every registrant in that Jinja loop. Each of the cells, the table
datas have the person's name. And then if you go inside of that
dictionary and look up the name, you get the value thereof,
so name, sport, name, sport. And the route, of course, again, is
just this, render registrants.html by passing in that dictionary. So what is registrants.html? It's just this. So I think if we go and run
this version of the application, we have some nice new features. Let me go ahead and do Flask-- let me kill Flask in the other window
just so it's not using the same port. Let me do flask run inside of froshims4. So far, so good. Let me go over to my other tab. Let me reload. So I get the latest HTML. I'm going to go ahead and
type in something like David but select no sport using radio buttons. So, again, you can only pick one. And now not only am I
seeing one grumpy cat there. It's also telling me at the
top that I'm missing the sport. Conversely, if I reload the
page, don't give my name. But do give the sport and register. Now you see that I'm
missing name and not sport. So, again, the UI is not very pretty,
but it has the building blocks of being much more appropriate. Let me now cooperate on both fronts. David wants to register
for soccer, Register. And now notice where I am. Apparently, I got redirected
to the registrants route, inside of which is
this two column table. It's not very interesting yet. So let me go back to the form. And let me register
Carter, for instance, for, say, basketball, Register. And now there's two of us. Let me actually go back to the form. And let me register Yulia for
ultimate Frisbee, Register. Now there's three of us. And, again, the CSS is ugly,
but I do have an HTML table. And if I right-click
and View page source, you'll see David, soccer; Carter,
basketball; Yulia, ultimate Frisbee all as tr, tr, tr. So, again, if you now think about
an app like Gmail in your inbox, odds are if your inbox
is indeed a big table, then it's probably tr, tr, tr, tr. And Google is rendering all
of that HTML dynamically based on all of the emails
in some variable somewhere. Well, let me go back here and
see, how did that redirect work? Let's watch this a little more slowly. Let me go up to the main form at slash. Let me type in David. Let me type in-- select soccer. And let me Zoom in to the URL. And notice that, when
I submit this form, even though the action is /register,
I'm indeed ending up at /registrants. So how is that actually happening? Well, let me go back
and do it one more time. But, this time, let me
open up Developer Tools. Let me go to the Network tab, which,
recall, we played with last week. And let me go ahead and do this again. So David, Soccer, and I'm
going to click Register. And now, notice, interesting, two
routes were actually involved. The first one here is Register. But notice if I go to
headers, ah, 302 found. 302 indicated some kind of redirect. What's the redirect going to? Well, if I look-- scroll down
here at response headers, there's a lot of stuff
that's not interesting, but location was the one we cared
about last week. /registrants, oh, that's why the second request over
here at left is actually /registrants. And it is 200 OK because it's
all of these basic building blocks from last week and now this. Where did that redirect come from? Well, now you have the
ability to do this. Notice that, in my register route,
the last thing I said we had done was add the name and the value
to this global dictionary. But the very last thing I did was
redirect the user to the /registrants route. What is redirect? Well, at the very top
of this file, notice that I proactively imported not just
flask, render_template, and request. I also imported redirect
this time, which is a function that comes with Flask
that automatically issues the HTTP 302 redirect for you without you having
to know anything about those numbers or otherwise. Let's do one final example
before we break for snacks. In this final example, froshims5,
let's actually do something with SQL. SQL, after all, allows us to persist
the data because this version here, with this global dictionary, what's the
downside of using this global variable to store all of our registrants? What's the downside? Yeah. AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Exactly, so,
as soon as the server quits or if something goes wrong
like maybe the power goes out, or we have multiple servers
or something like that, we'll lose the contents
of that dictionary. And so that's not really
good to store data that you care about in the
computer's memory alone or RAM. You want to store it on disk using
something like fopen and fwrite and all of the file I/O
stuff we talked about. But, in week seven,
recall, we introduced SQL. So that writes things
to disk in a .db file. So let's actually do that
with one final example. Let me go ahead and close
these tabs here in my terminal. Let me go ahead and close
the old version of froshims and go into froshims5 now. And, in this version, let me show
you, in app.py, the following. It's almost the same in terms of
what we're importing from Flask. But I'm also going to
import from CS50's library a SQL function, which we used
briefly when we wrote code in Python to talk to a SQLite database. This is the one example
of a CS50 training wheel that we actually do keep on
deliberately through the end of the term because it's actually
just really annoying to use most third-party libraries
with SQL in as user friendly a way. You're welcome to, but I do think
that, even though you shouldn't be using get in getstring, getfloat
anymore the SQL function's actually pretty darn useful, I would say. So how do we use this? Everything in this file so far is pretty
much the same except for that import, including these lines here. But notice that I am opening
up a file called froshims.db. And that's a database
that's empty initially. But it is in my account. So, actually, let me do this. Let me run sqlite3 on froshims.db. Let me increase the size of my terminal. Hit Enter. What can I type to see the
structure of this database? Sorry. Wait, what? AUDIENCE: [INAUDIBLE] DAVID J. MALAN: Oh, yes, thank you. .schema should show me. OK, it's actually a very simple
database, a registrants table with one, two, three columns, an ID
for a unique identifier, a primary key, the name of
the person, and the sport for which they're registering. And, presumably, the ID will be
automatically incremented for me. So let me exit out of that. Go back to app.py. And this line 8 here is just giving
me access to that SQLite database. And recall that the three
slashes are appropriate. It's not a typo relative
to something like a URL. Here is my three sports
that I want to support. Looks like my index route
is pretty much the same. So nothing new there. In fact, I'm using that
same lesson as before in passing in the whole sports list. Notice that, OK, this is interesting. Deregister, this version is going to
let users sort of bow out of a sport as tends to happen over
the course of a semester. But we'll come back to that. But let's look at register now. register is almost the same even
though I do have some comments here. We're making sure to validate the form. But this is where it gets interesting. I'm now inserting rows into the
database to register these registrants. Notice that I'm using CS50's library
to insert into the registrants table into these two columns name
and sport, these two values. And I'm being very careful to use
question marks to escape the user's input to avoid injection attacks. And then I just redirect the user. But what's going to be interesting
about this version is this too, /registrants no longer just uses Jinja
and iterates over a global variable. In this version, we're
selecting all of the registrants and getting back a list of dictionaries. And then we're passing
that list of dictionaries into the Jinja template
called registrants.html. So, just to make clear
what's going on there, let me open up templates
and registrants.html. It's almost the same as before. Notice that I'm using the dot notation
this time, which Jinja also supports. And it's almost always the same
as the square bracket notation. So you'll see both in
documentation online. But notice that I have a third
column in the registrants table that's a little interesting. And this will be the
final lesson for froshims. A button via which people can deregister
themselves, like a bow out of froshims. So let's do this. Open the terminal. Let's do flask run in
version 5 of this here. Let me go into my other tab, close
the Developer Tools, go to the /route, and I have a form quite like before. But now, when I register,
David for soccer and click Register, notice that it's ugly UI. But there's a button next to
David to deregister themselves. Let's go back to slash. Let me also register Carter, for
instance, for basketball and so forth. There's now two buttons. This, now, is what really ties
together our discussion of SQL and primary keys with
the world of the web. Suppose that there were two Davids
in the class, which there surely are, two Carters, two Yulias,
two of any names. We clearly can't rely
on first names alone to uniquely identify
humans in a room like this. So we probably should
use opaque identifiers, that is, those numbers, 1, 2, 3. Indeed, if I go into VS Code-- let me open another
terminal and make it bigger. And, in my src9 froshims version 5,
let me run sqlite3 of froshims.db. And, sure enough, if I do SELECT * FROM
registrants; I'll see the two of us thus far. And we've indeed been
automatically-- been assigned an auto-incrementing
primary key, 1, 2, respectively. That's useful now in the web especially
or user interfaces in general. If I view this page as
source, here in my browser, notice that both David and Carter
have their own form in a third td element next to them. And that's what gives us this. But notice that form, even
though it's an ugly UI, is a form that will post to a
/deregister route a hidden input, the name of which is ID to
match the primary key column, the value of which is 1
for me and 2 for Carter. So this is how you stitch
together a browser and a server. When there's a database
involved, you just uniquely identify the things you
care about by passing numbers around from browser to server and back. You might visually show David and
Soccer and Carter and Basketball. But the server only needs
the unique identifier. And that's why we dwelled
so much, in week seven, on these primary keys and,
in turn, foreign keys. So, when I go back to this form
here and click on deregister, this is going to submit ID equals 1 to
the /deregister route which should-- and this was the only route
we didn't look at earlier. Let me open up app.py again. You'll see that this
happens in deregister. In the deregister route,
which only supports POST, I'm going to get the ID from the form. If there is, in fact, an ID and
it wasn't missing for some reason, I'm going to execute
delete from registrants where ID equals question mark as a
placeholder, passing in that number. And then I'm just going to redirect
the user back to registrants so they can see who is
still actually registered. So if I go back to my browser
here and I deregister myself, we should see that now that's gone. And if I deregister
Carter, that's now gone. And if I indeed go back to VS Code, open
up my terminal window, make it bigger, run SELECT * FROM registrants, now
no one is registered for the sport. And so we've effectively stitched
all of these things together. So that's all how we
might implement froshims. Just so you've heard the
vocabulary, what we've implemented is a paradigm known MVC,
Model View Controller, where the view is everything the human
sees, the templates, the HTML, the CSS, the JavaScript. The controller is everything
that's in app.py, the logic that we've actually been implementing. But, as soon as you introduce
a database especially or even a global dictionary, then you
have the M in MVC, a model, which is where all of your data is stored. Now, you don't have to
think about it this way. But humans, over time, realized
that, wow, most of our web apps follow this similar paradigm. So they started thinking about
different components of the application as having these different identifiers. So there's still a lot more. We have not yet considered how,
when you log into a website, the website remembers
that you've logged in. We've not remembered how
you can keep track of what's inside of someone's shopping cart. This was a lot of effort
just for two-second joke. But let's go-- with that
said, for roll-ups and snacks as served, let's take a 10-minute break. We'll see you in 10 for
that and more as we wrap up. All right, we are back,
and let's consider now how web applications typically
work when you actually have to log into them, which is
most every web application nowadays. Somehow or other, even
though you only log in once, at least at the start of the day
or the start of the browser tab that you open, somehow
or other, websites are still able to remember
that you've logged in already. And that's how you see your Gmail inbox
or your social media feed or the like. So here, for instance, is a
representative login form. This one here for Gmail or
for all of Google services. And let's consider what actually
happens underneath the hood with respect to those virtual envelopes when you do
log in with your username and password to a site like this. Well, typically, inside
of the virtual envelope that your browser sends to Google
servers, that is, accounts.google.com, is a request maybe for that form. So GET slash HTTP version 2 or whatever
version your browser's actually working and some other headers dot, dot, dot. But, for the most part, that's
what we've seen thus far. When you then actually log in-- or, rather, when you visit
that page, hopefully, you get back a response from the server
saying that everything is OK. That is 200, OK. And the response that comes back
is text/html, so same as last week. This is just what's inside of those
virtual envelopes back and forth. But, when you log in to a
server, it turns out, typically, what's happening is that the
server is, unbeknownst to you, kind of stamping your hand once
it's verified your username and your password to remember
that you have logged in. In particular, what
Google server is going to send back after you visited that form
and submitted that form as via POST, is you're going to get back a
response that looks like this. It's going to, hopefully, say 200, OK. It's probably going to be a
web page written in text/html. But an additional HTTP header that
we didn't focus on last week, which is this one, the Set-Cookie header. And the Set-Cookie header
specifies yet another one of these key-value
pairs, the name of which can actually be anything depending
on the server, the value of which is some unique identifier. So you've all probably heard about
cookies in the context of the web. You've probably heard that
they're not good for your privacy. And that's generally true. But cookies need to exist
if you want web pages to be or websites to be stateful, that is,
remember a little something about you. And so session is the name of the-- session is a word that
describes the maintenance of state between a client and a server. That is to say, if the server's
remembering something about you, you have a session with that server, the
equivalent, really, of a shopping cart. So, in fact, if you go to amazon.com or
any website via which you can not only log in but add items to a shopping
cart or equivalent, that is a session. Shopping cart is the
real-world equivalent thereof. So this Set-Cookie header
is essentially a directive from the server to your browser to
store this value in the browser's memory somewhere, either for the life
of the browser tab or maybe even longer, for an hour, a day, a year, depending
on the expiration time that's actually set. The idea, though, is that because your
browser is designed to understand HTTP also, just like the server, you're
on the honor system, your browser, such that the next time you
visit Google's same server, you should remind the
server what cookie was set. That is to say, the browser
should send back to the server, not set cookie because it's already
been set, but a cookie header that contains exactly that same value. So the metaphor here
is kind of like when you go into maybe a bar or
a club or an amusement park, and you showed your ticket, or
you paid your fees, ideally, they'd do something like stamp your
hand such that the next time you go through the line, you don't have to
take out your ticket again or your ID and prove that you have paid
or that you belong there. You just show your hand stamp. And the idea is that
the bouncer can trust that if you're presenting
this hand stamp and maybe it's the right color and the
right picture for that particular day, they should just let you in
without prompting you again for your ticket or your money or, in
this case, your username and password. So cookies are a very
good thing functionally. They are a feature of HTTP. And they are how servers implement
state between themselves and you because, after all, when you
click on a link on a web page or another link on a
web page, eventually, the browser icon stops spinning. And it's no longer
connected to you typically. But, so long as the next link you click
results in a virtual envelope going from client to server
containing this header, it's the equivalent of just
reminding the server who you are. This value is generally a big
number, a big alphanumeric number, so it's some unique identifier. Generally speaking, cookies do not
need to contain your actual username, your actual password. That's generally frowned upon. The useful information
like your username, your password, what's
in your shopping cart can be stored entirely server side. This cookie is just a reminder
to the server who you are. The problem with
cookies, though, nowadays is that they're used so
often for advertising, for tracking, and the like. Why is that? Well, this is a natural result
of that basic primitive. If your browser unbeknownst to you
is in the habit of just presenting this hand stamp every
time it visits a website, you're proactively
reminding websites who you are again and
again, at least who you are in the sense of if you logged in. Now they'll always know who you are. Even if you're in incognito mode,
for instance, private mode browsing, your hand is still getting stamped. Your incognito window is still
sending that same unique identifier again and again. And, so long as you don't
log in, they might not know that your Carter
or David, but they do know you're the same user or the same
person using that browser or, rather, the same browser visiting the
website because that hand stamp is going to be sent again and again. But, when you clear your cookies or
when you close your incognito window, that's like washing your
hand and starting fresh, getting a new unique identifier. So you appear to be someone
different even though, as an aside, even if you're in the habit
of using incognito mode and clearing your browser
tabs and all of that, with very, very high probability,
servers can still track you nowadays based on your IP
address, of course, which is on the outside of those envelopes,
based on the particular browser extensions that you have installed,
based on the various fonts that you have installed. There's some
crazy-high-percentage likelihood that a browser can uniquely identify
you even if you're scrubbing your tracks in this way, so just FYI. But, for today's purposes, it all
derives from this thing called cookies. And this is how they are then set. So let's actually use this
a little more productively and leverage it in the context of Flask
by using another global variable that comes with Flask that you can
import that gives you access to the equivalent of a shopping cart. That is to say, Flask deals
with all of this stuff like setting cookies and checking
for cookies and all of the plumbing. Someone else have solved that for you. And Flask just handles the
contents of the shopping cart or the username to you in
the context of a variable. So let me go over to VS Code here. And, during the break, I created
a new folder called login. If I type ls, I've got the beginnings
of an app.py and a templates folder. And, in the templates folder,
I've got the beginnings of a layout.html and
an index.html just so I don't have to type quite as
many characters to get started. What I'm going to do
with this app, though, is let's implement a very simple
app that allows the user to log in and demonstrates how a server can
remember that you are, in fact, logged in. So let me open up app.py. And, at the very top of this
file, which is otherwise-- let me shorten this even further
so it looks as simple as possible. In this app.py, let me go ahead
and simply add one more import up here called session. And that's going to give me
the equivalent of a shopping cart at my disposal. But I do need to configure it. And there's some different
ways to configure it. But the conventional
way or a recommended way here is as follows, to run app.config. And then set this configuration
variable, which is flask specific, called SESSION_PERMANENT equals
false so that this will indeed be treated as a session cookie. So, as soon as you quit your browser
or close your tabs, typically, what's in the session will be deleted. This does tend to vary by browser. Sometimes things might be kept
around longer than you expect. But, by default, this is going to
ensure that, essentially, the cookie is deleted when you quit the browser. I'm also going to do this
app.config SESSION_TYPE is going to equal, quote, unquote, "filesystem". This just ensures that the contents
of your shopping cart or equivalent are stored in the servers-- in the server's files, not in the
cookie itself for privacy's sake. And, lastly, I'm going to
activate sections on this app by just running this line of code. These, for today's purposes, are
sort of copy-paste-able lines that just get sessions working
in a recommended manner for you. Hereafter, now, we can just use
them as we expect, as we would hope. So let me do this. Let me now open up another file in
templates called, say, index.html. And, in index.html, I'm going to
make a very simple web page that's just going to check if the
user is logged in or not and explain as much if they are. So, in the body block of index.html,
which I prepared in advance, I'm going to do this. I'm going to use Jinja, not to
have a loop or a placeholder, but an actual conditional like this. If the user is logged in with a name,
then go ahead and output inside of-- and let me do this else here. And, actually, let me
proactively do this just so you can see the structure, endif. So I've got the beginnings
of an if else block in Jinja. If there's a name variable
in this template, say this, You are logged in as name period. And then let's give the human a link-- actually, nope, let's do this. Else, if you are not logged
in, you are not logged in. So super simple English text
that's just going to tell us, is the user logged in with a name? Or are they not? And I'm not even going to
bother with a password. We're going to keep it simple
with just a name to demonstrate. All right, so now what am I
going to do in my controller, that is to say, app.py? Let's go ahead and do this. Let's go ahead and create
an app.py called login. So I have a route that just
handles logins for the user. And I'm going to go ahead and do this. Initially, I'm going to define
a function called login. And I'm going to have it return
render template of login.html. So this is going to be
a login form by default. Well, let's create that. Let me go into my templates directory. I'm going to go ahead and create
a new file called login.html. And, in login.html, I'm
going to borrow some of this HTML-- this template from before. In login.html, I'm going to just
have a very simple form for logging in, again, no password per
se, just a user's name. So let's keep it simple as follows. form action equals,
quote, unquote, /login. The method for privacy's
sake will be post. Inside of this form, let's just ask
the user for an input with autocomplete off by default and autofocus on. And the name of this field will
be name for the human name. The placeholder text, as before, will
be, quote, unquote, "Name," capital N. And the type of this field will be
the default, which is just text. And then, lastly, let's have
a button, the type of which is submit because its purpose
in life is to submit this form. And, inside of that button,
we'll see the words login. So very similar to greet,
very similar to register. But we're now just
implementing a login form. OK, now let's go back to app.py. The only thing login does
right now is display this form. But let's actually handle
the user's logging in. So I'm going to do this. If the request.method equals,
quote, unquote, "POST" and, therefore, the form was
logically submitted, let's do this. Let's use this session variable. So this session variable
that I imported globally here is essentially a dictionary. It's an empty dictionary
with two columns ready to receive keys and values. So if I want to put in someone's
name from this login form, I can just tuck it in the session. And it's similar in spirit to this thing
that we did last time for froshims, giving myself a global dictionary. But the problem with that was
that, as soon as the server quits, all of the memory contents are lost. And it was also global. So no matter whether it was me or
Carter or Yulia visiting the same URL, there was no distinction among users. All of us were treated as the same user. But what's brilliant about this session
variable is that flask, because it knows about HTTP and
Set-Cookie and cookie, it creates the illusion that
whether you, the programmer, have one user or a million
users, the contents of session are guaranteed to always be unique
for me or for Carter or Yulia or whatever human is visiting
your code at that moment in time. You have the illusion that everyone has
their own session object or dictionary or, really, everyone has
their own shopping cart just like you would
expect on an amazon.com. So let's use this session
object like a dictionary. Let's do this, let's remember the user's
name from the form by going in session, quote, unquote, "name" and set the value
of that key equal to request.form.get name. So whatever's in the form, let's plop
it into this global session dictionary to remember the user. And you know what? Just for user interfaces'
sake, let's do this, let's return a redirect, a
302 or whatever, to slash. Let's just redirect the user back to the
home page to see if they are, in fact, logged in at that point. So I think, at this point, that's
the entirety of my login route. If the user has submitted their name via
POST, store their name in the session, and then redirect the user
back to the home page. The home page meanwhile
looks again like this. If there is a name in this template,
we're going to see that person's name. Else, we're going to see
that they're not logged in. So how do we get at that name? Back to app.py. Let's just assemble
these building blocks. In my index route, I'm going to do this. name equals session.get,
quote, unquote, name. So you can treat session similar to
request.args, similar to request.form. But it's a more permanent thing. At least so long as it's
the same human logged in, I'm going to get my name or
Carter's name or Yulia's name depending on whose browser is visiting
my URL at this moment in time. All right, so let's cross
my fingers because I wrote a lot of this on the fly. Let me open my terminal window in
my login folder and do flask run. And, OK, I screwed up already. Session is not defined. Did I mean session in lowercase? No, not in this case. It turns out what I should have done is
one more line from flask_session import Session. Why? This is another flask library. It's technically a third-party
library that other smart people wrote for flask users to use. So I copied and pasted that
line from the documentation. So clearly forgot about it. Let me get rid of this
registrants dictionary which has nothing to do with this example. Let me now open my terminal
again and do flask run. OK, now we seem to be in good shape. No error message is apparent. Let me go back to my URL and reload. And notice I am not logged in. Now, this is not very user friendly
because the only way I know I can log in is by going to /login. So let's actually improve the UI. In index.html, if you are
not logged in, let's do this. a href equals /login. And let's give them a link to log in. And, otherwise, you know what? Let's do this. a href equals, quote, unquote, /logout
even though it doesn't exist yet if we want to facilitate the user logging out. But I think this will be a nicer
UI because if I reload now, I'm told that I'm not logged in. But there's my new login
link shown conditionally. Well, let's do this. If I click on this, it's super small. But, in the bottom left-hand
corner of my browser, I'm seeing the /login route. If I click on that there is that
login form, no password, super simple, just a name. I'll type in D-A-V-I-D. And
now, notice, if I Zoom in here, I'm indeed currently
at the /login route. But, when I log in, I'm going to be-- oh, damn it. I did make a mistake. 405 method not allowed. I think we fixed this before. I can go back into app.py. Which line number do I need to fix? Method was not supported
for which route? So, yeah, line 16. So I think I need methods
equals both of them. So GET so I can display the form. And POST so I can process the form. Let's try this again. I'm just going to go back. And I'm going to click-- type David and click Log In. And I'll zoom in on the URL. Log In, ah, you are logged in as David. Now I can log out. And, indeed, if I log out. This one's not going to work. I think I'm going to get a 404
not found, but that's fixable too. So let me go back into VS Code. Let me go down to the
bottom of the file. And let's add another route /logout. Just GET is fine because I'm
not posting anything to it. I'll define a function called logout
just to match the name of the route. And you wouldn't know this
without reading the documentation, but if you want to clear
the session and forget the user's name, forget the
items in their shopping cart, you can do session.clear. And that will just clear
the contents of the session wherever the server is storing them. And then I can just return a redirect. redirect to, for instance, slash. So let's see this in action. Let me go back to the
browser and hit back. I'm logged in as David. But now, when I click Log out,
I'm going to quickly go to-- and I'll show you in the inspector. Inspect, Network tab. Let's click on Log out. I indeed end up at logout first. But then I'm redirected to this long
URL, which is unique to my codespace. But the first logout URL redirects
me with a 302 until I see a 200. And I'm back at the home screen. So, even though that was a
bunch of steps for me to do, that is how every website implements
the notion of logins and also shopping carts. Now, you might add some fanciness
with usernames and passwords and the like, maybe two-factor
authentication and the like. But it all boils down to
storing stuff in the session and using those cookie headers in
order to keep track of who is who. Any questions on these
techniques thus far? No? All right, how about an actual
Amazon, if a simplistic one? Let me do this. Let me go back to VS Code here. Let me close these tabs, and
let me open up an example that I wrote in advance, this one
in my src9 directory's store folder, so the beginnings of an
electronic commerce store online. Let me just dive in blindly
and do flask run for this one. And let me go to my browser
tab and reload because I'm going to see my new app now. And it's super simple, also ugly. There's no CSS or fanciness there. But it's just HTML. And this is the earliest version,
if you will, of Amazon 1.0 where they just sold books, and
only these five books, for instance. So what's going on here? Well, here is how using
all of today's primitives, you can start to infer
how websites work. So let me View page source. And, even though the UI is not very
pretty, let's see what's inside here. There's an h1 tag for books,
just like the title of the page. There's a whole bunch of
h2s, each one of which represents apparently
the title of a book. And below every title is a unique form. Every form, though,
has an action of /cart. And every one submits
via post to that cart. But notice this trick. Just like the deregistration for
froshims, every one of these forms has a unique ID that's hidden,
but the value for this one is 1. The value for this one is 2. The value for this one is 3. So even though for froshims,
we used it to deregister people to opt out of the froshims, here,
you're using it to effectively add items to your cart. Why? Well, where is this data coming from? Let's poke around further. I'm going to open another
terminal window in VS Code. I'll make it bigger. I'm going to go into src9 and store. And if I type ls, you'll see
app.py, requirements.txt, templates, all of which I predicted would exist. There's a temporary folder
called flask_session. This is where, surprise,
surprise, your sessions are temporarily stored on the server. So even if the computer quits or
reboots, the files are still there. The shopping carts are still there,
but you shouldn't need to go in there. And definitely don't change things
in there because things will break. But notice store.db, which
came with my folder today. Let me run sqlite3 of store.db. .schema to see what's going on in there. It's a super simple
database table called books with an ID column and a title column. And that's it. Let's see what's inside. SELECT * FROM book; Not surprisingly,
there are the five books. So, again, you see how we're
stitching these technologies together using these basic building blocks. So let's look now-- let
me quit out of SQLite. Let me shrink my terminal
window and open up app.py. And there's some comments in here. But, for the most part,
it's the same as before. I'm importing some of the
session stuff, so I can keep track of whose shopping cart is whose. I'm opening, though, in this version,
the database called store.db. I'm doing that same stuff, copy-paste
with the session just to make sure that it works. And then, down here,
this is where things get really kind of interesting
and, again, representative of web apps everywhere. My /route, that is, my index,
has this line of code first, a book's variable that gets the
return value of the CS50 function-- CS50 execute functions. SELECT * FROM books return value. What does that return? Well, recall that, when using
the CS50 library and you're using the SQL function, this
execute function within SQL, you're getting back, from db.execute
typically, a list of dictionaries. And the keys of those
dictionaries represent the columns from the database table. So here is a Python list
per the square brackets. Every element in this list is a
dictionary as per the curly braces, not to be confused with
Jinja's curly braces today and that's in the HTML files. Here's one key. Here's another, value
and value, respectively. So, again, db.execute,
when using SELECT, returns just a list of dictionaries. It's as simple as that. So if I go back to VS Code, I have a
string of SQL inside of my argument to this Python function. That gives me a variable
called books, which is a list of all of the
books in the database. So I can certainly pass
that in as an argument to render template so that,
when books.html is rendered, it has access to all those books. So let me do that. Let me go into templates books.html. And, perhaps not surprisingly, here's
that same boilerplate as before. We extend the layout. Here's my body block. Here's that h1 tag that just says
Books at the top of the page. And here is a Jinja for
loop in the template that is going to output, for every book,
an h2, which is smaller but still bold for the title. And then inside of that-- or below that
is a form that's identical for every book except-- notice that I'm very cleverly
outputting a unique value for every one of those forms. Just relying on those primary
keys back and forth, back and forth to implement
this notion of adding. And so what is the /cart method? Let's follow these breadcrumbs. And I'm doing this deliberately live. If you were to receive
or inherit code like this from a colleague, a
friend, a class, you really just follow these breadcrumbs to wrap
your mind around what's going on. And, honestly, start
with the files that feel the simplest like I did, like
index.html, start with the entry point. So let's see the cart route. It's a little longer. But let's walk through it step by step. So the cart route supports
both GET and POST. It looks like, with these lines, which
I haven't had occasion to use yet, I'm just checking. If there is no cart in the session,
create a cart that's an empty list. And I'm doing this
logically in this program because I want to make sure that the
cart always exists even if it's empty. Even if it's empty initially,
that's what that line of code is essentially doing. So we can put things in it. So if we have submitted something to
this cart and thus the method is POST, let's go ahead and grab the ID
of the book from request.form. Let's go ahead and make sure
that that ID is actually valid, and it's actually a number, so
it doesn't evaluate to false. And if so, do this. Go into the sessions cart, which
is initially an empty list. And, just like we saw in week
six, append an item to the list. Append, append, append. So we just have a list of book IDs. And then, when you are done with
that, redirect to the cart route. Well, that's a little
weird and almost recursive because the cart route that we're
in is redirecting to itself. But redirects are always get
requests when done like this. So even though we ended
up here via a POST, this redirect is going to send me
back via a GET, to the same route. And that is why now, outside of the
conditional, these final two lines apply. Here is a books variable. I don't want to get all of the
books because I'm not showing you the catalog now of amazon.com. I'm showing you your shopping cart. And how do I do this? I'm SELECTing * FROM books WHERE the
id of the book is IN this list of ids. And you haven't seen
this yet most likely. But, in the CS50 library, if you
use parentheses and a question mark, so placeholder as always, you can then
plug a list into that question mark, and the library will
separate them all via commas. You might have for a past
problem set manually. We will generate the commas for you. So everything is nicely escaped. And then we're just rendering a template
cart.html, passing in those books. So what's cart.html? That's the last breadcrumb to follow. cart.html, and really nothing going
on that's that interesting here. There's an h1 tag at the top. There's an ordered list, which
is a numbered list just because. And then there's this Jinja loop that's
outputting a list item or li element for every book showing its title. So if I go back to the store
here and actually start adding things to the cart, there,
I've added that to the cart. All right, just one book. Let's jump to the last one, so the
Hitchhiker's Guide to the Galaxy. How about Mostly Harmless? Click that. Now there's two items
in the shopping cart. Let me go back. There's now three items in the
shopping cart and so forth. But if I were to make this URL public--
it's currently private by default. So not everyone on the internet
can visit it at the same time. If I were to make this
public and any one of you were to open it on your
phone or laptop, you would see an empty cart because you
would have a different handstamp, a different cookie. But the server would be keeping
track of all of our shopping carts in, if you will, that flask
session folder, just all sort of happens automatically. Phew, OK. So that then is how
amazon.com is implemented. Questions? Yeah. AUDIENCE: Or is the client-- so let's say if you open
up this application. How do the server know that
this is to be a new session now? [INAUDIBLE] and someone who
starts up a new session. How does the browser [INAUDIBLE]
when someone [INAUDIBLE] that is like a new session? DAVID J. MALAN: A really good question. How does the browser-- how does the server to give you a
brand-new session the first time you visit a website? When you visit something.com
for the first time, your browser will not
send a cookie header. There will be no cookie
colon session equals value. It's just going to be blank,
it's going to be my showing you my other hand that has no ink on it. Then the server will know, well,
if you didn't send me a cookie, I'm going to set one for you by stamping
your hand with the set cookie header. It's just going to generate typically
a really big random number that's different for you, for me, for you
to keep track of us individually. So that's all. It would just happen by default.
And Flask makes all of that happen because we have not only
imported these lines at the top. We have also used these
configuration lines too to ensure that the server does
exactly what I just described. If you had to do all of
that manually, honestly, it would be so annoying to make
web applications because you'd do copy-paste all of the time. This is still some copy-paste, but
it's way less than implementing all of this cookie stuff on your own. All right, how about a
final set of examples that allow us to escalate
quickly to a larger data set and make this more like an actual
amazon.com or maybe an actual imdb.com. And we'll do this by way of a
tour of some pre-written examples rather than do all of
these here from scratch. So I'm going to go into
my terminal window. I'm going to hit Control-c to kill
the previous version of the store. And I'm going to go into shows version
0 initially, which has our old friend shows.db from our SQL lecture, which
has all of the latest TV shows from IMDb using the same schema as then. And there's an app.py. There's a requirements.txt. And there's a templates
folder just as predicted because we're still using Flask. So let's go ahead and
take a look at what's going on inside of this file
app.py as our entry point. All right, so I see some
pretty familiar imports now. I see shows.db using the SQL library,
so nothing too interesting there. The index route is super simple. It's just rendering index.html. And then there's a search route. So this is kind of an amalgam
of google.com and imdb.com. I want to implement this
relatively simple search website for IMDb, just a search box. Well, we can kind of preemptively
infer how this is going to work. A shows variable is being set to
the return value of db.execute, where I'm executing SELECT * FROM
shows WHERE the title of the show equals this question mark. And what am I plugging in? Well, just like Google, I'm plugging
in the value of the q attribute from the URL apparently. Then I'm rendering a
template called search.html. And I'm passing in those shows
as a list of Python dictionaries. So that's it. It's only-- that's the entirety
of the back-end logic here. It's a search engine. So what might I want to do next? Well, my mind goes to index.html. Let's just see what the
template looks like. So I can wrap my mind around index.html. OK, it's pretty darn simple. It's just a form that
has an action of /search, which aligns with the route we just saw. Method is get. It's got an autocompleting
input called q, as expected, and a button called Search. So not really that stimulating there
versus past examples we've done. Let me go into search.html, which
is the other template in here. So let me open my terminal and
do code of templates search.html. Close the terminal. OK, this is kind of
like the bookstore too. It's just iterating over the shows,
outputting list item, list item, list item. But I'm using an unordered
list instead of ordered. So there's not that much to this
application to enabling search. But recall that, two weeks ago, when we
introduced HTML and CSS and JavaScript, we completely punted to
the actual google.com. Today we are, if you will,
google.com or imdb.com. So let's go into the terminal
window, do flask run. I'll go back into my browser and reload. So we no longer see the store. We now see IMDb. And it's a pretty simple search box. Let me search for the office
as in the past, click Search. And notice two things. At the top of the URL is ?q=the+office. The plus is a way of encoding spaces in
URLs so that it's just one long string, but that's conventional. And then there's a lot of offices. But why is this? Well, there's the
American one, the UK one. There's some prior ones that
weren't nearly as popular as either. So there's a bunch of offices in there. But we could probably-- we can tie our ideas together here. Let me open another terminal. Make it bigger. Go into src9 shows0. sqlite3 of shows.db .schema. Oh, yeah, there's a lot
of data in this database. Let's do .schema show specifically. OK, so we have the year of every show. So this just means I can play around now
with the intersection of SQL and Python as follows. Let me go back to how about search.html. And, instead of just showing
the title, why don't I do something like curly curly
brace show, quote, unquote, year. Curly curly brace because why? Well, every element in
this list is a dictionary. So I have access to all
of those SQL columns. So let me reload. And now you see, oh, that's
why there's so many offices. They're each from different years. So every piece of data you have access
in SQL you have access to in Python you now have access to in
HTML by just knowing how to stitch these ideas together. All right, so what's
not so good about this? Well, if I go back to the search box
and I search for just office, Enter, there's apparently no show called
literally office, at least lowercase o. Let me try a little more specific,
Office, as someone might type. OK, so there's one version of
Office, but not The Office from 2013. But what if I want to have
more of a wild card search? Well, we can borrow these ideas too. So let me go back into VS
Code here in my app.py. And I think here, instead of
using equal, what was the keyword? Yeah, so we can do like, for instance. But this we have to be
a little careful of. We don't want to resort
to a Python f-string where we plug in values with
percent signs and the like. So this is a little bit
tricky, but it's worth knowing that if you want to
do wildcard searches safely we still should distrust
the user's input. So what I'm going to do
is this even though it's going to look a little cryptic. I'm going to just do a question mark. But, instead of passing
in request.args.get of q, first, let's tuck this in a variable
so the code is a little more readable. q equals that even though that's
not changing anything fundamentally. Let's do this. Let's put my percent sign
here, concatenate q with that, and then put another percent sign
here so that this whole string, after joining and joining
three things together, gets plugged into the question
mark and therefore escaped. You should not use an
f-string for this thing. You should always use the question
mark placeholder as we keep preaching. All right, so now that I've done this,
I think my search functionality just got way better. Why? So if I go back to the form-- and I'll reload to clear everything. I'll zoom in a little bit. Let's type in just office
in lowercase with no the. And I think I should get now every
TV show that has office, O-F-F-I-C-E, in it somewhere even if it's officer. So that might not be quite what we want. But there's indeed a
much broader match here. So more like the imdb.com. But now it's a design decision. Do you want your-- do you
want to be really nitpicky and require users to type in The Office? Do you want it to be capitalized? This now becomes more of a user
interface or design decision, not so much code. All right, well, let's
make one tweak here that's representative all the
more of today's modern apps. It turns out that this
approach of generating new HTML every time a user
submits input or visits a new URL is increasingly dated whereby every
URL is unique, as opposed to apps being much more interactive. So it turns out, there's
this technique in general, in the world of the web,
where you use something called AJAX, which used to stand
for Asynchronous JavaScript And XML. Nowadays, it just refers
to using JavaScript to get more data from the server so
the user's URL doesn't even change, and the whole browser screen doesn't
flash as though the whole page is being reloaded. So this is going to look
a little more cryptic. But let me go ahead and
show you an alternative to this relatively easy approach. A lot of today might
be feeling like a lot. It's about to feel more
like a lot but not in a way that you need to replicate, just to
give you a taste of what more modern web apps are doing. I'm going to close these two tabs. I'm going to go ahead
and exit out of SQLite. I'm going to kill my
previous version of Flask. And I'm going to go into
shows version 2 now. And, in shows2, I'm
going to do flask run. So the app is running. I'm going to go back to my
URL here and just reload. And notice I've gotten rid of
the Search button altogether, minor aesthetic detail. But what I like about this now is
that if I search for O-F-F-I-C-E, you're seeing the
beginnings of autocomplete, which is kind of everywhere. But you can't implement autocomplete
if you have to reload the whole page, reload the whole page. Why? If nothing else, the
user's going to be-- see a big flash of white as
the whole page redraws itself, which is not what we're used to. If I start over, O-F-F, notice the URL
is not changing, nor is the whole page flickering. Just the URL is getting
shorter, shorter, shorter. And if I really go shorter, there it is. Officer, now I have only this
many bullets on the screen. There's no more below the break. So how can I do this? Well, let's try to
infer a little bit here and be demonstrative
of how you can infer how a third-party websites are working. If you want to-- if you
want to mimic their behavior or just learn better how they
work-- so let me do this. Let me open up developer tools. Let me open the Network tab. And let me search for O. And watch
what happens in my Network tab even though the URL of
the page is not changing. O. Apparently, an HTTP request was
sent from my browser to this URL, which is essentially
representing /search?q=O. Notice that the response was 200. And what is in that response? Well, let me click on that row. Let me click on response. And, very interestingly,
notice what came back. It's not a whole web page. It's just a bunch of li elements,
which you can kind of infer are probably the ones that are getting
snuck into the page dynamically. So if I go back to the
top, there's no ul here. It's just a bunch of lis. And watch what happens this time. Let me close that panel. Let me search for O-F
without even hitting Enter. Here we go, O-F. Now
there's a second request. And If I zoom in, there it is, q=OF. If I click on that and zoom out,
you'll see a whole bunch of lis. But let me claim there's fewer of them
now because fewer strings match O-F. And if I finally type in
office, let alone the office, notice now, at the very bottom
of this, every time you're doing an autocomplete, it's sending
an HTTP request, HTTP request, HTTP request. Back in my day, you'd
have to click Submit. The whole page would reload, and
you'd see the list again and again. This is more modern. And this is an example
indeed of what's called AJAX, Asynchronous JavaScript
that's getting you more data and slipping it into
your existing web page. So how does this work? Let me go to VS Code here. Let me open up a new terminal. Let me go into src9 shows2. And let me open up,
for instance, the index template, which is the entry point. Everything at the top is sort of boring. Here's the head of the page. Here's the input. I didn't even bother with the
whole form because I'm not even submitting a whole form. So I don't need an action or a
method or anything like that. I'm just using it as
a dumb input box only. But notice that it is indeed
an input of type search. Here, now, is an empty ul, initially. So this is why, when you visit the
page, you see a text box but no bullets because the list is empty. And, in fact, watch this. If I click on elements in the Developer
Tools, click on body, expand the body, there is the ul. If I zoom in, there's
nothing inside of it yet. That's why we see no bullets. But if I go back to the template,
here's some JavaScript code, which we haven't spent
much time on, but you can start to wrap your mind around
this line by line as follows. Give me a variable called input. Set it equal to the element from
the DOM, the tree in the computer's memory for the input element, so
that rectangle from our pictures from last week. Then listen to that input
for the input event. I showed briefly last week a list of
like dragging and clicking and mouse up and mouse down and key up and key down. And input just means generally
inputting into a text box. Here, I'm calling a function. It's an asynchronous function
in the sense that there-- it's going to get back to me eventually. If the server is slow, it
might take a moment or two. But, inside of this function, give
me a variable called response. Go ahead and fetch the URL whose
path or route is /search?q= and then concatenate onto that whatever the
user's input that is the value of that input. Then create a variable called shows,
go into that response from the server, and get its text value. So we're not going to
spend much time on this. But fetch is a function that
comes with browsers today in JavaScript that lets
you make an HTTP request. Fetch more content from the server. And you essentially pass it in the
URL that you want to fetch or get. This function here response.text
just grabs the text from that response, which means if I
go to the Network tab and I type in O as before and I click this,
response.text is all of this stuff that we manually looked at earlier. So you're just getting all of the text
that came back from the server that happened to be lis. Lastly, go into the document,
the DOM, select the ul element, go into its inner HTML, and change the
inner HTML to be that text, aka shows. And so what's happening here--
and watch this now when I-- let's reload the page. Let me show you the Elements tab. Let me highlight and zoom
in on the ul element. In the bottom of the screen,
nothing's there yet but watch. As soon as I type O, and I
get back all of those lis, and they get crammed into the inner HTML
of the ul, watch the ul as I type O, there's now more stuff there. And, indeed, if I expand it,
there are all of the lis. That is the inner HTML that just
got automatically populated. And if I type in more, officer,
notice that if I scroll down, there's only so many of them. And if I just type in
nonsense, notice it's back to 0 because there's no text
coming back from the server. This then is an example of
the beginning of an API. And I've used this term over time, but
an API is an Application Programming Interface. And it's a standardized way,
generally, of getting data from someone else's server or
service into your application. Now, in this contrived scenario,
I am both the web developer, and I'm the author of the API. But they're implementing-- they're being
implemented in the same application. But you could imagine, actually,
querying amazon.com for actual books and actual prices and
actual photographs thereof from their servers instead of your own. And so they might very
well send to your server those kinds of responses by just sending
you a whole bunch of text that, again, might just look like all of these lis. But the lis is a sloppy
way of sending information. Nowadays, it's not common to use
HTML, nor something called XML to send back your data. Rather, it's more common
to get back something called JSON, JavaScript Object Notation. And odds are, for your final project,
if you do anything with an API, you'll encounter this in the real world. And JSON looks very, very similar
to what a dictionary and a list looks like in Python. The syntax is almost
the same as you've seen. However, in the world of JSON,
JavaScript Object Notation, you have to use double
quotes around your strings. You cannot use single quotes. And, generally, it might look like this. So here, for instance, is a
response from a more modern version of a server that's not sending back
li tags and a messy bunch of HTML. It's sending back something
that's more machine readable. So this text, ugly as it is, is much
easier for a JSON parser, a function to read, because it's all standard. It's all square brackets, curly braces,
quotes, colons, and all of that. And, even though it looks
like a mess on the screen, it's very standardized,
unlike li tags which who knows what might come
back in those aesthetically? Since there's so many HTML
tags, not to mention CSS. So let's make a change here. Let me go back to VS Code here. Let me close this tab and quit out
of this version of my application. And let me show one final version of
shows by going into shows version 3. And, in this version, I'm
going to go ahead and do this. I'm going to open up app.py. And, in app.py, I'm going
to import one more function from the flask framework called
jsonify, which is not necessarily a technical term. It just means turn something into JSON. So what does that mean? Well, notice that, down here, I have
a search route that looks like this. But, before we look at that, which
is going to end with this spoiler, like jsonify, let me actually do this. Let me open up also
from shows version 2, the previous version of this,
which looked like this here. This was the app-- the search route from
the previous example. Here's how I got q. Here's how again I did the escaping
of the user's input with the wildcard. But notice that I also did this. If the user got back no results, then
I just gave it an empty list instead. But let me show you two from last
time. search.html looked like this. And shows2 in templates, shows.html-- whoops. search.html, what you'll see
here is the very simple template that generated all of that text,
all of those lis just again and again and again. So we're going to get
rid of that template in this third and final version here. So if I go into shows3, notice
I'm doing the same code as before. I'm building up a Python list of shows
that results from that SQL query. But, instead of passing
it into any template, I'm just jsonifying that text. What that means is that
what I'm essentially going to send from the
server to the browser is literally something that looks
like this here on the screen. So if I go back to VS Code here and
run flask run in this final version, and I go over to my other
tab here and reload, I have the same response as before. And it's actually
going to work the same. Office is going to still work the same. But if I undo that and go to inspect
and go to my Network tab here and now search for O,
same paradigm as before. There /search?q=o, let's click on that. But notice-- and let me make
this even bigger on the screen-- this, even though it looks more verbose
and it is, it's way more standardized. And this is the, quote, unquote, "right
way" to send data over the internet when you want two pieces of software to
generate and consume it respectively. Here, now, we have not only
just the titles of the shows, but I've even been generous and
sent the number of episodes, the unique ID, the title,
the year, I've essentially sent the entire SQL database in a
standard, portable, textual format that my code can now use. So, when you think about most any modern
application, we come back to Gmail. When you access something
like Gmail in your browser and you are waiting and waiting and
waiting in a new email comes in, what has happened? Tonight, for instance,
open up the Inspector. Watch the Network tab. And, odds are, every few
seconds, every few minutes, you'll see some kind of response coming
from the server, maybe JSON, maybe some other format,
containing your email, including at least its
subject line because that's what's adding more and
more HTML to the browser. Indeed, if you open up the
Elements tab and just watch it in the most watching-paint-dry
kind of situation, you'll see probably more and more
emails appear in the underlying DOM or document object model
inside of the browser. So I can't stress enough
that, even though we've spent just a few weeks on SQL
and the HTML, CSS, and JavaScript and now Flask together,
that is how the web works. That is how all of today's
modern apps work and, hopefully, you too with your final projects. That's it for today. We will see you one last time
next week for the end of CS50. [APPLAUSE] [MUSIC PLAYING]