字幕列表 影片播放 列印英文字幕 [MUSIC PLAYING] DAVID MALAN: All right. This CS50 and this is lecture 7 wherein we pick up where we left off last time in talking more about Python, but this time using Python for even more powerful purposes, an alliteration I didn't intend until I just said it there. But the goal here with Python now is to actually use this language to generate another language, in particular HTML. And what we'll do is start to provide all the more of a mental model for what you would call separation of concerns, whereby when you design more complicated web based, or rather when you design more complicated software, you tend to try to adopt certain design patterns so that you're not just writing everything in one big file that gets a little confusing, unwieldy to maintain and to add to over time. But you start to separate your concerns, and your functionality, and your features, and your collaborators' work into different files and different methodologies. So we'll try to give you some of these industry standard approaches today and why they actually solved problems. But, of course, how did we get here? A few weeks ago, we talked about this thing, this virtual envelope that just represents packets of information, zeros and ones, going across the internet from point A to point B. And among the messages in these envelopes might be for web requests. And we talked about HTTP, a protocol, hypertext transfer protocol, that just sends message like this inside of the envelope. And this is requesting, of course, the default page of a website as implied by the slash. And in many cases, but not always, this implies a default file name of index.html. But that's a human convention, not a requirement. Hopefully, the server responds with a numeric code equal to what integer? 200, hopefully, a number that we never actually really see unless you get more comfortable and start poking around Chrome's inspector and you look at your network traffic. Right. So here is an actual successful response, which means everything is indeed OK. And if the browser looks a little farther down in the virtual envelope, if you will, it will see another language called HTML, HyperText Markup Language, which is the stuff we wrote a couple of weeks ago and you'll continue writing this week as you now dynamically generate websites. But what we'll introduce today is what's also known as a framework, or, more precisely, a microframework, because we'll see in just a few minutes time that it can actually be really annoying and really tedious to generate websites automatically using Python if you have to write Python code alone. And so humans, over the past several years, have noticed, wow, every time I make a web application using Python, I find myself typing the same lines of code again and again just to get started, or when I'm collaborating with someone else, I find that, ooh, it's a little hard to collaborate if we're all working in the same file. So humans have invented what are called frameworks. And this is an example of code written in Python but using a framework called Flask. So whereas Bootstrap, which you've played with a little bit, is a framework for CSS and JavaScript, they have some dynamic features as well, Flask is a framework for Python that just gives you some library code that you can use freely in your own applications. And it just makes it, ultimately, a little easier to get your work done and build the thing you want to build as opposed to having to reinvent the wheel again and again like people before you. And so here is a methodology to which I alluded earlier just to have a mental model for the coming weeks. So up until now, pretty much every program we've written in C and most recently in Python you could call like controller code or controller logic or business logic. Right. It's all about getting something done logically, usually in one file, maybe two files if you have another helpers.ce or header file or whatnot. But we've been writing logic. But today and two weeks ago when we will focus on web programming in HTML and CSS, there's the second part of our world. There's like the logic that gets the interesting work done with loops, and conditions, and all that. But there's also now going to be the aesthetics, the stuff that the user actually sees and the way you present your data. So we're going to just introduce two letters of an acronym today moving forward. Anytime you start writing logical code in Python, it's going to be called your controller. It's sort of like the machine that operates your entire web application, but anytime you do something more aesthetic, we're going to call that your view code, so C and V being the letters here. And this just refers to a separation of concerns. Your logic goes in this file. And your aesthetics and formatting go in this other file. Next week, we'll introduce databases in SQL, structured query language. And we'll introduce an M here, because the acronym that's in vogue for quite some time now is MVC, though there are alternatives to this mental model. And that just means there are three different types of things you should be thinking about when building an application. But today we're going to focus on these two, controller and view. And up until now, we've pretty much been doing entirely controller stuff when writing code. So the motivation, ultimately, will be to get to the point of building, essentially, this, the freshman intramural sports website, which I was the first one to make back in 1996 as a sophomore, maybe 1997 as a junior, after having only taken CS50 and a follow on class CS51. And even though this is horrifying to see nowadays, underneath the hood was a whole bunch of controller logic and a whole bunch of model code, even though I don't think MVC existed as an acronym back then till humans figured out the pattern. But what it did have via the menu up here was a whole lot of functionality. So no longer did you have to walk across campus filling out a form and drop off a sheet of paper in an RA or a proctor's dorm room. You can instead just go on the web, which all of us surely take for granted today, fill out a web-based form. And then at the time, the proctor in charge of froshims would get an email confirming who had actually registered for a sport. And eventually we added CSV files, comma separated values files like spreadsheets, in which the data was saved. Then we had this really cool tournament bracket thing, which was all very dynamic and impressive I'm sure at the time. But we'll focus on really just the fundamentals today. So how do we get there? Well, let me go ahead and open up CS50 IDE and propose that if we want to make a website, a web application really, and by application I mean something that does have logic. By website, it's generally something that's static. So that's what a web app is. It's something that changes based on who's using it, when you're using it, and what you're doing with it. Let me go ahead, and in the most annoying tedious way possible, implement a web application using Python. So I'm going to go ahead and open a file called serve.pie to serve up a website. And I'm going to go ahead and import some library code. So from HTTP server, import something called base HTTP request handler and also something called HTTP server. And then I'm going to go ahead and use this keyword class, which is HTTP server, server request, request handler, base HTTP request handler-- handler. Then I'm going to go ahead and in here implement a function called def do_GET, which implies-- this is a function in Python that should be called anytime a user visits my web server via GET, the type of verb that we've talked about in the context of the web. So suppose that my only goal here is to make a web-based application that for the moment just says hello world. We know how to do this with HTML, but with HTML that's just a file. Let's write a program that not just prints hello world, but generates a web page containing hello world. So the way I might do this is this. I can literally say self, because self refers to the web server in this case. And I can go ahead and send a response code of 200, just kind of presumptuously assuming everything's going to be OK in a moment. Then I'm going to go ahead and send a header, which recall, we've discussed briefly in the past, whereby this header is going to be content type and its value is going to be text HTML, which is a clue to browser that it's receiving not a GIF, or JPEG, or something else, but an actual HTML page. And then I'm going to go ahead and say that's it for the headers. I'm going to call function called end headers. And then lastly, I'm going to use Python code to dynamically generate my website. Now, what does the website have to have? Just hello world at the end of the day, but there's a whole bunch of html we need to do in order to get to that point. So I'm going to use a function called wfile write in order to write out the following. Let me go ahead and write out doctype-- whoops, exclamation point doctype HTML. Then let me go ahead and do the same thing, wfile.write, let me go ahead and copy paste this moving forward, which, of course, is always a bad instinct. Then let me go ahead and output HTML lang equals en for English. And notice I'm using single quotes inside my double quotes so that Python doesn't get confused. Then let me go ahead and output head tag here. Then what comes after the head tag typically? Yeah, so usually title lately. So title will be like hello title, close the title tag. And now we're going to go ahead and what comes after title? Close head, I think, if I'm doing this right. And then after this we probably have a body. And then oh my God, this is the worst way to build a website. But let's go ahead and now say something simple like hello world. And now let's go in here and say something like body. And then finally, let's go ahead and end the page and say slash HTML. Done. OK, and now I actually need to configure the server to listen on a port. So let me go ahead and define a TCP port of 8080, which we've been using. Let me go ahead and define the server's address as being, oh 0.0.0.0, which is what the IDE uses by default, like a lot of servers. And then let me create the web server, HTTP server. server_address HTTP server request handler, httpd.serve_forever. OK, that is how you make a web-based application that dynamically generates HTML. In retrospect, I really regret typing all of that out because the whole point now is to throw this code away. Like, this is why frameworks exist. If my goal quite simply at hand is to write Python code that dynamically generates HTML, then calling lots of functions like write, which is essentially the equivalent of print in this context, is just tedious. I got bored writing it, I accomplished so terribly little at the end of the day. And so frameworks exist to make our lives easier. But what's going on underneath the hood is exactly this. When we start using this framework called Flask, it's just going to make all of this automated for us. It's going to make it easier to specify the IP address that you want to run your web server on, it's going to make it easier to specify the port number that you want your server to be listening on, and it's going to make it easier to respond to get requests because Flask will take care of the implementation of some function like this one called do get. So all of this is there underneath the hood, but the flask framework gives us essentially an abstraction on top of that. So what do I actually mean by that? If we want to distill this now into a simpler web application, first let's go ahead and do this. Let me go ahead and open up a terminal window and let me go into my code here and go ahead and run Python of serve.py. And you'll see nothing seems to be happening just yet. So I'm going to go to cs50 IDE web server to open up a tab containing the web server that I'm now running. And you'll see that's it. That's the only thing I accomplished right now. It's not dynamic to be sure, but there is code and Python code there with which I could actually do something dynamically. But let's instead do this now with Flask, this framework that seems to make our lives easier. I'm going to go ahead and make a program called application.py, which is just a convention. I could call it anything I want. And I'm going to go ahead and say the following. Let's first go ahead and import this framework called Flask. And specifically import capitalized flask, which is the name of the framework itself. And then let me preemptively import a couple of functions called render template and then this special global variable called request. You would only know to do this from reading the documentation. But now let me go ahead and say, hey Flask, could you please give me a web application? And this is copy paste. You need this at the top of any Flask application, and it just means turn this file-- application.py-- into a web application. Then lastly, I'm going to go ahead and do this. I'm going to tell Flask I want to build an app that has a route that's listening for slash inside of that virtual envelope. And whenever, Flask, you see a request for slash from some user, go ahead and call this function which I arbitrarily called index, but I could call it foo, or bar, or whatever. And what I want you to do is this, return hello world. And that's it. So all of those other lines I tediously wrote out a moment ago are now distilled into just 7 lines, which none of which are completely obvious how they work just yet. But if you assume that this means give me access to the library, turned my file into a web application, and listen now for get requests on slash, it kind of makes sense and fits into the mental model that we introduced a couple of weeks ago with HTML and CSS itself. And to run this server, what I'm going to do now in my hello directory, which is where a online copy of this is on the course's website, I'm going to go ahead and quite simply say Flask run. So Flask is not only a framework or code that you have access to as free and open source code, but it's also a program you can run at the command line that also figures out how to turn the server on and just run it forever. You're going to see some diagnostic output at first glance, most of which you don't have to care about. But there will be a few URL that's spit out which is based on your own user name. Mine today is jharvard 3 just for demonstration purposes. And if you click on that URL and then click Open, you'll see now this version of the same application doing just this. Now, I'm kind of cheating, though. Because if I open up Chrome and view my page source, notice that of course I'm cheating because all I've done is print out hello world. And if I view the source of this page, I'm literally only going to say hello world and no actual HTML because I literally didn't type out any HTML in my file. But it turns out that Flask makes this easy, as well. So let me go ahead and stop the server here for just a moment. Control-c is your friend, it gets you out of whatever program is actually running. And let me go ahead and do this. Let me go ahead and not just return quote-unquote hello world as a hardcoded value. Let me go ahead and return the rendering of a template called index.html. And so this is a feature of Flask, too. If you want to separate your logic-- your controller-- from your HTML-- your view-- you literally put the former in application.pi, and you put the latter in an HTML file like this. And you tell the controller code-- application.py-- show the user, to render for the user the index.html file. So where do I put this? The convention would be to make a directory called templates. And then in that directory, go ahead and put a file called index.html. And if I go ahead and open up the file that I already created in advance of class here, let me just show you what this looks like. And then we'll take a look at it in a browser. Here is HTML now with a pretty fancy feature that we're about to reveal the utility of. What jumps out at you is new in this file? AUDIENCE: Two curly braces on the name. DAVID MALAN: Yeah, two curly braces. And somehow or other that used to be saying world, but in my final version of this I actually am hinting at some more powerful capabilities. This kind of looks like a placeholder, if you will. Maybe someone's actual name. And here's where the power of something like Flask comes into play. It makes it really easy to do something like this. So let me go ahead and actually turn this into something a little more interesting. Let me go into application.py. And before I actually render this HTML file, let me go ahead and do this. Let me go ahead and declare a variable called name. And let me go ahead and check the incoming request, the inside of that virtual envelope. Let me check its arguments or any of the HTTP parameters that were passed in and try to get something called name like this. Let me go ahead and save that and then notice this. Render template takes multiple arguments. The first one should be the name of the template you want to render. But if you want to pass in data dynamically, you can use named parameters in Python. Recall from last week that you can do things like this, x equals y, and z equals w. You can pass in the names of the things you want to pass in and their corresponding values. So if I want to pass in a name variable and set it equal to the name variable that I just defined, watch what we can do here. Let me go back to my console. Let me go ahead and rerun in my hello directory, which is available online, Flask run. And now let me go over to this where it previously said hello world. Let me now, just like with our Google example a couple of weeks ago, type in not q equals cats, which is what we did last time, but maybe name equals David to simulate a get request. And if I did everything right, when I hit Enter I now see this dynamically. And if I change this now from David to, say, Veronica, of course, this is going to dynamically output this. Can someone now try to break my code? Propose an input that I should try to see if I messed up somewhere. AUDIENCE: No entry. DAVID MALAN: OK, no input. Or what's that? say again? Name equals name, I like that one, too. So let's try that, name equals name. So OK, I mean it's kind of maybe like a grammatical bug or semantic bug, but not really a code bug per se. That's just user error. But what if I just get rid of it? OK, that just looks a little stupid. So an aesthetic bug, but we should probably start handling this. What if I get rid of name altogether? Interesting. It seems that my final version actually has some built in functionality. So where is that coming from? Well, what if I did this? It turns out that I could say something like this. If not name, then go ahead and set name equal to world would be one way of doing it. Or I could actually use the function here. Turns out that this get function can take a default value. And so if you read the documentation, you'll see that the second value you provide will be used if the user hasn't provided one. And so indeed, if I reload now and see nothing, I get world. And if I instead do name equals, say, Brian, I get that dynamic output. And so when I say web application, this is just a hint of what I mean. This is dynamically generated content. It's not hardcoded, because it's actually coming from the user. So when Google implements its slash search web application, this is the kind of thing they're doing. It's way more involved, of course. They're searching a database, looking for cats, and dogs, or whatever it is you're searching for and then generating HTML. But notice with just this simple approach can we ourselves generate any HTML we want dynamically. Because all we have to do in that template called index.html is exactly this. Hello, comma, and then a placeholder where name is the variable you're passing in. And so to be clear, it doesn't have to be called name. I could do something like foo, which would be a little nonsensical. But if I do that, the variable I plug in needs to be called foo here. And so there's a one to one correspondence between the things before the equal signs and where they get plugged in down here. Any questions, then, on this simple example, but building block? Yeah? AUDIENCE: [INAUDIBLE] dynamically generated. DAVID MALAN: Sure. By dynamically generated, I mean I have not written in advance by typing it out manually a web page that says hello David, or hello Brian, or hello Veronica. Those pages are all generated dynamically based on user input. I wrote most of those pages. I wrote everything up into and after the comma, but then-- sorry, up to the comma, but then the names are dynamically added. Good question. Other questions? All right. So why don't we rewind to 1997 or so and see if we can't build a more dynamic web application that actually allow students to register for something that's a little more compelling than just providing their name? So let me go ahead and open up froshim0, which is the first larger scale application we have here today. And notice that I have a few files. So already things are going to escalate quickly whereby we're going to suddenly introduce multiple templates, but we'll do this in order to solve a problem. But first let me go ahead and open up application,py, which just like your main function in C is kind of the entry point now to a web-based application. So notice that-- let's start like this. Let me go ahead and delete that and start from the beginning here. Let's go ahead and do this. In froshim0, we have this default route of slash, and notice that it's going to render index.html. So when you start to read someone else's code, you kind of follow these breadcrumbs. So let me go ahead and open the same folder, froshim0. Let me go into-- let me go ahead, rather, let's do this from scratch, actually. Let's do this. Index.html, and let me do the familiar doctype HTML. Then let me go ahead and do an HTML tag here, it finishes my thought for me. The head tag here, the title tag here, froshim0 will be the title here. Let me go ahead and create a body. And now for this web page, I want to go ahead and have a few things via which the user can actually register for froshim. So let me go ahead and have just some title text here, like register for froshims like I did back in the day. Then let me go ahead and start a form tag. And then in here, what information might a student want to provide when registering for something like a sports team? AUDIENCE: Name. DAVID MALAN: OK, the student's name. So input type equals text. The name of this input probably shouldn't be something generic like q, it should probably be more descriptive like name. So its a little weird looking, but name equals name. And we'll go ahead and do this. And if we really want to be fancy, we can do like a placeholder text of name just to in light gray text show the user what we want. And then back in the day, minimally the students registering for sports had to provide their dorm, so the building in which they lived. So in HTML, we've got a bunch of input types. We've got text boxes, turns out there's text areas which are even bigger, check boxes, radio buttons, what's most apt perhaps for choosing your dorm? AUDIENCE: Dropdown list. DAVID MALAN: Like a dropdown list, otherwise called a menu. But which tag? AUDIENCE: Container? DAVID MALAN: Not a container, a little more precise than that. With what tag can you generate a dropdown list if you've done this before in HTML? AUDIENCE: Select. DAVID MALAN: Select. So it's not perfectly clearly named, but it's, indeed, a select menu by name. And so I can actually do this. Select, and the name of this field will be dorm, for instance. And then inside of this I'm going to go ahead and have a few options. So one option might be let's say Apley Court, which is one of the buildings in which freshmen live. There might be another one called Canoday, and then there's going to be bunches of others. And then notice, too, if you've never used a select menu, which you wouldn't have really had occasion to yet unless you've done this before, these options also have to have values, which for my purposes are going to be exactly the same. But whereas what's between the tags is what the human sees, it's what's between these quotes as the value of value that the computer actually sees. So these are the words that populate the dropdown menu, these are the values that actually gets stuffed into the virtual envelope that the student him or herself actually see. So let me go ahead and save this. Let me go ahead and now open up my console. And I'm going to borrow a little code just so that we can do this from scratch here. So let me go ahead and grab from froshim0 my application.py file and go into my workspace. So let me go ahead now and run Flask run wherein I have this application.py file. I'm going to see the URL at which my code now lives. And if I open this up, I'm going to see an internal server error. So intended at some point because internal server error, recall, was one of the more arcane status codes, 500, that you probably have not seen unless you're visiting a website where something has actually going wrong. So how do I begin to figure out what has gone wrong now that I'm actually writing code and not just writing hard coded HTML? Well, all of the clues are going to be found here inside of the console window. So when you're running Flask you are running a full fledged web server. You are listening via TCP/IP for incoming requests from users. And so what you'll see in the console is not just the diagnostic output when you first start Flask, but you're going to see all of the error messages that might actually happen thereafter. And this is a little cryptic looking. Frankly, it's overwhelming to see this text at first glance. But whereas in Clang and in C it generally helps to look at the very top, sometimes the error messages here in this context of Flask are kind of toward the bottom. And here we have template not found. Template because it can't find index.html. And that's just because I screwed up. So let me actually exit Flask by typing control-c. And if I type ls in my directory, notice that I haven't quite practiced what I've preached. It's perhaps a little subtle, but I haven't organized myself in the right way. What have I done wrong? Yeah? AUDIENCE: Didn't make a templates directory. DAVID MALAN: Yeah, it's kind of a silly mistake. I didn't make a templates directory. So you can do this in a few different ways. By the folder icon up here, you can create a new folder by right clicking or control clicking. Or in Linux you can do make dir for make directory. And so I can do make dir templates enter. And then I can move my index.html file, which I wrote a moment ago, into my templates directory by just using mv for move. And now I can go ahead and run Flask run, cross my fingers, go back to the browser tab and click reload, and voila, now I actually see the form. So have these kinds of instincts. I didn't actually intend that, but I forgot to create the folder. I got this server error. You don't have to just stare at the browser, which is not going to have much information. But if you look at the console, the terminal window that you have control over will you see those clang-like error messages. So here we have a basic HTML form. It's not complete because I didn't bother typing out all of the dorm names, but I do have an input of type text as well as the Select menu. And I'm also kind of missing a key detail, it seems. What should I probably add to this form? AUDIENCE: Where you're selecting. DAVID MALAN: Well, I'm selecting-- I could be selecting dorm, so I could clean this up in a couple ways. I also am missing a Submit button. Now, it turns out you could probably hit Enter and it would actually be submitted by default. But we can fix this. So let me go into index.html. Let me shrink this tab just a little bit and let me fix both of these things. So let me go ahead and open up this latest version, which is now in that templates directory. Let me go ahead and at the bottom here do an input type equals submit, the value of which is going to be a register, just to make clear to the human what's going on. Let me go ahead and go back to my form, click reload. And nothing's happened just yet. And that's because by default when you make changes to some of your files, Flask is not going to notice. And we'll fix this actually in the coming problem set by adding more code and a little more complexity to automate this for you. But when in doubt, just control-c to quit flask. Then go ahead and rerun flask, that will reload all of your HTML, all of your Python code. And if I now go back here and click reload, we'll see the Register button. So there should never be any surprises. And if there are, just try to get to the diagnosis thereof. This is also a little unclear, too, to what this menu is. So it turns out that if you actually create a bogus option like this that has no value and say something like dorm, you can save this. Let's go ahead and restart Flask and reload the page here. You'll see that now you see dorm. Unfortunately, this is kind of stupid because now dorm is where you can literally live apparently, which doesn't quite feel right. So there's HTML fixes for this too. I can actually go in here and technically say disabled, so you can't actually select that. Now if I rerun Flask and reload, now it still shows it there. But because I already selected Apley Court, you can see it in gray. And we can actually be a little more specific, if you want to not only disable it, but select it by default and then go ahead and reload the page here, now you'll see hopefully what's familiar on most websites. It says dorm, but it's disabled. And even though the silly checkmark is there, you're forced to choose an actual dorm. So these are minor aesthetics, but the kind of things you now have control over. So what's going to happen? I haven't actually specified where this form should go when I actually register because I'm missing some key details on the form tag. And we haven't done this in a couple of weeks. But when we were playfully reimplementing Google, what else did we add to the form tag? What attributes? Anyone remember? Yeah. Oh, say again? Action, which means what? AUDIENCE: What to do. DAVID MALAN: What to do. All right, so the action we want to take, even though this is not necessarily perfectly well named, is where do you want to submit the form to? And I could actually submit this in a few different ways. I'm going to go ahead and say, you know what, submit it to a reasonably named route slash register. Google, for instance, might have code that instead says to submit their form to slash search. But we're not searching for something, we're registering. So the name of the route is entirely up to us. And via what method should we submit this information? What are our two options? AUDIENCE: Get or post. DAVID MALAN: Get or post. Get tends to be the default if you don't mention in links. Why might you want to use post instead? AUDIENCE: You want it to go to a database in order to do something. DAVID MALAN: Yeah, you want it to go to a database and do something. And the right verb to use there is to post information as opposed to getting information. And even more specifically when it comes to privacy, when you use post, the information doesn't end up in the user's URL, and therefore doesn't end up in his or her history, and therefore doesn't end up in a place that siblings, or roommates, or anyone else can actually see just by poking around. So we'll, indeed, go ahead, because this is my name, and dorm, and maybe my phone number, and email, and credit card number, and the like on some other website. I'm going to use post for that instead. So the catch here is that this week, we now have the ability to implement slash register. Two weeks ago we could just send people to Google's slash search, but now we have the ability to make our own routes. But how many routes have we defined thus far in application.py? Just the one. And again, some new syntax with the funky at sign and the route keyword here. But let me actually just intuitively guess that if I go ahead and say app.route slash register, I bet I could implement a second route and tell the web server to listen in two places, on slash as well as on slash register. But if I wanted to listen specifically on post, it actually has to be this. Methods equals quote-unquote post. Because by default just for convenience, it assumes only gets. So I need to add that. You'd only know that from the documentation. Now I'm going to go ahead and define a function. This is slightly an annoying feature of Flask. You have to give the function a name even though you'll probably never reference it anywhere. So I'm going to go ahead and just reasonably call this register. And now I have a to do. What am I going to want to do when the user clicks that Submit button? AUDIENCE: Store it. DAVID MALAN: I want to store it somewhere. So I probably want to store it. But what might I want to do first before he or she is actually allowed to register, even though they've clicked that submit form? Maybe confirm their information, right? Because if a lazy user comes in, or if a user accidentally clicks the button or hits enter, they might actually submit nothing-- no name, no dorm. That's not useful information. It could just be perceived as spam. So we probably want to ask some kind of logical question like if they didn't give us a name or they didn't give us a dorm, yell at the user in some way. So I'm going to go ahead and do that. So let me actually express this in Python, kind of like we did last week with some error checking. So recall that the user's name can be gotten from an HTTP parameter. From the request.args get, and then ask for the name parameter. Their dorm, meanwhile, can come from request.args get dorm. And again, this request.args is something we gave ourselves access to up here when we said, hey Flask, please give me access to the user's request, that virtual envelope. Request.arg refers to all of the HTTP parameters in the URL. And get is just a function or a method built into that special Flask feature that allows us to get a specific parameter like name, or dorm, or anything else. And recall that in Python, what's kind of nice is that you can say pretty English-like, if not name or not-- not dorm, let's go ahead and reprimand the user. For instance, we could say failure because he or she did not actually cooperate as we intended. Otherwise if they did cooperate, I'm going to go ahead and render template success. And we'll flesh this out in just a moment. So I've got two scenarios handled. If they didn't cooperate or if they did, render quote-unquote failure or a full fledged HTML template. So now that I've implemented slash register and I'm listening for a route by a post, let's go ahead and reload the page for good measure. Type in my name. Not going to tell you my dorm, but you're going to notice as much. OK, so now the server has noticed that I didn't actually cooperate and provide both a name and a dorm. And so it's returning to me just quote-unquote failure. So that's good because now I know, all right, I did something wrong clearly. Let me go back let me go ahead and maybe do Canaday here. And now let me go ahead and register. But, but, but, but, this I know in advance is going to err. Why? I don't have a success.html. OK, so let's preemptively address this. Let me actually go in here to my templates directory. I'm going to go ahead and create a new file called success.html. Let me go into the templates to save it there. And you know what, success can be a pretty simple page. And let me open my index page, let me copy that, let me go into success, let me paste this. Let me get rid of all of that body and just say success, for instance. So let me now go ahead and go restart Flask, because I've made a new template. Let me go ahead and reload the form just for good measure. Let me go ahead and give you my name this time, and OK, I live in Canaday, and register. And what did I do wrong this time? So it turns out you can't do it this way, obviously. [LAUGHTER] So when you're actually submitting information to via a form via get, Flask very cleverly puts that information in a different place. Because by definition, as I claim very correctly earlier, in request.args are all of the key value pairs that are in the URL that are coming in from the user's request. But when you submit via post, for reasons I wish were now otherwise you actually have to access those values via a different variable. So instead of using request.args, you have to use request.form both here and here to make clear-- and this is horribly named for exactly the reasons that I think I'm tripping over here-- because they actually are both coming in via a form, via a get or post. But Flask puts get arguments in args and puts post arguments in form. Thereby leading clearly to potential confusion. But if I go ahead now and load this version of the site and I keep hoping, I'm going to go ahead now run Flask, restart this page, type in David, give you my dorm, and register and successfully register for froshim. See how easy web programming is? So hopefully now we can at least focus on the structure of what I've done and now begin to improve it. Because notice that I kind of did not practice what I preached a moment ago. What habit did I violate when I whipped up success.html? How did I get to that point? Yeah? AUDIENCE: Copy and paste. DAVID MALAN: Yeah, I copied and paste. Which again, usually in programming not the right thing to do. Might get the job done super fast, but it's probably the wrong instinct, because it's going to get harder and harder to maintain. Now, why is that? You've played HTML a couple of weeks ago. And recall from that problem set when you had to make a home page, you probably found yourselves copying and pasting across your two, or three, or four pages because you wanted them to kind of look the same. And therefore it made sense for them to have some commonalities. But in HTML alone, there was no way to say use the same layout for my whole site-- use the same color scheme, the same fonts, the same CSS-- but just change the body of the page for each individual page. And so some of you very rightly on discourse and beyond like posted questions asking, could you do this? And you can't really in HTML alone. But now that we have access to Python, an actual programming language they can do things logically, now you can actually start to factor those things out, too. And notice in this file, success.html, as well as in index.html, what are some of the commonalities, suffice it to say? The form is only in one of them, but what else is obviously redundant everywhere? The title, the head of the page more generally, the doctype at the very top, the body tag alone. And you could imagine there'd be even more details like from your own home pages that you wanted to be the same across multiple pages. So let's actually take a look at a refactorization of this code, the one I did write in advance in froshim0, and you'll see why it actually makes sense to have not only multiple files, each of which represents one of your routes or your views, but also to have this file called layout.html. In Flask, when building a web application that you know is going to follow a certain structural pattern, commonalities across all of your pages, you can actually do this. So in this file here, layout.html is a whole bunch of hardcoded HTML. And it's pretty simple. It's got my HTML tag, head tag, title tag, body tag, and a few other things, but that's the general structure of the page. And notice it has this funky syntax in the middle. In white here is what's called the block. This is now Flask specific. Just like Flask supports those two curly braces on the left and the right that says put a value here, flask also supports this other notation, curly brace percent and percent curly brace that actually allows you to put placeholders for actual chunks of HTML. Not just variables, but actual chunks of HTML. And so this layout you can think of as a mold or a template literally that all of your other pages are going to be structured based on, but they are going to vary in this line and only this line. And we're going to put as much HTML between the body tags as we want, the open and the close tag. This just indicates to Flask this is the stuff that should be changing. So if I now look at my index.html, which recall earlier contained my form index.html. Notice that here's the form, and I finished it earlier. I went ahead and typed out all of the freshman dorms, not just the two of them. And you'll see that the file starts almost the same and then continues with more stuff. But notice what's missing from index.html this time. No doctype. No HTML tag. No head tag, no title tag, no body tag. All of the common stuff has been factored out. But there's some funky new syntax that, again, is Flask specific. This first line is the link between this file and the layout. That first line says, hey Flask, this index.html file extends the definition of layout.html. So it says grab that template and plug myself in there. What do you want to plug in? The same syntax here. When you actually put stuff between the block tag and the end block tag, which is down below, that's when you say to Flask, go ahead and take this stuff and plug it into the placeholder in the layout. So meanwhile, the success page also now can be a little more sophisticated. If I go into success, it's not very complicated. And honestly, it doesn't even look like HTML anymore because we're using these more dynamic features. But this just says, hey Flask, use the same layout so the page is structured exactly the same. But for the body, go ahead and plug in this value instead. So indeed, when you go ahead and load this success message, you see this message here-- not just success, I expounded here and said you are registered. Well, not really, that's because there's no database yet. But that's going to generate a full fledged HTML page. And what about failure? Before I was just cheating and just saying return failure, quote-unquote, no HTML at all. The failure page is going to be almost the same, but now I can actually provide some descriptive text. This body just says you must provide your name and dorm, thereby admonishing the user for not having cooperated properly. So now your home pages, if you kind of extrapolate from this, could have the exact same layout, aesthetics and menu bars, and all of that fanciness, but only the content would have to change. And you can get out of the business of just copying and pasting. So there, too, to your question earlier about dynamism, the dynamism doesn't have to just come from the user. It can also come from the construction dynamically of a website based on multiple pages. So at the end of the day, the browser has no idea that Python exists, has no familiarity with Flask. All the browser still sees is an HTML page. But what Flask and in turn Python are doing for us is constructing that page dynamically, following the rules from two weeks to go in HTML and CSS, and following last week's rules on how Python works. Questions? AUDIENCE: So even though [INAUDIBLE] DAVID MALAN: It's not. Good question. This new syntax, the double curly braces that we saw earlier and now the curly brace percent signs, this is actually yet another language called jinja-- J-I-N-J-A-- which is a templating language. And there's dozens of these things in the world, people just come up with their own syntax. And the reason for the funky syntax is that the author of jinja presumably could think of no other language that uses like a curly brace and a percent sign and a percent sign and a curly brace. And so therefore they decided, you know what, I'm going to use this syntax because it will look distinct from HTML, and CSS, and Python So that frameworks like Flask don't confuse it with something else. AUDIENCE: So do you have to upload that into [INAUDIBLE],, or is it automatic? DAVID MALAN: It's automatically supported. So Flask by default supports jinja. It could have come up with its own templating syntax. But whoever invented Flask decided I don't need to reinvent this wheel, someone else already made a templating language that gives me this functionality. So I'm going to combine our works into one. And I didn't call it a language a moment ago, because frankly, HTML, CSS, Python, JavaScript-- I mean, we're already running out of fingers here. But jinja is, indeed, yet another language. It's just not a programming language per se, though it will have some control flow features that we'll see in a little bit. It's just much more limited than Python. Other questions? AUDIENCE: Is it possible to combine the success and failure HTML files into one just for better design? DAVID MALAN: Good question. Could you combine the success and the failure pages into one? Short answer, yes. And let me not show it yet, because it'll get a little more complicated. But yes, I could imagine passing a variable in that's a Boolean-- true or false-- into just one of these templates. And maybe I call the new template result.html. I can actually then have an if condition in my template that says if the result is true, say this. Else if the result is false, say this other thing. So you could do that, yes. Generally, though, it's probably cleaner to keep messaging separate if they functionally do something else. After all, these files are pretty small anyway. Yeah? AUDIENCE: Just for question, what does the user see if they were to open up the debugging console on Chrome and look at at it, what do they see as the HTML that shows up? DAVID MALAN: Really good question. What does the user see? We can answer this by just literally opening Chrome and opening View Page Source or the Inspector. This is what the browser sees. So when I claimed earlier that the browser has no idea of Python or Flask are even involved, that is, indeed, true. Because what browser's receiving at the end of the day is just this, the dynamically constructed HTML. Good question. Yeah? AUDIENCE: [INAUDIBLE] can you also put Python code in there, or is it just HTML? Good question. We'll see more of this in just a little bit. The question is, can you between the curly brace and percent signs put actual Python code? You can put stuff that looks like Python code, but not all of Python. And so more of that in a bit. AUDIENCE: Is there a function call or something like that? DAVID MALAN: Only certain functions. Templating languages, long story short, are sandbox so that they are not as expressive as a real programming language. Otherwise you are vulnerable to potential hacks. You want their functionality to be very limited because they're only about displaying data, not about thinking or doing logic generally. More on that in a bit. All right, that was a lot all at once. Let's take a five minute break here, turn on some music, come back, and we'll make this act better. All right, we are back. So to recap where froshim0 left off, we now have this structure which is pretty much conventional. Any web application we make here on out is going to follow this pattern of having an application.py entry point where all the interesting stuff starts, a layout.html file in your templates directory that lays out the whole site and any commonalities, and then one or more other pages that actually represent your individual views that correspond to one or more of your actual routes. So now we're at the point of a stable baseline, but had we dived in right to this, it would perhaps not make as much sense as to why we did this various factorization. So let's now improve this. Because of course, if you look at success.html, it just claims you are registered. Well, not really. Because in application.py, did we do anything with the user's information? No. We just checked, did they give us information? And if so, we claim success. Else if they missed their name and/or their dorm, we just claimed failure. So what might a data structure be in Python where we could store registrants? We don't have databases yet, we don't have SQL yet. That's a week ahead. AUDIENCE: Array. DAVID MALAN: Yeah, we could use an array, otherwise known as a list in Python. So let me propose how we might do this. Let me actually open up froshims1 for our second iteration of this program. And in application.py, notice this. At the very top of the file, not only am I creating my application using the same line as before, and I've commented things this time in advance using the hash symbol, notice that I claim that on line 6 and 7 here, here is an empty list for all of the students who have registered. This way we can keep the information around. And we only did this briefly last time, but does anyone remember how you add something to a list in Python? By what function? Append. So if you have .append at the end of a list's name, you can add something to it. So where is that going to go? Well, here is my route for slash, implies, again, get by default. That's the default route that a human might get, and they are going to see index.html, which contains that form. If I scroll down now, you'll see that I have a register route just like before. But I'm doing one additional step. Which is the new line here, to be clear? Yeah, 26. So I could implement this in any number of ways. But the key detail is that I reference the list name-- students, but I could have called it anything. .append, as someone proposed, is how you add something to the end of the list. And then I can add anything I want. To keep it simple, I'm just going to add a string. And I'm going to keep it super simple and just say the string is so-and-so from such and such a dorm. So David from Matthews Hall, or Brian from wherever. And so here we have placeholders using f strings in Python. So this has nothing to do with Flask, this has nothing to do with jinja or anything we just talked about. This has to do everything with last week's syntax in Python alone. So this appends to that list this name from this dorm. So let's go ahead now and try this version out. If I go into my source sub and directory for today's code into froshims1 and run flask run, we'll see a URL that I can now visit. Let me go ahead and open that for froshims1. Notice that I have that complete dropdown now. Let me go ahead and say David, but I'm not going to tell you my dorm yet and try to register. Now I see a more friendly message, not just failure. And that's because of my new and improved template. OK, I'll go ahead and be David, and I'll be from Matthews here. Let me go ahead and register and voila. Now we see David from Matthews has registered. And it seems to be all of a sudden in the form of a new bulleted list. But where did that actually come from? Well, I don't know. Let me try this again. Let me go back to slash, which is the route that gives me the form. Let me go ahead and type in not David this time, but say, Brian. And Brian, which dorm are you in? AUDIENCE: Pennypacker. DAVID MALAN: Pennypacker. So let me choose this from the menu instead and click Register. And now we see Brian from Pennypacker. So somehow the application is changing state, and notice the URL that we're at is called slash registrants. So that seems to be a third route this time that apparently is not interactive per se, it just spits out the list of registered students. So let's just put the proverbial engineering hat on. If we go about implementing this slash registrants route, logically what must that code be doing in verbal pseudocode, if you will? AUDIENCE: A for loop? DAVID MALAN: Like a for loop, iterating over what? AUDIENCE: In the list that saves all the registrants. DAVID MALAN: Yeah. Iterating over the list of students which contains all of those registrants. And the template, meanwhile, probably has like an LI tag for list item and a UL tag for unordered list, which gives me the bulleted list. So let's take a look at that. So how do we follow these breadcrumbs? Well, if I scroll up in application.py, we'll see a route called slash registrants. And you'll see that all it does apparently is it returns a template called registered.html, where registered.html is probably a template that is generating that list. But there's something different this time. I'm passing in an argument. And we saw this earlier. When I wanted to pass in name equals David or name equals Brian, I just grabbed that from a variable. This time I'm not doing request.args, I'm not doing request.form. Because what is students? Where did this come from? That's the list from higher up. Recall that we have this global variable at the top of the program, students, which is initialized to an empty list. But recall that we keep appending to it in my register route. So I can go ahead and say, you know what? Go ahead and pass into register.html a template-- or rather, a list-- called students whose value is exactly that. And again, it's stupid looking that you have the same word on the left and the right of the variable name. You could do this differently. Again, you could say foo, you could say x, or y, or anything. But frankly, it tends to make most sense, just pass in the same name as the variable that you care about so that the template can see exactly that. So what's the next breadcrumb? If I want to understand exactly what is happening, what file should I open up next perhaps? Probably register.html. So let's go in there. It's in my templates directory by definition, and you'll see, indeed, a failure message which allows me to error check. Index, which contains the form; layout, which contains the overall structure; and finally, registered.html. And now we can answer the question that you asked earlier about Python code in the template. So this one looks more advanced than before, but notice it follows a pattern. Register.html extends that same layout. So it borrows from that same mold, so it looks the same. The body of this page, though, is just this snippet of HTML. Give me an unordered list, open and closed, and this is what you can do now with jinja. Again, it's almost identical to Python, so you don't have to worry about thinking about learning yet another language. It's just a subset of Python essentially. So if I want to output in the list of all of the students, I use my jinja syntax here, my template syntax with curly brace percent. And I say for student and students. Just like in Python, that induces an iteration over that list. And then what do I want to output? Well, we can borrow our curly braces from our name example and just do list item, plug in the name of the student, close list item. And then endfor. So this is the one stupid thing with the templates. Whereas in Python proper, recall that you can just say for student in students, you have a colon and then indentation handles everything. The problem with that in the world of HTML is that browsers recall ignore all whitespace, like whitespace has no special significance, but in Python it does. So the way people solve this is you literally, if a little weirdly, say endfor-- one word, no space. And that's it. And indentation helps you read something like this. So what is the HTML I'm getting back? I can actually look at this. Let me go ahead and view page source in Chrome, and you'll see it's not quite as pretty as might be ideal because there's a lot of whitespace which comes from those templates from my having pretty printed those, as well. But this is syntactically correct, and I'm dynamically putting this part inside of this layout. Any questions, then, on this? AUDIENCE: So if we restart the server, whatever's stored in the list, that goes away, right? DAVID MALAN: Good question. Let's kill Flask with control-c. Let's rerun the server. And let me go back to my registrants route and reload. And sadly, yes, this is not the best way to register students for a sport. Because if the server ever goes offline, loses power, you hit control-c, you obviously, indeed, lose everyone. And notice, too, even though we've generally frowned upon using global variables, which this students list indeed is, why did I define it up here in line 7 and not, for instance, in my register route here? Because indeed, I'm appending to the list here. But I very deliberately did not declare the list there. Yeah? AUDIENCE: You're using it in other parts. DAVID MALAN: I'm using it elsewhere in my other routes, the registrants route. And also even more to the point, if I declared a list here, it becomes by definition a local variable. Which means as soon as this function exits, now I've just thrown away those students who register immediately not even after a control-c. So this was a better approach to do it, but it's not what I did way back in my day. I actually did something that was a little fancier. So at the time, I didn't really know-- at least in, what, 1997-- anything about databases. I don't think I even knew about CSV files just yet, or at least how to create them dynamically. So I instead took this approach. Let me go into froshims2, and it has noticed the same templates as before. And indeed, I pretty much copied and pasted for this second example. But in application.py, notice this fanciness. So here I have almost the same thing up top in terms of Flask, but I'm also using this OS library, more on that in a bit. But what about line 2? It's subtle, but I rattled this acronym off I think just once weeks ago, SMTP. Does anyone know what that stands for? AUDIENCE: Simple mail transfer protocol? DAVID MALAN: Yeah, simple mail transfer protocol-- email, that is. So Python comes with built in functionality via which you can send emails, and this is exactly what I did when I first made this website. Didn't know anything about databases, I didn't know anything about saving things to files just yet, I was still learning. But I didn't realize, hm, I could use programming to send an email to the proctor or the RA who was overseeing the sports program so that they could just save it in a folder and know who had registered. It's not super user friendly, but it at least got the job done because they were then able to track everything. So in this program, notice that I have my route for my form. And I have this register route but a few new lines of code. And you would only know how to do this by reading the documentation. But in this case here, notice what I'm doing in my register route. I'm first getting the user's name and their email this time and their dorm. Then I'm error checking. If they didn't give me a name, or their email, or the dorm, render failure.html to apprise them as much. Then go ahead and do these lines of code. And this is more of a mouthful, and you would only, again, know this from the documentation. But it turns out if you read the documentation for this SMTP lib or library, you can use lines of code like this as follows. You can tell the library what server to use for sending email. And it turns out if you read Gmail's documentation, you can use smtp.gmail.com to automatically send e-mails not using the web UI, but using code. 587 is the TCP port that they use. So it's not 80, it's not 443, it's 587 by convention. Starttls, if you read the documentation, says turn on encryption. So the email is encrypted between you and Gmail. Then go ahead and log in with a certain username and password. I created an account in advance called jharvard@cs50.net, and my password is in my IDE's environment. I stored it elsewhere so that it's not visible on screen, otherwise people could send emails as John. Then I go ahead and call literally a function called send mail. And if you read the documentation, this one takes as argument who you want to send email to, the contents of the email that you want to send, and the message that you actually want to send here. Or rather, this is the from address, the to address, and the actual message that you want to send. After that, you just go ahead and render template and assume success. I could add more error checking, like I should probably check if anything went wrong here, but I'm keeping it simple. But these new lines that are highlighted actually send an email. So let's try this. Let me go into froshims2 and let me go ahead and do Flask run. Let me go ahead and open up the page here, slash. And notice I do, indeed, have a second field for text now. So this will be David, and this will be-- let's see, how about let's go ahead and just register not myself, since it's not my email account, but John Harvard who we claim's email is cs50.net, jharvard thereat. And he lives in say, Weld. Let's go ahead and click Register. All right, it's taking a little longer this time, but it was doing a little more work sending an email. So now let's try to go to gmail.com, open this up. Ooh. In my inbox, you are registered. If I open this up, notice jharvard@cs50.net has sent me an email by a BC seed, at least keep part of the information private. And it just says in the body of the message if I move the cursor, you are registered. So I did a little more back in 1997, but I included like the user's name, and their email address, and their dorm, and maybe their phone number or whatnot, and the sports they were interested in. But the idea is exactly that. You can send any information you want just by now using code. You could not do that with HTML or with CSS alone. Any questions, then, on this? Yeah? AUDIENCE: Last week when we wrote code in Python we had to like say if name equals the function to like execute that. How come in this we're not doing that? DAVID MALAN: That was all in JavaScript. So that allusion to if name equals, and then you assign it to a function, I think you're referring to our JavaScript examples, no? OK. So we'll actually come back to that in a little bit where we reintroduce a bit of JavaScript, which actually gives us some more functionality reminiscent of those examples. Other question? AUDIENCE: What email address did you send that email from, and don't you need to enter like a password to make sure that no one just randomly sends email? DAVID MALAN: Yeah, it's a really good question. So via what email address did I send that, and to whom was it sent? So again, this is the from address, this is the to address, and this is now the message. And just because I only have one email account open, I had John send himself an email in this case. Theoretically if I were running the freshmen intramural sports program, I could try to do this and change this from address to be myself. The catch is that Gmail actually has protections in place so that if you've logged in as jharvard with his password, then the email, no matter what you specify as the from address, is actually going to be overridden to be from John Harvard. However, this does speak to the potential insecurity of email. If you don't use Gmail but you use a third party service that is not so rigorous with its error checking, it is incredibly easy to fake emails from one person to another. I mean, look through your spam folder sometime. Most of those people who send you those spams don't exist. Like, the email addresses and/or the names are fake. And yet they might appear to actually be from a sibling of yours, a family member, or a friend, even though those humans did not send e-mails. And that's because some spammer has written code like this in Python or some other language, but has overridden these fields, but used a server that's not Gmail that doesn't enforce these policies. Other questions? Fun fact, also in 1995, I learned how to send-- or how to change the from address on an email. And turns out at Harvard there's this entity called the ad board who doesn't like doing this. So don't do that. Sometimes there's human defenses in place for this, not just technological. Thankfully, my friend whom I faked an email from did not-- it worked out OK. All right. You have now great power, don't use it for evil. All right, so let's go ahead now and do another example, but that takes this a further step, adding, finally some persistence of information. Let's go ahead into froshims3 now and open up application.py. So recall that we can use CSV files-- comma separated value files-- to create the illusion of like spreadsheets, but now we're actually going to create them ourselves. The code for this is a little more involved, and the only thing I've changed now really is the register method. So in version one of this code, I saved it in a global list just in memory. That was not good because it gets thrown away too easily. Version two of this we just sent an email to the proctor who runs the program. That was a little better, because at least they can then save the email. Version three, we're going to use a very lightweight database called the CSV file that saves it to my hard drive permanently. So even when the server stops, the data is still there. So in Python, how does this work? Well, notice that I've improved my register route this time as follows. If the user did not give me their name or dorm, then I go ahead and render a failure. That's pretty much the same logic as before, but I didn't bother declaring the variables this time, I just called the functions directly. Here's a new line of code that might be reminiscent of some of your past file I/O code. In line 16 here, I'm telling Python to go ahead and open a file called registered.csv quote-unquote a. So we've seen R, we've seen W for read and write. Anyone recall what a is, or no? AUDIENCE: Append. DAVID MALAN: It happens to mean append, which means just add a row to the file, which is nice. Because if there's already students registered, when a new one registers we just want to append to the bottom of the file. Quote-unquote a is supported by Python with this open function. That gives me back a reference or like a pointer to file, even though Python does not have pointers. Then this is the new feature. And here, too, you'd only know this from having seen an example or you reading the documentation. You can use the CSV library, which we'll see as imported up above. And you can ask for a writer, a piece of code that writes out-- that is, creates-- CSV files. And specifically, you want to write to this file. That library-- the CSV library-- comes with a function called write row, which does what it says. If you pass it in a comma separated list of fields that you want to represent your first column, your second column, and your third column, it will handle the writing for you so you don't have to get into the weeds of file I/O like you did several problem sets ago. Notice the subtlety. You do need to have these second pair of parentheses, because technically what you're giving it is a tuple. We talked very briefly about that last week, which is just like an x comma y pair, or latitude comma longitude. Same idea here. First column, second column, and so forth is a so-called tuple. Then I close the file, then I render the template. So what does this actually do for me? Well, let me go into my folder froshims3 here. And notice register.csv at the moment is empty. This is a CSV file. Nothing's going on inside of that. There's no one registered yet. But let me go ahead, then, and go into froshims3, run Flask run. Let me go ahead and load this up. And you'll see the same kind of form, but also a new link. Notice that no one's registered yet if I click on that link. But if I go into here and register David from Matthews and click Register, now it claims I am registered really. Let me click this link and notice that it's very small on the screen, but slash registered is where this is going to lead me, which is just where I was before. You see that now David from Matthews registered. Let me go back to the form itself. Let's register, say, Brian from Pennypacker. Click Register. He, too, is apparently registered. Let's click that link. Brian from Pennypacker. All right, so where is this data going? Let me go back to the IDE, close my registered CSV file, because it's probably changed and open it up. And voila, indeed, it's prompting me to reload it. There is the file. And notice David comma Matthews, Brian comma Pennypacker, all of those rows were written out for me. So now I actually have a database. And even though it's kind of a simple database, you know what I can do? Let me go ahead and right click or control click on it in the IDE, download it into my Downloads folder. And then if I actually open this thing, if I have Excel installed or Apple Numbers, which is the first time I've ever used it, let me go ahead and open that file. Opening register.csv. And voila, here now is a file. And Numbers is formatting it in kind of a funky way, but it is showing rows and columns. Those of you who are more familiar with Excel we can do that, too. Let me go down here. Let me go into my Downloads folder. Control click or right click here, and this time open it with Microsoft Excel. And if you've seen Excel before, we'll probably see a very similar UI. Because anytime Excel or Numbers-- OK, first time I've used Excel. So that, too, will open up some rows and columns, as well. So CSV files are just very lightweight spreadsheets. But what's cool about them is that you can create them so easily. You just have to put commas in there. Now, as an aside, can you contrive a user's input that could potentially break a CSV file? What could a human type in that could potentially break your own CSV files? A comma, right? If it's like David Mayland comma junior, or something like that. Or anything with weird punctuation. This is why you use libraries. That CSV library in this code, which we're importing at the very top of this version 3 of the code, is actually handling all of that complexity for us. When the library encounters David Mayland comma junior if that's the user's input, it will then additionally put quotes around my whole name, thereby making sure that my comma is inside quotes and not, therefore, confused with the special comma that demarcates the start of other columns. So again, that's why you don't reinvent the wheel, because corner cases like that arise. Well, what about slash registered, which is this list that's generating an unordered list? Let's see how that works. If I scroll down to this code, notice that it's not just a simple matter of grabbing a global variable, because there is no global variable anymore. Now I have to read it from that CSV file. So here's three new lines of code that work as follows. I'm going to go ahead and open this file, register.csv, in Read mode this time, not append. I'm going to go ahead now and say hey Python, use the CSV reader-- which is the opposite of writer-- on that file. And then, hey Python, go ahead and turn that whole reader into a list. So you'd only know this from reading the documentation. It turns out this is the recommendation. If you want to take a reader and just read the whole thing into memory at once and convert it to a Python list, you literally just pass it to this list function. That gives me a list I'm going to call students, and then I can do my same code as before. For good measure, I should probably do what I did last time, which is file.close to close the file, as well, just to make sure it's closed the next time it's opened. But I can actually simplify this, and you'll see more of these examples online. It's actually more conventional in Python not to do this, but instead to change your code as follows. To not bother closing it explicitly, to instead use a keyword called with to instead put the variable name back there and indent everything underneath. Doesn't matter for our purposes which one you do. The first one was correct. The second one is correct. This is just more conventional, if only because it handles the closing of the file for you. So if you see this online, that's all that's happening there. But it's just like in C doing fopen and fclose, or in this case open and close like I had a moment ago. Any questions, then? AUDIENCE: How would you handle duplicates? DAVID MALAN: How would I duplicates? good question. So haven't handled duplicates here at all, and David from Matthews could register again and again. But logically what might I do? Well, it probably belongs here in my register route. I probably want to do more error checking than just these two lines. Because what I probably want to do to see if David from Matthews is already registered is open the CSV file, iterate over its lines looking for David and for Matthews on the same line and then show a failure to the user if he or she is trying to register for a second time. I've not made it easy with this code, and frankly that's going to be so much easier next week with SQL. SQL, this other language for databases, will make it easy to search data that has already been saved. CSV files do not make this easy. It's doable, but you have to write more lines of code. So more on that to come. Other questions? All right, so let's skip ahead to one final example, froshim6, which we'll do something a little more for us here. So if I go ahead into froshim6, notice that if I do Flask run, and go back to the website here, and reload the screen, and I go ahead and give you my name, but no, I'm not going to give you my dorm, we have this feature. It's ugly again, but where did we see this kind of functionality when the user does not cooperate? Or how did I implement this, apparently? AUDIENCE: JavaScript? DAVID MALAN: Yeah, JavaScript. So it turns out that with Python, you can obviously validate the user's input on the server by just checking what's in request.args or request.form and then yell at the user success or failure accordingly. But you can also use JavaScript-- and honestly, we did this two weeks ago, so we just seem to be solving the same problems again. So how do you think about this? Should I be checking for the user's name and dorm in JavaScript? Should I be checking for the user's name and dorm on the server? I mean, mixed messages now. AUDIENCE: Whatever's fastest. DAVID MALAN: Whatever fastest. That's a pretty good heuristic to use, what's fastest. And we can make it prettier by using Bootstrap or some library to give you like a colorful box, or red error text or something like that. So which probably is faster, Python or JavaScript? AUDIENCE: JavaScript. DAVID MALAN: JavaScript. Why, is JavaScript just a better, faster language? AUDIENCE: You're not creating it [INAUDIBLE] DAVID MALAN: Say again? AUDIENCE: You're not creating it on a new server, so it's all happening on the same-- DAVID MALAN: That's why, yeah. We don't have to get into the religious debate of which language is better or faster, but where they're running is certainly important. JavaScript is running, recall, by definition, in the browser. It is sent as JavaScript code to the browser which then executes it client side. Python by definition today is doing everything server side. And indeed, the browser doesn't even know Python is involved, because all it gets is the HTML code that results. So OK, that seems to be an argument for not doing all of the new work we did today with if not name, if not dorm, and all of that, and just use JavaScript. But the problem is that if you get a little sloppy or a little clever and only implement your error checking client side. Because as you say, it's faster, and frankly once I make it prettier, it's just going to be more interactive and more seamless. The problem is you can't and should not trust users. Suppose that I'm a malicious user and I just want to inject some bogus data into your website, or I want to spam you, or subscribe 1,000 freshmen who don't actually exist, or just generally create problems for you. Well, you might think, well, that's OK, I have some defenses in place, and JavaScript code, and this adversary is going to get very quick feedback, very pretty feedback that they've not provided these various fields. But honestly, you can't trust anything ever coming from the human. If I open up Chrome's developer tools, and I go to this down here to the dot dot dot menu, and I go to Settings, and I go down here, there. That's all it takes to disable all of your hard work. He or she can just open up their browser-- Chrome or something else-- turn off JavaScript. So now when I actually submit this form, there's going to be no on-submit checking, no on-click handling. All of that is disabled. So if I go ahead and click Register, I at least still, in this version of froshims, have server side checking, as well. So this might be a little frustrating, but it's kind of the reality. It is perfectly fine to use JavaScript code and use client side code to give the user a better experience-- a.k.a. UX, user experience. But you can't cut corners and not implement the same kind of logic server side because you need to defend against this. Otherwise bogus data is going to end up in your database, things are going to go wrong. Never, ever, ever trust the user. Any questions? AUDIENCE: Can you do the same via CSS? DAVID MALAN: Can you do the same with CSS? AUDIENCE: Yes, can you [INAUDIBLE] JavaScript [INAUDIBLE]?? DAVID MALAN: Not with CSS alone. You can use CSS to make the error messages far prettier, yes, but not logically enough with CSS alone. And in fact, just to give you a sense now how you can make things prettier since I keep alluding to better design skills than the ones I'm showing here. If we go to Bootstrap, this very popular, free, and open source library for CSS, it actually has some interactive features, as well. And if I go under components in the documentation and I scroll down and I go to forms, you'll see, one, notice that these forms are already way prettier than the ones I've been making, right? It's like black text, and gray text, and small text. It just looks nicer and cleaner. But it's relatively easy to do this. And indeed, for the next problems that you'll be welcome to copy and paste some of this sample code and HTML, use Bootstrap CSS just to make your forms prettier. But what it can really do if I go to the sub menu over here, notice that there's this validation section in Bootstrap. And other libraries have this, too. And you'll want to read the actual documentation. But if I just scroll down for a while, here's a sample form in Bootstrap. It already looks a little prettier than anything I've made in just raw HTML. But notice if I don't cooperate, you can do really cool validation of forms with green and red text if the user does or doesn't cooperate by using a mix of CSS, but with some JavaScript code. And so what Bootstrap does for you is it actually automates some of the process of that JavaScript code that we saw two weeks ago and just used now. But it doesn't just pop up a message for the user, it actually gives them more immediate feedback. And almost any popular web site you visit these days gives you this more immediate proximal input. Generally you don't see some simple error message popping up, even though that's easier to do. Any questions? All right, so where did that logic come from? So let me go into, for instance, my template file now for the form in froshim6-- again, the last of these examples-- and you'll notice that I did this. If I scroll through this file, you'll see the same HTML as we've been using for some time. But notice at the bottom of the page that I draw some inspiration from two weeks back when we looked at HTML, and CSS, and JavaScript. So just as a quick refresher, notice how this is working. This line of code says in JavaScript check the document using the query selector, which lets you select anything in the web page looking for a form. When that form is submitted, call the following anonymous function. If the document query selector finds an input that does not have a value-- and I say not because of the exclamation point here-- then yell at the user with this, you must provide your name and return false. Else if the user did not provide a value for the select menu-- a.k.a. the dorm-- go ahead and alert them that they must provide the dorm, otherwise return true. And just to be clear and to recall from two weeks ago, what am I returning false? What does that effect have logically? Yeah, say again? What was that? I heard a whisper here. No? Yeah, it prevents submission of the form. The default behavior of a form is it wants to be submitted. That's why they exist. But if you return false in JavaScript, it will short circuit that and prevent that default behavior, thereby stopping the user from submitting the form at all. So let's take one step back now, there's now so much going on in this one file alone. In this sixth and final example, notice that we have application.py, which is the entry point, the so-called controller of this web application. It has a route which very simply for slash looks like this. When the user gets slash, this template is simply returned. What is in that index.html template? Well, it contains a partial HTML file. It contains this HTML. But it does not contain the doctype, the HTML tag, head tag, the body tag, the title tag, and all of that. It only contains the stuff that should go inside of the body tag. Because this file is using a bit of jinja, which is the templating language that Flask uses. You can just think of it as Flask, that's fine. It uses some HTML here, but it also in the bottom of the file uses JavaScript. And so just as before when we've looked at the source of the page, what I'm going to see in the browser on this forms page is no jinja, no Python, nothing related to Flask. Just a fully formed HTML page that also now contains some of that client side code. And so I have this mixture now of several different language, but each of which solves a very distinct problem. Yeah? AUDIENCE: So I think it was a week ago or two weeks ago when we were working on JavaScript and CSS, you were saying that it's preferable to split languages, not mix them in the same document. Right now we are mixing multiple languages. DAVID MALAN: Really good observation. So a couple of weeks ago I really preached the separation of concerns, and therefore separation of files. And that's why we introduced a .css file, we also briefly showed examples of a .js file. The short answer is as your applications get more complex, life starts to get messy. And the team and I were actually talking about this earlier as to how to present some of these examples, because what you are seeing in my design decision here is a tension. So that tension here is as follows. It is not necessarily the best practice to just have your logical JavaScript code comingled with your HTML. It makes it harder to collaborate with someone else. If one of you is really good at design and wants to work on the HTML and CSS, the other person really wants to do the JavaScript code, kind of hard to do that when they're both in the same file. So we could factor this out. I could change this line, just to be super clear, to be this. Instead of putting my actual code in the file, I could do something like this. The source of this shall be form.js, and that is just it. And then I have a separate file maybe my colleague works in as follows. But at some point the thoughts that go through my head are, it's only like 10 lines of code and I just have to create a second file now, and that second file is going to be maybe in a different folder as my template. And you know, it feels like this is just overengineering a solution to the problem. However, once it's 20 lines, 100 lines, now OK, now it's feeling messy. Somewhere there's this inflection point. And this is where reasonable people will disagree, and I might argue one way, you might argue the other way. And honestly, both of us are probably right. And so this just speaks to the web's development over time. And there's fancier frameworks now. and if we tie things earlier into the question about CS50 beyond, an opportunity after this class that looks more closely at web programming, there are even fancier frameworks nowadays than Flask and than Bootstrap that if tried to solve this problem. React is one of the most popular ones developed by Facebook, now open source and used by so many people around the world that actually addresses this issue. And it allows you to separate your HTML from your CSS from your JavaScript in different parts of the file but still in the same file. And that was their particular solution. And View and Angular, there are so many different solutions to these problems. And unfortunately, once we take the training wheels of CS50 off, this is what's ahead of you. The world is messy. And the reason there are so many darn languages and frameworks is because people like you have these instincts and think, this could be done better. And thus do we iterate and have new and new technologies. But this is the kind of stuff-- and honestly, this is the kind of silliness that changes over time. The fundamentals of HTTP, and client side code, and JavaScript code, those fundamentals are invariant even as the implementation details change. So the short answer is, could this be better? Probably. Could it be much better? I don't know. It really now becomes more of a debate among developers. Good question. All right, so let's now use some of these basic building blocks to make a final set of examples that demonstrates a feature with which most of us are pretty familiar reminiscent of what we did two weeks ago with Google Search. At the time we searched for cats. Today we'll keep it a little simpler and a little less graphical and just search for words. Because you'll recall from our speller problem set, you implemented a spell checker with 140,000 plus English words. That's a pretty juicy dataset to search over, and you're probably all familiar with autocomplete these days. There's hardly a website these days that when you start typing it doesn't try to finish your thought for you. Google, Facebook, any a number of other sites. So autocomplete, how does that work? Well, let me propose the following mental model. If you do have some data set like a big list of words, or a big list of Facebook friends, or a big list of whatever, you might store that server side because it's a lot, a lot of data. And in fact, next week you might store in a big database. But for today we'll just store it in a file like we did for the speller piece set. But if you want to create an interactive experience for the human, what language are you're probably going to want to use so that he or she gets immediate feedback? Probably JavaScript, right? That's the whole principle. Client side code is just going to execute faster because there's no internet between you and the code. But with Python, you have access to files. And yet with JavaScript code you have closer access to the user, so there's these tensions. So how could we go about building a site that lets a human via form search across that file for words? Well, let's start as follows. So in word 0 we have the following. Large, which is just a text file borrowed from the speller problem set, 140,000 words, one poor line therein. I'm not even going to double click and open it because it's so darn big it'll take a few seconds to open. In application.py we have probably the entry point to this application, and in templates we have just three templates this time. So just when you're reading someone else's code for the first time, where should our entry point be? Where should we start looking to understand what's going on? Maybe application.py. Or honestly, you know what? If you want to see what something does, run it. No harm in doing that. So lets run Flask run. Make this a little bit bigger. Let me open up the URL here, open, and I see a very simple form asking me for a query. Let me go ahead and search for a and click Search. And after a moment, OK. This is a lot of words, but apparently these are all the English words that our dictionary knows about that start with the letter a. And if I go all the way to the bottom, you'll see it stops with az whatever with no B words, in fact. Well, let's make sure this actually works and isn't just a trick. Let's search for b words. OK, so that seems to work, as well. And notice I borrowed some inspiration from Google. Notice that the route I'm using is called slash search like two weeks ago. Does take a cue parameter for query, and b is whatever the human typed in. So if I want to search for z the words, enter, I should hopefully get back now z words. So now unlike two weeks ago, we can implement both the front end and the back end for a search engine. But our search engine's searching now just for words. So let's look at application.py as proposed, which is the entry point, and let's see how I'm doing this. So this is some code that I borrowed a little bit from last week when we quickly implemented the spell checker in like 12 or 20 lines of Python code. I'm declaring a global variable called words, and I capitalized it just to be reminiscent of last time and the problem set. I'm using this syntax which I alluded to earlier is just more conventional or Pythonic. Open the large file in Read mode and call the variable file. Then here is a for loop via which you can iterate over every line in the file, reading one at a time. But recall, what does every line in this file end with? Like a backslash n, and we don't really want those as part of the words. That's not part of the English word. So R strip, right strip removes any whitespace from the end of the string. And that's why I needed to add that extra line. So I'm just cleaning up the file or massaging the data as you might do with any sort of data based application. So then I just seem to have this route that renders the template. If I look in index.html, let's follow the bread crumbs. Go into index.html. OK, not that much going on here. Looks like an HTML form, the action of which is slash search, just like Google's. The method of which is is get just like Google's. There's nothing really private about the words I'm searching for here, so I don't care. There's some fancier features here. Notice placeholder is the grayed out text the human sees. Auto focus. What does this do again? This is just a UI feature, better user experience. AUDIENCE: Puts the like, right in the text box. DAVID MALAN: Yeah, it puts the cursor right in the text box. To focus on something in a web page means make it what's interacting with the user right now. And Mac OS, for instance, highlights it in blue. So when you first load the page, the cursoe's blinking in the choice of text boxes that you care about most. Autocomplete off just disables the browser's version of autocomplete. So I don't see past searches, just because the whole point here is to implement this ourselves ultimately, and then I have my search button. This is just jinja stuff from Flask so that I have a layout file. Lets follow that breadcrumb. Lay out.html, nothing really that interesting going on there. If you've ever wondered why we have these in a lot of our demos, this cryptic looking line here just makes web sites look better on mobile devices. Typically by default if you pull up your phone and look at a website, if it doesn't have that kind of line, like the text is going to be super tiny unless you pinch and zoom. By using this line and variations thereof, it will increase the default font size a bit to make it a little more tolerable on small screen. So it's an easy win for users experience. OK, I seem to have exhausted all the interesting stuff in these templates. Let's look at another and final route. Here's my search route, and this is pretty Pythonic. This is a mouthful, and will re-implement it in a different way in just a moment. So I have a search route that listens for get requests on slash search. Then this crazy looking line is about as Pythonic as code gets. And I'll explain what this is doing and why it's conventional as opposed to straightforward at first glance. And then I render the template, passing in these words. So this one liner on line 17 actually has the effect of searching 140,000 words for whatever words start with what the user typed in. See, this would be a pain in the neck to do. In Python you can do it with literally one line. A long line, but one line nonetheless. Let me make this more clear. If I were to search for words in this big file, I might do something like this. Words is an empty list. So this lower case words is all of the words that match, that I want to send back to the user. So by default I have no idea what to send back. But I do know I can do this. For word in Words-- which is the capitalized variable, the constant up at the top that has the whole-- or not even constant, but the global variable that has all of the words from the file, here is a for loop over those. I can now say something like this. If the current word starts with whatever the user typed in-- well, what's the user typing in? Well, q equals request.args.get quote-unquote q, gives me the user's name, dorm, or in this case Q value for query. So if the word that we're currently iterating over starts with q, I can go ahead and append to my-- whoops, append to this list that word. Would you say you're comfortable with these lines here? To recap, give me an empty list in which to store the search results, iterate over all possible 140,000 plus words, get-- and actually, this was stupid. I should just put this up here, because I only need to check for that once. So store the user's input in a variable called q. For each word among the 140,000, check if it starts with the user's input-- a, b, z, whatever. And if so, append it to there. So let's temporarily get rid of this and just render the template. So notice this gets the job done, but this is very C-like logic. It's not wrong, it's perfectly correct. But Python is a language that's meant to be a little more human readable and a little more elegant, if a little more non-obvious. So this one line does the exact same thing using a feature called a list comprehension, which is ironic if you don't quite comprehend how it's working. But here's the variable I want to create called Words. These square brackets here say give me a list. What do you want to put in that list? I want to put a word in this list. Which word do you want to put in this list? The result of inducing this loop and then only putting in this list a word if it starts with what the human typed in. So it takes some getting used to, but this is just a one liner way, a very Pythonic way of expressing those several lines of very procedural code into a simple one line. Is it better? Not if you can't read it. But once you get more comfortable with Python, yes, it's better because it's less code. Yeah? AUDIENCE: You said Python uses notation to tell where conditions are. How is [INAUDIBLE]? DAVID MALAN: Good question. In this case of a list comprehension, you can only have one line or one condition. You can't have multiple lines therein, so I cannot start hitting Enter and indenting here. It's just not allowed. So you would only use this-- and I'm frankly really pushing the limits. You should only really use this syntax when it fits on your screen or fits on a reasonable person's screen. After that you should probably do something a little more expressive. Other questions? But this is very common to see online. So any tutorials, if you ever see this kind of one liner, just try to think about it from that approach what it is actually doing. OK, so propose from a user experience perspective how could this program be better? Because this is just our first version. So what could be better for the user than this? What could be better? Yeah? AUDIENCE: Just going back, can you explain words in caps? DAVID MALAN: Oh, sure. Words in caps is this global variable I defined up here that stores all 140,000 plus words. That's the really big file called large, the text file. Down here I just need a local variable. And if it's more clear, I could call it results and then just say results equals results. That is the subset of words that start with a, or b, or whatever the human typed in. That's all. Good question. Yeah? AUDIENCE: Why do we change the last time-- why did you have to change args to form? DAVID MALAN: Why did I have to change my args to-- AUDIENCE: Forms? DAVID MALAN: So earlier today when I didn't understand what was going on, you should use request.args for get requests. You should use request.form for post requests. AUDIENCE: But it's still .get after? DAVID MALAN: It's always .get, yes. But you change what you're getting things from. In an ideal world, it would have been something-- oh, I see what you mean. Get in this sense is the verb. We humans mean go get something. Args in this sense, if they had done get-- I'm making this up, but this is probably why they did this. Because get from get seems weird, whereas get from post is less weird. But it's just they called it args and form instead. OK, so let's actually improve this, but how? What could be better for the user? Yeah? AUDIENCE: They could search a word, not just the first letter. DAVID MALAN: OK, maybe searching a whole word would be good, not just the first letter. What else could we do? AUDIENCE: We can create an index list of letters and words? DAVID MALAN: OK, we could create an index list. So maybe using a hash or some form of inspiration from our problem set with Speller and actually use a more sophisticated data structure to get these answers more quickly. And let me propose, too, the goal here is to actually implement autocomplete, and this was not auto complete. This was like old school search. Type in a query, hit Enter, get a page of results. What if we want to do something more immediate? So let me actually propose this. Before looking at the code, let me go into words 1. Let me go ahead and run Flask in that directory. Let me go ahead and reload the form here, and now notice no Submit button because there's not going to be any actual submissions here. But I'm going to go ahead and hit the letter A, and ooh, that's kind of cool. Let me delete that, goes away. B, there's all the B words. Let me go B-A words, B-A-B words, B-A-B-A words. This is how autocomplete works. So it seems to be responding immediately to my input, so something's happening. But I'm not actually submitting the form. So I'm kind of using it now, it seems client side JavaScript to maybe talk to the server? Let's infer. So here, too, this should be your instinct. Whenever you're trying to understand how someone's website works, if you want to learn from it or mimic certain fundamental functionality, go ahead and inspect the page. And you don't probably care too much about the HTML yet. Where is this data coming from? Let me click on the Network tab, which we looked at a couple of weeks ago. Let me go ahead and restart this and let me clear this and start from the beginning of the story. Let's see what happens when I type the letter A. Interesting. There is a web request. So if I zoom in down here, notice that my browser actually searched for Q equals A, the human's input. Let me go ahead and-- it keeps searching because I'm using keyboard shortcuts here. But let me go ahead and click this row. Notice what happened. I made a request to slash search question mark q equals a via get. Let's see what the response was. The response here, if I view the source-- or rather, if I read the response-- notice what came back. It looks like my server returned to me a fragment of HTML containing hundreds, maybe thousands of words starting with A. But notice there's no UL tag, there's no head tag, no title, no body, it's just a partial HTML fragment. But that's interesting, because I know with Python I can do exactly that. I can generate anything I want on the server, and then maybe the browser can just plug in those changed the results. So let me go ahead and look at the code for this page. If I go now to the browser's source code, the view page source, you'll see a few new lines. So to do this easily, I'm actually using another library. This one is called jQuery. This was for many years super, super popular. It's kind of starting to fall out of vogue, but it's still so powerful and so useful. And it's used by Bootstrap, the other CSS library we've talked about, so it's perfectly reasonable to use it here. Notice how I'm including it with the script tag, and it's hosted on a third party website so that I don't have to save a copy of it myself on my own IDE. Then let's look at the code I actually wrote. So notice that atop this file is not even a full fledged form, it is just the HTML input. Because I don't need a full form. I don't need an action, I don't need a method, because I'm not submitting it anywhere with the human's cooperation. I'm going to use my own code. So in my script tag here, my JavaScript code, notice what I'm doing. This is some code from like two weeks ago. I'm going to search the tree that represents this web page. And indeed, it is meant to be a tree. Recall from that time when we looked at an HTML page, there is in memory, thanks to the browser, something treelike-- a DOM, document object model-- that represents your page. Using JavaScript, can we change that page after the fact? So what am I going to do? I'm going to tell the browser whenever this input hears an event called on key up-- so whenever the field has focus-- it's blue in Mac OS-- and the human hits the key and then let's go, and the key goes up, go ahead and call the following anonymous function. What do you want that to do? Now, this code is a little cryptic, but let me walk us through it because it's only three lines. This code here is using a special feature-- dollar sign-- that comes from this library called jQuery. More on that in a moment. That library, somewhat confusingly named, has a function called get, which has nothing to do with Python or the one we just talked about. But this has to do with an HTTP get. With this line of code, you can tell a browser, even after a web page has been loaded, go get me this other URL, please. So what URL do you want to get? Go ahead and get me from the same server slash search q equals, and then what does plus mean in JavaScript if you recall? Concatenation. So it means just append one string to the other. So this is like saying, go ahead and get me the URL that ends with slash search, question mark, Q equals A, or Q equals B, or Q equals Z. Whatever the human typed and just gets slapped onto the end. And then that's where we're getting it from. Input.value is the user's input, the value thereof. And then the last line-- and this is perhaps the fanciest-- notice that I have an anonymous function. In this library called jQuery, there is this function called get that gets a URL. When the server responds to your request with a virtual envelope of its own, this anonymous function gets called and the response envelope gets handed to you, so to speak, as a data argument, as a data variable. Then what you can you do? Document.queryselector UL. What is UL? It's an unordered list that by default on this page has nothing in it. But recall that what the server is sending back is a bunch of LI tags. That's great, because I want to put those LI tags right in between here. So how do I do that? I go into the so-called inner HTML of the UL tag, and you might not have seen this before. But you can change the contents of an existing tag inside of it by using inner HTML and just plop the data in there. And so what's happening is this. Let me go ahead and open up Chrome's inspector. Reload the page so it's empty. Let me open up Chrome's inspector. Go to elements, as is the default. And notice on this page, notice that UL tag is opened and closed with nothing inside of it. The moment, though, I search for something, watch what happens. If I search for a, all of a sudden-- ooh, it blinked, it's a little small. Now there's a little triangle there. What's inside of it? All of those LI tags that came from the server. So with JavaScript, we have this amazing power now to change what's inside of a web page by just asking the server for more data. So if you've ever used Facebook, or you've used Google Chat, or any websites that's dynamically changing every second, every minute and each time you get a message, you can literally, if you get a little nosy, open up Chrome's inspector and watch the DOM, watch this elements tab. And you'll see new stuff popping up every time you get a message, or a chat, or any other such notification on the screen. Now as an aside, this is a little sloppy to be returning HTML, but let's see how it's done. Let me go into application.py for words one, which is this example here. And in application.py, notice what I'm doing is this. Rather than return a whole page of results, I'm returning a template called search.html. All of the rest of this code is identical to before. If I go into my templates and go into search.html, look how terribly simple the code is on the server. If all you want to do is spit out a bunch of list items, this is all you need. There's no template. Like there's no extends layout because you're not returning a whole web page, you're returning a tiny, tiny, tiny fragment of HTML. But this is arguably a little sloppy, because there's a lot of redundancy in what's coming back. If I look at this tag that's coming back from the server, what is obviously redundant about all of this information that's coming back? And if I look at the Network tab, you really see it under response. What's redundant? AUDIENCE: You're doing a bunch of calls to the same address and to the same-- DAVID MALAN: This was just because I hit some, like, zoom in and zoom out, so it pretended to make multiple requests. So red herring there. Focus only on this part here. What's redundant about all of the data coming back? It's just keeps saying list item, word, close list item. List item, word, clothes list item. I mean come on, just use a more efficient syntax. Just separate things with commas or something lighter weight. This is sending way many bytes. I mean, look. There's thousands of bytes. This is kilobytes by definition of information that we're sending just to send open bracket, LI close bracket again and again. This is not very efficient. And so the world actually has adopted a different approach, and I'm going to show this in words 2 that actually returns something called JavaScript Object Notation, which is a more succinct representation of this as follows. Let me go into words 2, run Flask in there. Search for the same kind of thing and then watch what happens over the network panel this time. When I search for A, immediately get back the same visual result. But if I look at this search query, now look what comes back. I claim that this is a much more compact representation of the same information. It's a little annoying that there's double quotes, because those are a little redundant. But at least double quotes are a lot more efficient than open bracket, LI, closed bracket, and then the opposite at the end. So this is what's called JavaScript Object Notation. And as this square bracket here and thousands of words later the square bracket on the end implies, this is a JavaScript array that's being sent back from the server. So the only thing that's changed here is as follows. In words 2, this example, notice that I don't even need to return a template anymore. This code is the same as the past two examples. This is how I'm searching 140,000 words quickly. But if I include now a fancier function from Flask called jsonify-- which is not really a word. But jsonify, that takes any data structure you have data in like this list of words, the matches, and it turns it into that text based representation with quotes and commas. And you don't even have to write a template yourself. And indeed, I got rid of search.html. The only thing you have to do to give yourself access to this feature is import not just render template, and request, and Flask, but jsonify, as well, from the Flask library. Which is just one more feature. Any questions on that before we bring it all together with one final example? Yeah? AUDIENCE: Can double quotes break that into [INAUDIBLE]?? DAVID MALAN: Can double quotes break that? Good question, great instincts. No because the author of jsonify was smart about this. And if that author notices a quote like an apostrophe, or like something in your own name that has a quote, it will escape it in some way, usually with a backslash. Good instincts. But that's why you probably wouldn't want to write this code yourself because then you have to think of all of those corner cases as opposed to focusing on the parts you care about. All right, so there's one final example. And it's perhaps to come full circle here, do we even need the server? These 140,000 words right now are in a file called large. My web application loads that file into memory and then searches it. But who else could search over a big list of files? Where else could we put this logic? The browser, right? Browser gives you JavaScript, JavaScript's a language, languages can search things. So let me try this instead. In our words 3 example here, notice that I've got one new file. In advance, you know, I took my big text file that just had one word per line and I put it into a standard format just because it makes my life a little easier. And I called it large.json. And in there, actually, where if I open up this folder, you'll see large.js, which is a second file, this time a JavaScript file, in which I've just declared a JavaScript array of all 140,000 words for better or for worse. I just put them into a slightly different format with commas, and quotes, and square brackets, and I gave this whole thing a variable name at the very top of the file. Now, why is this useful? Well, if I go into index.html, notice that there is no more application.py or templates for this example whatsoever. We've gotten rid of Python entirely, I don't know if it's for the better or worse, but we'll now see. So in this file, notice we have an input tag as before, a placeholder for all of the ULs, but we're also now including this large.js file. Thereby telling the browser please download all 140,000 words and then search them locally. How am I going to search it locally? I've essentially converted the language I wrote in Python a bit ago into JavaScript as follows. Here says the browser go get me the input that the user can type into. This tells the browser, go ahead and listen for key up. Whenever that happens, please call this function which has no name, because I want you to just call it immediately, I don't need its name. This function is defined by these several lines. These several lines have to do a bit more work than before. Because before the server was doing all the hard work sending back all of the data, and we just jammed it into the web page. But here I'm going to build up a list a little more manually. So I'm going to let a variable called html equals quote unquote, because I want to build up the unordered list myself. Then if the human, indeed, types something in-- so if their input is non null, so if they type at least one character, do the following. This is weird in JavaScript, but when you iterate over an array in JavaScript, you use the preposition of not in. So for word of words, go ahead and do the following. If the current word starts with-- notice the capitalized w-- also different from Python, but same idea, just different spelling-- if the word I'm iterating over starts with whatever the human inputted, we found a match. Go ahead and append to this HTML variable, open bracket LI closed bracket, concatenate the word to it, and then close bracket, as well. So I'm constructing a variable in the browser's memory containing HTML that I want to jam into the DOM ultimately. How do I do that. Well, at the very last line I say to the document, select the UL tag, go inside of its HTML, and change whatever is there to this string of HTML, which is presumably 0 or more LI tags now based on those search results. So now let me go back to words 1-- rather, let me go back to this example here. Let me go ahead and serve this up. It's not Flask anymore, so I have to use our server from two weeks ago, HTTP server, to serve up HTML. Let me go ahead and reload the screen here, open up index.html, and now notice we're good to go. What do you want to search for? A, B, C. And let's open up the Network tab. Inspect, Network. Let's see what happens every time I search. Z, Y, Q, A. Why is there no network traffic now? AUDIENCE: There's no network traffic. DAVID MALAN: OK, but that's what I said. But why is there no network traffic? It's not sending any routes, it's not talking to a backend server. Why? Because all the data I might need is already local. So mixed messages here, too. Which is better, which is right? What's the takeaway? How do you think about this? Because now, whereas a lot of our programs early on in the semester were relatively small even though they didn't feel that way at the time, now we have even more design possibilities. And the answers are increasingly non-obvious, and this is why you as programmers will just get more comfortable with conventions, you'll maybe practice what you've seen preached first, then you'll decide as you might be already saying, I don't like that, I'm going to do this some other way. So how do you think about which of these several words examples is best? Version 0 was let the server do all the work and just send back a full new page of results like Google in 1999 did. Then version 1, we added a bit of JavaScript that used jQuery the library to talk to the server using a technique called Ajax-- asynchronous JavaScript and XML-- but that just means go get me more data and we returned LI elements. Then we decided that's a little sloppy, don't seem to send me all these useless tags, just send a comma separated list of words. That was version 2. And then this last version gets rid of all of that and just sends all the words to the browser and lets it deal with it entirely. Who likes which best, 0, 1, 2, or 3? What do you think? AUDIENCE: So if you aren't supposed to trust the user in case they turn off the JavaScript, then their decision of the functionality of the website works. DAVID MALAN: That's a good trade-off. So if the user turns off JavaScript, 3-- OK, it won't offend anyone-- 3 2, and 1 won't work anymore because JavaScript's disabled. Now, do you care about that? Maybe not. It's a small number of users on the internet who are so concerned with this they just turn off JavaScript altogether, and the reality is so many websites just break these days without JavaScript. That might be a reasonable cost of doing business. Other thoughts? Yeah? AUDIENCE: Is the last version the fastest? DAVID MALAN: Is the last version the fastest? I don't know, how could we assess that? Yeah, I mean, we can literally measure it. And honestly, built into Chrome in other tabs that I've not even clicked on are a lot of performance tools where you can actually monitor how long everything's taking. This is called benchmarking more generally, and this is what you did essentially in the speller problems that even though we wrote the code that timed everything and measured everything, to answer that question, you could just try timing every one of the examples and then decide for yourself which is best, sure. Yeah? AUDIENCE: The last thing I have with problematic issue is like my phone's old, and it doesn't have a lot of memory, and I may not have the fastest plan on Earth. So you might be charging me more data, you might be slowing my phone down. DAVID MALAN: Very reasonable concern. Let me go into our dictionary file here and list the size of this. That large.js file is 2.2 megabytes, which on mobile devices, especially in places where your signal is slow, or your bytes are expensive, or you only have intermittent access, I mean, that's kind of obnoxious to send two megabytes to the user if you can avoid it, especially when you can send only the subset of results, a few kilobytes maybe, of matches instead. So a very reasonable concern. Cost, user experience, performance. Other thoughts? Which is better for people on university and corporate campuses who tend to have really good internet access? Say again? 3, OK. But what if it's not 140,000 words, but it's like a billion pictures of cats that Google indexes? Or hundreds or thousands of friends and all of their profile data? Like at some point there's this inflection point where too much data, right? It's not reasonable, it's too much. You don't want to just get a copy of your corporate database to every one of your users just for them to search more locally. So again, these are the non-obvious questions. And one of the goals of, frankly, the final project if you do something web based, or mobile based, or C based, or anything, is all of these questions now boil down to you. Like we have no well-defined answers in mind as to what your final project should do and how it should do it best. But in the weeks ahead, you'll be pre-proposing some project ideas, proposing an actual project idea, and then designing and implementing this. And you, too, will probably feel this tension where the answer is not always obvious, your teaching fellow might not even know how to answer your question, because he or she will have thoughts of their own. But it's ultimately up to you. And we're now at the point of a level of coding maturity where we're taking the training wheels off and more of the decisions are now left to you. Let's end here. Stick around for one on one questions, and we'll see you next week for databases.
B1 中級 CS50 2018--播放7--網絡編程 (CS50 2018 - Lecture 7 - Web Programming) 8 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字