CS50 Video Player
    • 🧁

    • 🍭

    • 🍍

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