NICK WONG: Hello. So my name is Nick. And this is CS50 seminars. I'm going to be covering a little bit of web development. I think the title of this seminar is Basics of Web Development with Python and Django. Unsurprisingly, we'll be using Python and Django. To say this is the basics of web development is probably an overstatement. It will be a very, very brief dive into web development. And I get about an hour. And I'm going to try and cover as much Python, as much Django, and as much web development as I can. Currently, we are looking at my beautiful screen saver. I'm a huge fan. I didn't write it. It's basically just borrowed from The Matrix. I was recently told that The Matrix actually used a Japanese cookbook's characters, I think, for the things that come across. I am still in trying to get that to work. But we're going to just dive right in. So if you wants to see some code, if you go to this GitHub repository, I've built a bunch of stages for the app that we're going to be building today. I'm going to talk a little bit about what Django is, why I like using it, some of the frameworks and ideas behind it. And then we're going to go right into just kind of seeing how that kind of all comes together. I'm luckily not going to be doing a whole lot of live coding today. Live coding is one of the banes of my existence. I hate it. I will never, ever successfully live code entirely. I think I spend most of it debugging. And I make a lot of typos. So I have pre-built most of this code. Knock on wood. And so yeah. If you go to this GitHub repository, you're welcome to clone it, download it, fork it, whatever you'd like. There's all sorts of stuff on there. The latest directory, I think, has actually the thing that works the most. It's the one that's deployed. This is actually a live app on Heroku. I will mention a little bit, there are some things that aren't related directly to the topics of the seminar. We'll be talking a little bit about Heroku just briefly. And we'll kind of briefly skim over some Bootstrap and JavaScript and stuff like that. They're just not the focuses of the seminar. So I probably won't explain them very well or at all. I'll just kind of mentioned that this is where those are. There's a lot of great documentation for both Heroku and Bootstrap. JavaScript is questionable. And so if you're more interested in that, you're welcome to go and figure that one out. They're fully implemented in the code base. So hopefully not too bad. Yeah, getting right into it. Basically, Python, beautiful basically multipurpose language, super versatile. You can use it for machine learning. You can use it for computer vision, data processing, web development, which is what we'll be doing here. Web development is a very vague term. It means roughly just building things that go onto the internet. That's roughly the idea. I think people generally assign a bunch of terms to it that sound really cool, like full stack, back end, front end developer. We're not really going to be in any one of those particular things. I guess this is probably close-ish to a full stack, whatever that means. And so we will basically be building an app entirely from scratch in Python. All of it is in Python minus the front end stuff, which will be in HTML, JavaScript, and CSS. But the stuff in the back, the things that make everything work are in Python. And Python is a really cool language. It does all sorts of cool things. And it does them pretty well. And what I mean by that is it's pretty readable. I can look at some code and pretty quickly understand what's going on. It's extremely modular. So there are these modules, and you import them. And they each have their own functionality and things that they do. I'm generally a fan of clean and elegant code. Now, I say that. And then we're going to look at my code. And I'm sure there is someone out there who will go, ah, that's not clean. It's not elegant. And I will agree. That's correct. It can always be improved. But generally speaking, I am always looking towards that paradigm. And because of that, I particularly like Django as a framework. Here at CS50, we actually teach the framework of Flask, which is very similar. There are a lot of similarities between the two. But it's a little bit, I think, more small-scale. And then we kind of teach like all three separately. The databases, the controlling part of it, and then the front end are all kind of separated in CS50's way of teaching it, where we teach Flask for the back end controller part. We teach Jinja2 as our templating engine. So how do we control HTML? How do we generate that? And then we use, I believe, MySQL for the back end. Django is kind of all three in one. It just brings them all together. Django has its own templating engine. It's very similar if you've been following along with CS50 stuff. It's very similar to Jinja1. And then a lot of its paradigms for how things are controlled in the back and used are also quite similar, though a little bit more Pythonic, I think. That's a dangerous statement. We'll say that I just like them a little bit more. That's more of a personal choice. And then I believe we're actually using the same database back end as MySQL. We're using MySQL. However, it doesn't necessarily matter you can actually swap back ends out underneath Django. And it's pretty quick, very convenient. And we'll see later that deploying to Heroku, that is very convenient. Well, we might not see it a whole lot. But I'll point it out. So yeah, Django makes all of that come into one framework, one package. And it's super convenient to use. We'll talk a little bit about downloading the dependencies and things. But it's basically just Django. And then we'll talk a little bit about-- I'm using Waitress as the server for Django, because Django comes with some built-in controllers for running a server locally and abroad, remotely. And that isn't super suitable to even basic developmental purposes, except for local development. And since we put it on Heroku, I figured it would be a decent time to throw out that Waitress is a really great platform for actually running these sorts of things. But yeah, in Django, there are three main ideas underneath this framework. And when people say framework, they can mean all sorts of things. What I mean here is that this is a platform on which you can build pretty quick, very clean, very versatile web apps. And it's built entirely in Python. So I'm a huge fan. I'm kind of addicted to Python. And I've really learned to love Django a lot. So in Django, these three main ideas are actually shared across a lot of the web development space, except it's called a little bit different of a thing. So there's this acronym, MVC, models, views, and controllers. That's kind of the general web development term. Because of the way that Django is set up, we actually call MVT, which is models, views, and templates. And it ends up swapping two of the terms roughly, as far as position goes, but otherwise not too bad. So I'm going to talk about it in the Django context, because I promise the seminar will be mostly about Python and Django, with definitely an emphasis on Django. And so models are very cool. If you want to look at what they are in Python, they are in this file, models.py. They are classes in Python. If you understand Python's classes, if you understand classes and inheritance and things like that, then you understand models in Django. And you might be like, whooptie-doo, models, cool, don't really care. But models are our database. That is how we represent data in our database. Each individual item, you could roughly think of it as a table in a database. And so Django has this high-level abstraction for how we represent data in our database. And it does it in a way that is extremely congruent with how Python represents classes. In fact, they are Python classes. And so that's super convenient for the, I will say, intermediate or aspiring or advanced Pythonic developer. And I'm a huge fan of just being able to write classes for things and not really have to worry about SQL, SQL syntax, or any of that. For example, I haven't had to debug a SQL error message since-- well, I guess since teaching CS50. And so I'm a huge fan of the way models are written in Django. The only thing that I think is particularly troublesome in Django, and we will talk about it, are these things called migrations. So you build all these models. You write all these models down. They're beautiful. They're eloquent. They're perfectly well-written. And then you have to migrate them onto your application. And what that means is that you have to tell Django how these tables should be structured, how things should actually be designed. And you actually make these migrations first. They live in their own folder. And we'll talk about them a little bit. And then you have to actually migrate them over. If you don't do that, nothing gets actually changed in your data. It's a bunch of paper changes. And I think that that's one of the troublesome parts of Django development. It's pretty well done, I think. I couldn't think of, necessarily, something better off the top of my own head. But it's a little frustrating. I have lost lots of data before because of that. And to be clear, that is not the Django developer's fault. That it's certainly my own. And that's my own confusion and then experience. So we'll try and clarify a little bit of that as we go. Let's see, the next part is views, the V of MVT. And so views are basically Django's way of controlling what happens in the back. So you can think of it as, in web development or in the web in general, there are users or clients. I might refer to it as browser-side, client-side, user-side. Basically, I just mean the person with the laptop accessing our website. Excuse me. Or the phone or however they're getting to our website, they are the client. And that user is going to send some sort of request, preferably through HTTP or HTTPS, actually, would be ideal. And that request might be some form of get request or post. And they'll send that to me, the server-side. And I will generally try to refer to myself as server-side, everything else as client-side, since I am developing. And on that side, on my side, on the server-side, I'm going to try and handle that request. I have to figure out where it was intended for, what I should do when it gets there. Should I process certain data from it? Do I assume certain data exists from it? There's all sorts of things to do with that. And that is Django's views. So there is a views.py for every app in Django. You might be wondering what the heck is an app in Django. And I will point that out shortly. Sorry, there's a little bit of a chicken and egg problem here. But we'll get there in a sec. And views in Django basically say, hey, what did you get. What kind of URL was parsed here? What kind of arguments are there? And then it handles them accordingly. And if that sounds vague, it's because it's actually just very open. It's very versatile. It can be used for all sorts of things. One of the things that is worth mentioning here is that Django's URL system is really pretty. I like the fact that there are no file extensions in their URL system, at least not built-in. Everything is just domain name slash whatever without a .html, .php, or whatever it is. And I particularly enjoy that. It's also very easy to alias a bunch of URLs to the same view. You can do all sorts of things. It's very, very well-designed, I think. So there are views for Django. We'll cover each of these things in a little bit more concrete detail in a moment. And then there are templates, which is basically the way that Django actually represents HTML. And the reason they're called templates is because it's not written in pure HTML on the server side. It is actually written in a mixture of a HTML, JavaScript, CSS, the standards, and Django's templating engine. And so if you've ever used a templating engine before, you've got it. But if you haven't, no worries. Templating engines are really cool. They give us a lot of power. We'll see it very early on that if we were to write an index page or a home page, and if we were to then write a other, maybe posts or articles page, and then an authors page, and then in each of these things, each one has like a nav bar-- but then I went and added a favorites page. And now I've got to go change each nav bar. And I'm going to overexaggerate how tedious that is when we get there. It's annoying. And not only is it annoying, but if you're working on a team of developers, it can lead to a lot of inconsistencies. Simply put, it can lead to a lot of inconsistencies in style. But even worse, it can lead to inconsistencies in thought process and actual design choices. And that can be really bad. And I said really bad as a vague, scary term. But it has a lot of implications. We might have a developer who forgets to do a certain thing in the navigation bar. And then whenever you get to customer support, you can't get back. Bummer. And those sorts of things might not necessarily matter to you, the developer, who knows your website very well. But to the average user trying to get to your website for the first time, it can be really frustrating not having an intuitive user interface. So following what does it mean to be a real user of your website whenever you're web developing is really important. What might be intuitive to you because you've sat there for 35 hours straight looking at this code might not be intuitive to the person who has never visited your website before in their lives. So we'll stick to a very basic standard of not quite the monolithic one-page website. I'm not a huge fan of those, although they are nice, and they have purpose. But we are going to be on kind of the thing in the front, nav bar at the top, navigate to any page in one click. And that is a kind of simplified version of a web development practice, which is basically just to keep everything as flat as possible, meaning that I don't have to click very far to get into everything. So we've covered a little bit about what Django is, a little bit about the paradigm underneath. Let's look at some code. So if you have looked at this GitHub repository, then you certainly know a little bit about what's going on. I left some READMEs there. I apologize. My sense of humor from this talk is actually bleeding through. You will get a sense of my sense of humor, and it's terrible. If you're interested, this is my command line. This is the repository that you hopefully have access to. And what we're going to look at is the actual code. So I labeled them in classic CS style starting at 0 and going up to 7. So there are 8 stages total. And we're going to say a goal is to get to stage 5. It's a decently complete web application. We'll ideally get to stage 6. And if there's magic in the world, we'll get to stage 7. So we're going to start at stage 0. And basically, we're going to spend a lot of time looking at this file structure here. So I'll expand that a little bit for you. And what you see here is there's this pycache directory. It gets ignored by Git. We're not too worried about that. This dv.sqlite3. Sorry, we use SQLite as the actual database structure. And then we have this manage.py, readme.md, which is just explaining what's going on, run.sh, and we have rango. And so actually, that's a great point. I'm going to tell you a little bit about what we're actually doing today, since that is a reasonable question that was probably asked a while ago. I thought it was really cute and punny/funny to say randomized Django blog, rango. Yeah. So that was a lot funnier to me at around 4:00 AM two days ago. But it's still kind of funny to me. And I hope you enjoy it. But the idea basically being a lot of people build their first Django app as some sort of basic blog. I'm kind of lazy. And I didn't really want to go stealing through web scraping a bunch of other people's blogs. So I figured, why not create some kind of randomized blog. It's not particularly pretty. But there are things you could do to improve on it. But again, this is definitely trying to just get through a very large number of practices and ideas in a very quick period of time. So what basically happened here is, instead of stage 00, you might do django-admin startproject of rango. And what that would actually end up doing-- and that command looks like this. Whoops. See, this is the problem with coding live. I can never spell. That implies that I could spell normally, I can't. Cool. So this command over here is what I might use to start a new project in Django. And you might have typed that into your console and been, like, command not found, Django admin. Why did you lie to me, Nick? And I apologize. I was not lying to you directly. Basically, you actually need to install Django first before you can actually run that sort of command. And so it is a PIP package you can do pip install of Django. I just did that, at least as far as building this project goes. And that will get us going. You'll notice in the left of my prompt, I have this little parenthetical rango over here. You're going to hear me say rango and Django and Python and oh, no frequently. But rango being the name of just a virtual environment that I setup. And virtual environments are in a great aspect of controlling which version of packages you're using in Python. They basically just say, hey, I only want to use this set of packages for this Python thing that I'm doing. So let's keep them all separate from each other. For example, if I'm using the Harvard Crimson's version of Django, I will have to go back a couple of decades. So we are basically saying here that we just want to use this particular project's versions of every Python package. It also means that I can just have those dependencies. And I can install them in a requirements.txt, for example. So if you go to that GitHub repository, then you're actually able to just do a pip install -r of all of these requirements. And that will set you up with all the requirements you could possibly need-- knock on wood-- for the latest version of this, as well as all the intermediates. So you're welcome to go do that. You're welcome to install them one at a time. I'm not your mother. You're welcome to do whatever you'd like. Beyond that, once you've gotten that done, you can run this Django admin command. And now I'm no longer lying to you. And it will actually do-- well, hopefully, it will do what I tell you it would do. So django-admin startproject rango will get you going with a project called rango. Now, Django-- oh, there's going to be some fun tongue twisters. Django tries to set everything up in a beautiful directory-based structure for you, meaning that when I ran this, I would actually have ended up with a directory called rango. Mine is called stage 0, because I built them into several separate stages. But it would then have inside of it another directory with the same name. Now, you might be wondering why do that. That sounds non-flat. It sounds very nested and hierarchical and annoying. And you'd be almost right, except that within a project, which is what this upper directory will be, the directory underneath in rango itself is actually the main or root app underneath our Django application. So within a Django project, you have applications that are installed. The first one that is created is actually one of the main ones. It is the main one. And it is generally where requests are going to go first. Now, you can configure the heck out of Django, no problem. So I'm going to say things in kind of this probabilistic way. You can generally do this. You can usually do this. It will usually be related to the default settings. But technically speaking, you could manipulate it however you'd like. So in this initial installation, there's very little that has to be done. I just got it running. That was all I wanted it to do. So what we're going to do is, if we go into stage_00, I can actually run this application totally reasonably from my local computer. So what I can do there is I can do python manage.py-- manage.py being the management interface for Django. So if I want to run a server, if I want to migrate my database, if I want to do all sorts of other things, I can do managed.py. And then we're going to do exactly that, runserver. 0.0.0.0 is-- I always forget how many zeros there are in that. There are four, 0.0.0.0 is just kind of display on my computer and open the interface out to the public. 8.8.8.8 is a nifty general port for development use. You'll notice that Django initially will give you this, you have 15 unapplied migrations. Those are all the admin page migrations. Admin is an interface built into Django. It's kind of cool. There's all sorts of things we can do with it. I'm not going to touch on too many of them during this talk. But it is a very useful interface. And it's worth looking up. I just think it's relatively intuitive. And so it'll tell me that I have at 0.0.0.0, 8.8.8.8 this thing running. I'm actually going to just go here in my web browser. I would say your web browser of choice will suffice. People generally disagree with my use of Safari as a web browser. But that's all right. So you'll see this cute little page from Django itself. We've done nothing. I literally just did a django-admin startproject, went into that directory, and ran the server. So we have very little to do in this stage. And you get this, it worked. You're on the right path. As long as you are there, congrats, you did it. So we are now running a web server. It's local. And I'm actually able to visit it and see what's up. There's nothing interesting really on here. There's a bunch of documentation on the bottom. Their documentation is beautiful, by the way. I would definitely recommend reading it or reading through it a little bit. Don't read it like a book, because that would be a little weird. Again, I'm not your mother. You do whatever you want. And so we kind of get to here. We see that things are working. We're glad. Everything's wired up a little bit, or at least mostly, correctly. Now, Django will output all of these things for you. You get to get a live log of what's going on. And when you're developing or when you're debugging, this is actually really useful. So development-wise, this is a great way to run the app. Excuse me. Oh, think I got some dust in my eye. That's fun. And there we go, I think. This is really useful. You can print all sorts of things to this console. You can actually use logging practices. Generally speaking, if you're running some sort web app, please use a real logger. There's a great article on Python logging mechanisms out there on the internet. It's like the first thing you get when you do "Python how to logging" on Google. And so I would definitely recommend reading through that as well. That is a fantastic practice for just keeping everything consistent. Logs are built in a really good way for just grepping through them and seeing what's going on. So cool. We've gotten through stage 0. We can pat ourselves on the back a little bit. And now we're going to Control-C out of that. And we're going to go figure out what is in stage 1. And I say that as if I don't know. But unfortunately, I do. Cool. So if we go into stage 1, there's now a new thing that exists. There's a new directory I'd like to draw your attention to. It's called web. And this one looks very similar to our base directory or our base application, rango. However, it has a few other things in it. And so when you do install another app in Django, which looks roughly like django-admin startapp-- and this is the command that was run for that. I'll bring that up to the top. We ran this command. And we got this web subdirectory. And what that did for us is it gave us an actual application that's going to kind of run underneath Django. And so it's a little bit strange, in that it itself is not really running. We're still running our main project. We're still using the management interface to run things, at least for now. But it is the thing that's actually doing our web deployment. And so there are a lot of different ways of doing this. You could run it as this monolithic, everything's under the rango app. And you don't do anything else. But I'm generally not a fan of doing that. I think separating things out by functionality is a really good practice. It makes debugging a lot easier. It also makes development with a team a lot better. And I say that, again, generalist statements. But what I mean by that is, if I'm on a team with three people, I want to work on the web app version of what's going on. He wants to work on the API version of what's going on. And she wants to work on making sure all of our URLs are beautiful and settings are configured correctly for production and development. Then we can all do that at the same time using some sort of version control and not step on each other's toes. If we were all working in a monolithic views.py, models.py, et cetera, then the API guy, the web guy, and probably our product manager woman are all going to conflict. And it's going to be a pain. We might all end up killing each other. So generally speaking, keeping things separated out has an actual purpose. There is a reason for that. And I think even in doing personal projects, it is a really good practice to get into that habit. It helps to make you a little bit more efficient too. Generally speaking, I would argue that there are plenty of arguments for why it's a good idea. So now you'll notice, or you might notice, I will point out to you that there are these kind of new directories. There's this migrations directory within our web app. You'll notice it does not exist in rango, the base app. However, in rango, we do have this static directory, which I believe it actually does exist in stage 0's rango. Oh, no, just kidding. Cool. So that is also a new directory. We're going to talk about that in a second. We've also changed some of the settings in this one. And we've added a basic URL and a basic view. And no models have been modified yet. So we're going to talk about that in a second. Let's start with static. So Django has a built-in system for static files. And static files can be notoriously annoying and difficult to configure. I'll do my best to explain them here. But honestly, it's worth playing around with it and really getting an intuition yourself. So static is what refers to all sorts of JavaScript, fonts, images, CSS in a Django web app. And so generally speaking, you're only going to have, really, three or four folders in static. I like to keep mine actually separated out, in that I actually separate out by app. So I say, underneath static, there's each app's name. And then under each app, there is each of the things that you want to actually control or have in the static directory. So admin has already been collected into here. The Django web management interface command is python3 manage.py. That'll be the management interface that I refer to is python3 manage.py. And then we can say something like collectstatic. And that will allow us to actually take all of the static things that exist somewhere and shove them to wherever they actually belong. And the reason I say that and such weird terms is that for now, that means just the things that you wrote here, keep them here. However, when we actually switch to some sort of real production environment, which I don't believe we'll have time to get to, but it is worth looking into, and there's great documentation on that as well, we might want to use some sort of content delivery network, or CDN, to actually move everything off of our personal server. Django itself and a lot of the services underneath it are not super great for serving all of these static assets, particularly like images. They're kind of big and a pain to load. And we're not optimized for it, especially if we have a high traffic volume. So it makes a lot more sense to maybe use like an AWS S3 to just have all of your static there. And then this command actually becomes really useful, because it means that all the local stuff we have here, we don't actually put that into our version control. We just push that onto some sort of remote content delivery network. And then it gets served from there. And again, a very cool practice. It's something that is very useful. And then this command becomes important to us. For now, it's actually not too essential that we use it. But it is there and, I think, worth mentioning. So yeah, we have this static. We will later add a web folder underneath static. And then that will have its own CSS, JavaScript, fonts, images. I'm generally a person who prefers to do motivated development. So when I have a need for something, I will then go look it up, figure it out, and build it. So I'm not going to put that in here yet, because we don't need it. I think that it helps keep my code a little bit leaner and a little bit more streamlined. But we have the admin interface's static files in here. And that's good enough for us. So that's static. Now, I said we modified settings a little bit. Thank you. The IDE I'm using, by the way, is Visual Studio Code. Love it a lot. I would definitely recommend. I also like Atom quite a bit, a personal preference. There's this cool security key. Mine is in my version control. If you would like to go hack this web app on my Heroku here, you're welcome to. It won't particularly matter to me. But the allowed host is what's interesting here. And so Django actually requires that you tell it which host it's allowed to be run on. So 127.0.0.1 is my own local computer. Well, it's the IP address for local host, which is resolved to 127.0.0.1. And those two things just allow me to run this app on my own computer using either the local host or 127.0.0.1 domains or IPs or addresses. Now, rangodjango.herokuapp.com is where this is actually live right now. You're welcome to visit it with your smartphone or your laptop or your other internet-enabled device. You can explore a little bit of what the final product of this whole project looks like. I don't believe we'll end up actually getting there in this talk. But there are a bunch of READMEs. And all the code is online. It's on that GitHub from the beginning. And so this allows me to tell Django, only run on these domains. Any other domain, don't work. And that's just kind of a quirk of Django, a perk, I would say. Now, I've added in two apps from the original start. django_waitress allows us to use that server process Waitress to actually run and serve Django. I'm not going to be talking about that one too much. But we've also added in the main a root project app rango. And that comes from just this actual subdirectory rango. And it tells Django to check in rango-- that's way funnier than I thought it was going to be-- for actual templates, static assets, things like that. It tells it to allow looking into that app for certain things. Web has not been installed yet. But we just haven't really needed to install it yet. We will want to install it soon. I think that's in the next stage. And we'll want to make sure we take care of that. Then the last thing at the bottom-- there we are-- is-- you'll see I left a comment there that says, this is not suitable for production. Don't do this for production. I repeat, don't do this for production. This will not help you as far as production goes. However, it is a very simple way to do some development. And it's a little bit quicker than the production mechanisms might be. So what this says is this is the static route, static URL, static dirs, and project route. And the reason this is configured the way it is, in kind of this generalist way-- I didn't hard code anything-- well, give or take-- is because it gets pushed onto Heroku. And Heroku configures a bunch of stuff for me. So I don't really want to mess with what they're doing. So I just say, you know what, wherever this file sits, that's our project route. And then I make everything off of that. Now, static URL just tells Django how to find static assets. And so whenever a request comes through, we are going to see how, in urls.py, we actually configured the URLs a little bit to make it a little bit easier for Django to go, ho, OK, this is a static asset, go here, as opposed to this is just a general URL, go there. And then the static route itself is where on our directory structure is this located. If you switch to a production-style version, then these two things become actually remotely located. And that's kind of cool. Static dirs, this also depends on how you're serving things. Because I'm using kind of just this generalist, Ubuntu-ish, Heroku style of serving everything, I don't have to worry about this too much. But actually, this is a list of these URLs paths. And you can actually list out a couple of them. And it'll check all of them. It does check them in order. And so if you have assets that are named the same thing under the same path but they're in different directories, be aware that one of them might get tripped and not the other. And they'll be in order. So that is kind of something to keep in mind when you're debugging. Now, moving onto our urls.py, I left the comments up there at the top. There are these URL patterns. So we import from web. So this basically says, take the web application that we made, and then take its URLs file, and import that. We alias it to web_urls. I think this is generally a decent practice. But you're welcome to do whatever you'd like for it. I think it's just very readable. And then we have urlpatterns. And this is kind of just my default for the root of the urlpatterns is I tell it to include all of our static assets, include admin, and include them in this order. Now, Django does care a little bit about your urlpatterns being roughly in order, well, actually exactly in order. It'll search them in order top to bottom. So if you have two conflicting paths, only the top one will ever be gotten to, as in if I had admin/ and admin/, and they went to two different places, well, we would only get to the top one. So then we have the static assets here. And then I just include all my other urlpatterns down here. Now, I am generally a fan of using just some sort of regular expression version of a path or generally a path, either/or, to get us to certain applications underneath. I like to use this regular expression path with an empty string to match all, so that any URL that is typed in will get us to that. Now, include means, actually, this isn't exactly where my URLs are. Go look at the web URLs. They're all configured particularly for the web application. That'll tell you more about how to match things and what to do. So this is pretty general. It's not going to tell us too much about how what works underneath. But it allows us to separate out, our web URLs live in web, our API URLs live in the API, our email URLs live in email. And that way, we can separate out functionality a little bit cleaner. This just tells us what to do, where we go. You might include some sort of directory here. I think for web, that should actually be the default. But it is pretty reasonable to include api/. And then anytime they go to like api/ blahbity-blah, they actually get directed to a different part of your application. Cool. So we now have that. urls.py has been examined. Now we go into web, which is going to have all sorts of other things in it. It has these migrations which we mentioned before. They're not going to be super useful to us yet. But we'll get there. And then we have admin.py. Again, not going to matter too much to us right now or probably throughout this. But it is a very useful thing to understand. This allows you to take all the models that you build, all of those beautiful classes in Python, and put them onto an admin interface that is usable by the general user of your site who doesn't necessarily need to understand a whole lot of Python. It also lets you create instances of models and stuff like that. You have apps.py. We're not going to talk too much about apps.py or tests.py, though they are useful. And then you have models.py, which is where we're going to actually create all of our database structures and interfaces there. We'll get there in a little while. We have urls.py, which is very generally built. It does very little right now. But we are going to point out that it imports all of the views from this application. And then it does that same kind of catch-all pattern matching to just say, hey, anything that comes through here, throw that to this, which is index. Now, index is something that I assume I imported from web.views. If I was a better developer, I probably would have explicitly stated that index was imported from web.views. I am, however, a lazy developer. This is not necessarily the best practice. It's not clear to me immediately where index came from. However, I think that it's tolerable for now. If you wanted to be a much better developer, you would be very clear as to where exactly index came from, otherwise it seems kind of magical. And then, speaking of where index came from, we'll look at views. And within views.py, we actually have access to index. So if you're confused about how views work, they're actually something very similar to something you probably hopefully already understand, which is Python functions. And all they take in, at least by default-- and they can be expanded quite a bit-- is this request objects, so that's your HTTP request, it's either get, post. Oh, there's a bunch of other ones like put, patch, and a few other ones. We'll talk mostly about get and post. And then it returns this very basic HTTP response which just takes in some HTML as a string and puts that back out to the user. And this is independent of whether it's a get, post, put, patch. It doesn't matter. It will do this every single time it receives a request. And I imported HTTP response from Django's HTTP submodule. That's generally, I think, a decent way to start. It lets me just know that I got things working correctly. And speaking of getting things working correctly, let's go ahead and verify that that's actually true. We'll run the same command that we ran before. It's going to print out the same error about unapplied migrations. It's correct. We have not applied our migrations. We don't really have a whole lot to apply. And when I reload the page, I get to Hello comma world, exclamation point. Hello, world! And so I have gotten here. It's very ugly. It's mostly white space. But we do have that in the upper left-hand corner. And that should make sense to us, because we created a view that controlled any sort of response so that after I put in the URL into my browser and it sends a request to our basic 127.0.0.1:8.8.8.8, then Django said, OK, well, we should go and match that to whatever matches first here. It doesn't match admin at least. It doesn't match anything in static. But it does match the everything wildcard. And that says, go into weburls. OK, so let's go to weburls. And that says, match anything, then take that request, and hand it off to index. And that's in our views.py. So we look at index. And we say, OK, we take in some request. That was the HTTP request that we got. And regardless of what it was, just return this open-handed hello world using the first type of header. That was a mouthful. And that's what happened. So I am very glad. I don't know if you're very glad, but hopefully you are too. I'm particularly glad that worked beautifully. And so yes, that actually is what ended up going on underneath the hood. And we roughly understand a lot about how Django works generally. Now, I say that the URLs can be configured to be kind of magical. And we're going to see that in a hot second. That is kind of a weird expression in English, just as an FYI. So we're going to go up into stage 2. Cool, we have very similar things in stage 2. We haven't modified much. I tried to make these pretty reasonable stages but we're going to actually write some HTML here. And so the way HTML and templating in general works in Django is it uses Django's built-in templating engine. You can actually modify the engines underneath. Django's settings.py is extremely versatile. It allows you to configure all sorts of environment variables and just variables in general for Django to be using. And anything in Django, any subapp, any subfile can actually access all of the settings configs. And we'll see that in a second. Actually, we saw that in rango's urls.py. We import settings up here at the top. And you can access any of the conflict variables in settings with settings.configvariablename, just something useful to know. So we didn't really change this urls.py very much. We don't need to in order to add templating. Static has not really changed a whole lot. Nothing in rango, the base app, has really changed. However, in web, we have modified a little bit of what's going on. Now, we have built some templates. In fact, we've built two. They're index.html and posts.html. We're building a randomized blog, because I thought that was funny, and it made for a cool name. So it makes sense that we would have some home page or index and some posts page that lets us deliver our posts. Now, I wanted to be a little bit contrived in classic CS style. So this is kind of a derpy page. It's got very little in it. In its body, I've got container fluid. That's from Bootstrap. But I basically just display "Hello, this is rango, the random Django blog." And then there's a bunch of other stuff that's included. This is just-- I basically just copied it from Bootstrap's documentation. I like Bootstrap quite a bit. I think that generally, you should build a web app from complete scratch before you actually start using some of those frameworks. It helps give you a better intuition for what's going on. But there's some other tags up here that we're not going to worry too much about. And then I copied and pasted which, according to see CS51 here, you should never ever do in code. I think it's like a never, ever with a star. Generally, try not to. And as a good rule of thumb, if you're copying and pasting code, you could probably abstract something out or build something a little bit better. It's almost like I'm foreshadowing what we're going to do in a second. But I copied and pasted this code. And so we have basically the same page, except in its body, it displays whatever the content of that page is, a rango post, because rango Django, random Django. But in here, we have a paragraph tag. And we have this curly brace curly brace, variable name, curly brace curly brace. And I say variable name like that because it's not intuitive that that is a variable name. But this is templating syntax. It's the first of a little bit that we will see. It's the only one on this page. And what it says is that somewhere in this nebulous Django templating engine, post_content has been defined. If it hasn't, Django just goes, oops, and just runs it, because it's a little bit weird to display some sort of error. Django actually does have templating errors that can occur. And those are great. The more errors that you can catch and the more modular and atomic they are, the better, because then I can know, oh, exactly what I'm trying to fix. The generalist catch-all, which we actually do do later in the views.py where we do try except:, there's no exception in particular that is caught. That's awful. How are you going to know what exception was there? How do you know what went wrong? It's really good for making sure that your application doesn't stumble and fall. It just kind of stumbles, looks around, and hopes no one noticed and then keeps going. But generally, you want to know, when you're developing, what exactly went wrong as precisely as possible. So templating errors are really cool. And the reason that those can occur in Django is because templates get rendered before they ever get sent to the user. So this will never be visible to the user. This will get replaced with whatever it is as a variable. And you use this curly brace-curly brace to indicate that I want this to be in there. It's generally used for some sort of content. And so this should be probably some sort of string, because it's going into a P tag or paragraph tag. And so hopefully, knock on wood, the developer who built this-- that's me-- made sure that this was some sort of string. And you might be wondering, cool, cool, where? Where was this defined? And that's a great question. I'm really glad you asked. So if you go into your views.py, we've modified that too. Go, us! And so we actually have this same index. But we have, also, posts. And you might be going, you said the same. You lied. That's not the same. This is no longer an HTTP response, at least not explicitly. It's a render. And then we have passed than and the request itself as well as the name of one of our templates. Now, there's a lot that had to change in Django to get this to happen. But let's say that we went and we return render of request index.html. And then we do this weird context as a dictionary. And then we say context at post_content-- hey, that looks familiar-- is defined as this long string of lorem ipsum. I pulled that from an API, by the way. I did not build it myself. I also did not manually type it. I can't type nearly that quickly. I'd still be typing it. And so then we have return the rendering of-- this is very similar up to this point-- of the return above where we render-- this is the request that was passed in. It lets us set session IDs and variables and caches and cookies and things. And then this is where we're actually going, like what web page do we actually return to the user. And then this, context=context is a very convenient way that Django passes, things into the templating engine. So I've defined, as one of the keys of the context dictionary, post_content. And its value is this string. And you'll notice that we have guaranteed, kind of by hard coding, that this will be a string. And so by doing that, we now have access to this post_content variable within our Django templating engine. And hopefully we're very happy. However, if you were to just do these things, it still would not entirely work. We have to make sure that Django knows how to get to posts. I still use this general catch-all to get us to index, or our home page. However, if you pass in posts now, you actually go to the posts view. I'm still using import*. However, it would be equivalent and probably better coding to do posts,index. That's not what I wanted. Cool. And then you might be going, excellent. This will now work perfectly. And you'd be almost correct, except it won't, because templates is not something we explicitly tell Django to go look for, at least a we might not have noticed where we told it to do that. We told it that in settings.py in our root app. But we actually never installed web. So we're going to want to go up into rango, under settings.py. And we're going to look into installed apps. And you'll notice web is now there. And the reason for that is because Django will check each of these things, each of these apps for a templates directory. And it'll say, hey, if you have a templates directory, kick those into the things that we actually check in order to render templates. So when I say return render a request and some template name, it's going to look in these ones' templates directories. And then we'll go, oh, bummer. And it's very common for people to forget to do this, yours truly included, even now. And then they go, why. I made this template. Where is it? I don't understand. Why is Django not working? It's the equivalent of yelling at the compiler when you're doing C development. It's not my fault. It's your fault. And then you're yelling at an inanimate object. And people think you're weird. Welcome to computer science. So installing this saves you a lot of trouble and frustration. And it makes you look a little less weird. So just remember to do it. And now everything is all wired together correctly. So we might go, OK, excellent manage.py. And this command should become second nature to you. I'm actually going to stop manually typing it out after this. Oh, no, this is why you stop manually typing that out. There we go. And you'll notice, if we open up that output a little bit, I still have 15 unapplied migrations, unsurprising. I kind of am deliberately ignoring that. And we might re-render this. It'll take a little bit longer, because it's actually rendering something now. And it's doing its job a little bit better. Yeah, the other thing about live demos, anytime you're doing real coding or just anything that depends on some sort of time constraint live, it's kind of like when you need a printer. It'll never work. The internet stops working just perpetually. And this is a locally hosted thing, and it still stops working, which is ridiculous. So you'll notice, in even smaller text, because I thought you guys needed a little bit of eye exercise, you'll see, "Hello, this is rango, the random Django blog." I think I'm funny. And nothing else, so it's ugly as heck would be a way to say that. But if we going to posts, this should change, fingers crossed. And we get there, and this is a rango post in much bigger text. And then you get the cool lorem ipsum blah, blah, blah there. And that is exactly what we hopefully expected, because we didn't do any real fancy formatting with Bootstrap or anything of the sort. So we get this. And so we now have successfully looped up some of what Django actually does, which is fantastic for us. Now that we've gotten through there, we're going to go through stage 3. And you'll notice, hopefully, that as we kind of get going, we'll get going a little bit faster and faster, because things will not really-- we won't have to change as many places. And we'll just be changing a little bit. Also, you guys will have a very good intuition for what's going on. Or y'all will have a very good intuition for what's going on. And we won't have to explain nearly as much of what exactly is changing. Now, in stage 3, we've said, OK, it was really annoying to re-copy and paste. I alluded to this at the very beginning of the last stage. I said, you know what, we actually should probably make this a little bit more extensible. Our developers are all in a mess. They all keep complaining that I change code, and then Sally changes code, and then Bob changes code, and no one knows who changed what or when or who's on first. So it's really frustrating. So we do this. Now, there's all these curly braces. And you're like, dude, you just changed everything. Nothing is the same. What the heck? And you'd be roughly right. So what we've done is actually quite a bit. We've said, hey, there's this curly brace percent sign, not a double curly brace anymore. And what that means is this is like Django templating run a function. So we run the extends function. That can only be done once at the top of the template. You can only extend one other template. But you can chain a bunch of extends. And so we extend base.html. And you're like, Nick, we didn't write a base.html. And I'm like, you're right. We're going to go point out what that is. However, you might intuit that that is going to just kind of, do a general layout for what's going on. Then we do this load static. And you're like dude what the-- where'd that come from? And I would agree. It's a little confusing. But this is alluding to our static files. It's generally a good thing to include if you're going to use your own custom static, which is what we did here. Now, there's these blocks. And we'll see what those are in a second, because they're actually defined more similarly or more understandably in base.html. We have a block title. Hopefully, intuitively, that's the title of the page. This is home, because it's index. This is a block CSS. It contains all of our custom CSS that we don't necessarily want to import to every single page but we want to import it whenever we need to. So it gets included here. And then this is just our custom style sheet. And since it's locally hosted, it's defined by our own static. We actually use this static tag which will regenerate the URL based on where our static assets live, which makes static assets being locally hosted versus a CDN hosted very plug-and-play. You just swap out the back ends. And no one else notices. And then we list out it's relative path. And we have block body, which is the body. And it has all the same content as before with a little bit more styling due to bootstrap. I don't like things looking too ugly. And so I included some cuter snippets here, whatever. And then we look in posts, and it's something very similar. But you'll notice, it's a lot less code them before. We're at around 20 lines for roughly the same content, even though it'll look a little bit prettier. And so you'll see the post content is still here. This is under body, which makes sense. We include the same style sheet. And then we have a different title. This is posts. And we load static and extend base.html. I don't know why I went through that in reverse order. If that was confusing, just watch the YouTube video in reverse. And then we're going to go look up at rango. And notice, it also has a templates directory now. And when we go into that, there's base.html. And you're like, oh, thank god, that's where it went. And this has all of our standard doctype, html, charset, all this. And then you'll notice these empty blocks. This is a CSS block that has nothing in it. And then you'll go, oh, of course. When we did our index.html and our post.html, we wrapped it in the same block CSS and block, but we put stuff in there. We put stuff in the middle here. And that actually just gets copy-pasted onto this. Same thing for title. It does the exact same functionality, except this is in the title tag. And then I added a nav bar. Don't worry too much about what that is. I just copy and pasted from Bootstrap. Bootstrap's great. They do all sorts of cool coding for you. Just copy and paste it in. And then I added the block body here. And we added this JavaScript block. We don't use it yet. But it's a decent practice for not including all of your JavaScript all the time at every page. It can be very heavy to include. And by heavy, I mean it takes a while over a network. So again, I'm going to stop typing that out by hand. We're going to again run run server. We again, have unapplied migrations. None of that should be counterintuitive. What you'll note is that it looks a little bit prettier now. We're still on posts. We have a nav bar, which is great. Rango, Random Django, Rango. And then we have a jumbotron going on. That's a bootstrap thing. And we have cooler formatting. It looks a little bit prettier, give or take. It was a low bar. It was very easy to make it look prettier. And we can now switch between the two. However, this is not a great blog, in that we only have one post. It's a hardcoded post. And that's kind of derpy, not that this whole thing isn't derpy. But it's a little derpier than usual. So we're going to go back here. And sorry if I speed up a little, but I don't want to run out of too much time. And we'll go to stage 4. And we'll say, OK, so we've gotten some things working a little bit better. Also, I didn't explain this very well. And I will stop and explain it for a second. The reason I put this base.html in the root project is, generally speaking, my whole web app should be pretty consistent, I think. And so I want to put this base.html in the thing that governs everything. I think that that makes a lot of sense if you're inheriting something that is standard across. No matter what kind of other applications we have running here, they should all look the same. It should be very user-consistent. I don't want my user to be jarred by like banana slug orange. Yellow? And so that's why I actually rationalized putting this template set here. In CS50, we use layout.html instead of base.html. I just think base is more intuitive for myself. And I think in general, it doesn't change much about the semantics. So that is what's going on there. We are now in stage 4. And what's going on here is very similar. We're actually now changing a little bit about what's going on in the back end. So we go into views.py. And we haven't changed too much. But I've been putting this requests library, which, for some reason, I thought was included by default in Python. I'd just deluded myself by having it installed on my base computer. So if we go into here, we'll see posts has changed a little bit. And so I use this list comprehension here. If it doesn't make any sense, I'll explain it really briefly. But what is worth shouting out is lorem ipsum-- or loripsum-- hmm, never really read that. loripsum.net, they built all their own stuff. Theirs is really cool. I take no credit for that. I'm just using their API to get a bunch of blog posts for our random Django application. And then you'll notice that my context is now a list. So we're going to go look at how that changed our templating engine syntax, which is now a list of each of those posts. I do like this element.strip. That just takes off new lines and whitespace on either side of the string. It assumes that it's a string for element in. This is the response that we got from our beautiful API. The text version of that response, I happen to know it delivers text, the plain text part of their API. And then I split it by lines. And I stripped off all the whitespace on either side of those lines. And as long as there is an actual element there-- so in case they had a double new line somewhere, which would end up being its own element in this list, that just gets ignored, because it gets counted in Python as not existing or IF fails. So this is just kind of a really convenient way. It's pretty readable, I think, once you get used to it, for just going through and doing that very quickly. And then we do the same thing as before. We deliver this request. We have our posts.html and then our context. And that all should hopefully make sense to everyone. So then we're going to go look at how that changed our templates. So if we go into templates, posts, most things haven't really changed that much. We've added JavaScript. And we're not going to talk about that too much, because this is about Django. But I thought it was kind of cute and quirky. I'm a huge fan of randomness. So then this has changed, though, for us. So what's going on here is, it's roughly like writing Python, just in HTML. And the reason I say roughly like that is because we're not actually writing Python in HTML. There's no running of some sort of Python code in the browser. But on our side, on the server side, we do actually execute some code and generate some HTML. And this might not seem too big of a deal to those of us who grew up in this generation. However, as someone who used to not know what this was, what templating engines were and wrote out all of his own HTML, it sucked. It was awful. So writing this is much more eloquent. I can do things very extensively. And it's a much more dynamic way of writing code. So we do four posts in posts. Now, you'll notice posts is the new variable that I actually send in. It's defined as a key in our context dictionary. And when I go through posts, it's going to just take each item in that list, which is the value of posts in our dictionary. And it will give me access to that variable here. And so I now can run and do all sorts of actual dynamic generation of a bunch of posts. I can change how many get rendered. That's totally reasonable for us. This is Rango Post #. And then I do the forloop.counter. I believe it's counter 0 if you want a zero-indexed counter. Otherwise, for loops in Django's templating engine syntax actually are one-indexed. I don't know the reason for that, but I don't mind. Again, more unapplied migrations. We're ignoring that for now. We'll get to it, I think, in the next stage. But hey, we now have a bunch of posts. And you know when I said that the JavaScript was kind of cute, it changes the-- eh, yeah, they're different colors. But you'll notice we have all 10 posts. And that's great. That's what we would expect to see. If we reload the page, they'll all be different. And that's because the API just generates them randomly. So that's very cool. It's very random. It's very Django rango. And if we go to Home. That should still work. It hasn't really changed a whole lot. I think I might have changed some words or something. Who knows? But the main focus being here that we now have a list of posts. They're all separated out. You might think this is ugly. I might agree. We can all just move on. And so we can now go on to stage 5, which is here. There we go. And when we go on to stage 5, we might be saying, OK, what else is there to do. You've really taken this contrived concept. You've beat it into the ground. You've drilled it into our heads. And we're going to do that a little bit more. So now, when we go into web, same directory structure. You should be like, yes, we are familiar with this. Don't say it again. And I'll be like, yes, you're right. We've added another one so that we can actually modify what's going on here. Now, this looks quite a bit different. But you'll notice it's actually not too bad. It's the same thing here. This is what we've seen before. But I've added another version of it. There's also this one. It uses another API, very well written, not mine. I did not build any of that API. That was built by Samir Kumar. And that's all I have on that one. But it's a really good thing. And it has a bunch of geeky jokes that I think are really funny, because, you know, geeky. And so that one just takes the first 10. This underscore just means don't actually assign it. Don't do a variable for it. Just count through 10. Range is what you use in Python to iterate through a range of numbers. And so then this says, if post_type equals equals geeky, which is a string. And you're like, where is post_type? Where do you keep getting these from? And I'm like, yeah, you're right. And so you'll notice, I actually defined context up here so that I can append to it down here if I need to. And then I have post_type. And you're like, wait, didn't you say that this only takes in a request, returns HTTP response? And I will say, yes, I did. I'm retracting that, kind of like your chem teachers did once you got to college. Things have been changed here. And I'm going to explain why. So if you'll recall, I said that URLs in Django can actually be used to do all sorts of really cool things. In fact, you can use them to pass parameters to the function that deals with it, the view. And so that's what we've done here is I defined some string. And I matched it with post_type equals equals geeky. And then I do something different based on that. Otherwise, just go back to our default, which is lorem ipsum. And so you look in urls.py, things have been modified a little bit. So we've done a couple of things here. But we're going to focus on this first path thing, which is posts/. And this takes in these like less than, greater than signs. I don't know the general term for these. And it causes me a lot of angst when I'm trying to explain stuff to people, because I'm like, this, this thing. And I use less than, greater than. If someone knows that, someone will tell me, I'm sure, in the left while doing so. So what this says is, if you go to posts/, and whatever you put here is going to be called post_type and sent as a variable or passed as a parameter to our view posts. And that's what we've done is we've said, hey, whatever gets passed there, match that with either geeky or anything. And then do different things accordingly. And that's really important to us. Now, you'll also notice, hey, where'd this come from. This is random and doesn't make any sense. I added a different view, because I am sneaky. And I said, hey, actually anytime you add a favorite, pass in the quote that you want to do. And you're like, what is this. This is so random. And I will say, random, Django, rango. And so yes, what actually is going on here is we are also adding in the functionality of being able to select certain posts as our favorites and have them get stored to the database. And you're like, database? And I'm like, database. And we're going to build a model. And so when we get to models.py, this is what's going on. I built a class, because, hey, models are just Python classes. favorite(models.Model). Yeah, models gets imported by default. So that's cool. And this just extends the model base class from Django. And then we say body, which is actually going to be a field in the model or an aspect of our class or, if you're really thinking ahead, you're like, wait a second, that's a column in our table called favorite. And you're right. And so they follow the same general practices as Python classes do. You generally will capitalize them. They will be not camel case but just uppercase every word, no spaces, certainly no spaces. That's absurd. But no underscores either. Generally, you'll see this. I only have one field here, because again, contrived random Django example, rango. And we do models.TextField. There's a great reference on Django's documentation that explains all of these different fields, all of their different parameters. And they take a million. And then I also define this underscore underscore string. It's a cool attribute to define for classes. It overrides the default and says, actually, just return favorite quotes number of this item's ID. That way, it doesn't do the weird this of objects, favorite object, or whatever it is. So that's actually a really convenient way of doing things. And I would definitely recommend it as a generalist practice. So cool. That also, I realize, might not make any sense, because it's on the admin interface, which I didn't talk about. And so if you were really curious about where this comes into play, go ahead and explore the admin interface. And that's really cool. So we've created a model. And then you're like, wait, but you also called this addfavorite thing in views. And I deliberately didn't show us that. And you're right. It's here. In fact, we do this really bad exception practicing where we say, hey, any exception that ever occurs, just return false, yay. And so we're returning JSON responses now. And you're like, well, that's new. And I'm like, yeah, you're right. And so what basically is happening here-- I'll try and get that all on this screen-- is, if for our method is get, if the request that's given to us is actually a get request, then we're going to create a new favorite object, set its body equivalent to whatever the quote was, and remember, that's the pattern or the thing that we passed in here. That's the variable from our actual URL, Django URLs. And then we're going to save it to our database and then return back to whoever sent this request as a response, a JSON response, which is imported up here, and have that be just the dictionary mapping the key data to the value true, because all of it worked. Everything was good. Otherwise, anytime an error occurred or things were unexpected, just return false. Now, I think you can hopefully see how this could maybe be built a little bit more eloquently. And I will agree with you there. I pointed it out. And so we'll talk about that in a second maybe. But if we go into posts.html, you'll notice we included a second JavaScript, favorites.js. We're writing all of those into our rango, static, web, js, boom. And once we get there, we might say something like, OK, cool. It does a bunch of stuff. It does some Ajax requesting. And we'll briefly cover that. It looks like this. We're not really going to discuss what exactly is going on here, because it's not relevant to Django explicitly. But it is worth going through and understanding. And it uses our add_favorite url, which is really the point of this. And then it appends text to it. That's the quote that we wanted to favorite. So now, if we do this whole run server shebang, this one will actually tell us that we have no unapplied migrations. I don't think that's true. So we're going to talk about what it means to apply a migration, to make a migration, to migrate. We don't mean like the birds. We mean like Django. Got to love all the weird animal references in coding for Python, because it's like a snake, but it's also a programming language. I think that's funny. Ignore my jokes. Manage.py, management interface, we're going to make migrations. And no changes were detected. So I must have done this by accident when I was tired. But that's OK. That is going to be the first step that says take all of the database changes of the changes to models.py, and update them. And then we're going to migrate across. And so you'll notice it applies across admin, off content type, sessions. Those are all built-ins. And web, which is good, because we included that. You can also migrate particular apps by passing them in as a parameter. Just apply web migrations. Things like that are really useful. You can also roll back migrations, roll forward migrations, things like that. And so we might then say, OK, cool, cool. I was doing a reverse search, by the way. And that was entirely useless. We can just go up, boom. Running the server, nothing else to check. We just get a bunch of output. And we're going to go, OK, this will hopefully do something. Cool, we got Rango. And so you'll notice, actually, in our nav bar-- it looks really small. I'm sorry if I'm straining your eyes. We have both lorem ipsum and geeky quotes. Lorem ipsum has not changed. This is ugly, and it's off to the side. I don't think I corrected it in this stage. It's corrected in the next stage. I generally prefer to center stuff. And we have quote one. I can click favorite. And it'll say, hey, quote one was favorited successfully. And I'm like, woo! And then we can go to geeky quotes. And we can say, oh, Chuck Norris can touch MC Hammer, funny. And so these are just kind of a funny set of jokes that are kind of cute. Again, pulled from an API that I did not write. And we might say, if by some incredible spacetime paradox, Chuck Norris would ever fight himself, he'd win, period. That's hilarious. We can then favorite it and say, cool, we thought that was hilarious as heck. We get an alert that says it was favorited successfully. So we might say, cool. You'll get all this output here, because we're copying quotes into a URL. There are better ways of doing that. But we're not going to really discuss them here. Oh, I'm a dummy. Control-C, Control-L to stop and then clear up content. And then we're going to go into stage 6 very briefly, because I am roughly out of time. So in stage 6, we have done a little bit of what is roughly the right wrap-up. We haven't implemented all of the features that are on the latest version. You can look at those in stage 7. But we have implemented quite a bit. So in stage 6, we've said, you know what, we should also be able to look at the favorites. We're just saving them to the database. What the heck, man? I want to actually understand what our favorites were. What did other people like? What did I like? Things like that. And so we'll see, hey, we rendered some favorites.html. And you're like, oh, that makes sense. And so again, we're just extending from base. This should all be fairly familiar. We just changed the title to favorites. And then we say, all right, iterate through every favorite. Call it favorite number whatever. We're giving them some ID now so I can use JavaScript to deal with them. And we do favorite.body. OK, so that's a little new. And you're like, hmm, I don't know where that might have intuitively come from. And everything else has just not changed. And so I would then say, OK, cool. Let's look at posts, make sure nothing here changed. Yeah, seems about right. So then when we go into views.py, index is still the same. We import all the same stuff, except we also import our models here. We did that before as well, but it's just kind of worth pointing out. And we go through. Posts looks right. That's good. Favorites, OK, that's cool. Favorite objects all, huh. So this is how you actually interface with retrieving things from the database in Django. I always love feigning surprise when I look at my own code. I'm like, wow, that's new. So basically, what we're doing here is whenever we get to favorites, the view, then we're going to say, hey, here's our context, favorites-- that makes sense, because we remember what we wrote in our HTML. And then we do favorite.objects.all. And we take just all of the objects that are in the favorites table, so basically just every row. You can think of this as like select* from favorite. And it just pulls them all up. It gives you a query set. So it's distinct from a list. But it's roughly just an iterable object that contains each of those rows as their own independent objects. And because each object is its own favorite object, that's where we actually understood that we could use, in favorites.html, favorite.body, because we know that as long as we have an actual favorite object-- that's a little bit weird in English-- we can actually access its attributes here. So that's what we're going to be allowed to render. Now, we've added some favorites. And you might be like, OK, but I'm not getting it to work, Nicholas. And I would agree. urls.py is still really important to us. We're going to want to make sure that in path, we added a way to get any request that goes to favorites to get to the view favorites and deal something accordingly. So in order to prove my point, we're going to run server. And we're going to double check there are no unapplied migrations, because we are actually interfacing with our database now. So we do need those to work. And we're going to say, all right, things should be different. And this is just going to render a bunch of quotes to us again. OK, apparently, I didn't fix this until way later. And so now, you'll notice up in our nav bar, we still have home, boom, rango, boom. Lorem ipsum and geeky quotes still exist, unsurprising. But favorites is now here. And if we click here, we should have some favorites saved. Oh, my god. There are a million tests, because I was testing things. And I guess that got saved to this. And I think it's kind of cute that they just kind of change colors randomly, because I'm a derp. But you'll also notice that the things that we favorited, minus this first one. That was a test. But these last three things that we favorited in this demo are all here, which is really cool. So they are listed as the favorites IDs. So this is actually the literally 32nd thing that was favorited. And then if you look in stage 7, I'm not going to walk us all the way through it. But I will run it. Actually, it is running on rangodjango.herokuapp.com. Stage 7 implements some other features. And I guess this will be a good place to leave off. It stylizes things a little bit more so we don't have quite as much whitespace. I believe it puts that favorite icon in the middle. It also wraps things in a border. There's some contrived and derpy stylistic changes that were made. But if I go and favorite this thing, it still tells me that it was favorited successfully. If i into favorites, I've actually arranged them by the number of likes they've gotten. And then you also have a like button on favorites where I can-- this one is at 3. This one's at 3. This one's also at 3. But if I click Like, then that one should go to the top, which is what we'll see as soon as it reloads. There are a million other ways to do this, all in JavaScript, things like that. But hopefully, you've gotten a little bit more familiar with what exactly is going on here. Yeah, that concludes our whirlwind tour of very basic web development, very basic Django, and very basic Python. Hopefully, you understand why I love using Python, why I love using Django, and why I think it makes web development very easy. Despite the fact that this is not Facebook, for example, and certainly not a super aesthetic app, it is definitely very cleanly written and relatively easy to understand. It also does something that is not entirely contrived, although you might look at that and go, yes, it is. And I would say, yes, you're probably right. But it is something that you can certainly build off of. I encourage you to explore Django a lot more. The documentation that exist for Django is fantastic. And I don't think that I could ask for anything better as far as good documentation goes. Python similarly has fantastic documentation. There are a million and one things you can do with Django. And I would recommend exploring as many of them as possible. This is also super mobile-friendly thanks to Bootstrap, so responsive design. Bootstrap is another thing, great documentation, very nice for beginners, and super easy to use and customize. And so all of these things, hopefully, have demonstrated to you or motivated to you why Django is something that is worth exploring and figuring out. It is a fantastic Python development framework. This is all deployed on Heroku, which is another great service. I particularly love using it. I swear I'm not paid by any of these organizations. And I just am kind of walking advertisement for them, because they've help me a lot in my own personal CS career. And then Waitress is another thing that just is used underneath to build all of this. All of it is documented in the GitHub repository, which I guess I'll bring back up through here. And so yes, you are welcome to go and explore and see what's going on in this Django whirlwind tour. I did call it Django rango. My apologies. You had to hear that pun, I think, 44 times-- 42 times, sorry. And so there's all of that. Hopefully, you've enjoyed. I really appreciate you watching this lecture here, now, in the past, and in the future. And if you have any questions, then feel free to leave them in the comments in the YouTube video. I am absolutely awful at answering them. There are millions of people on the internet who would love to answer your questions. And Stack Overflow, Google, all sorts of great resources, as well as the documentation for each of these things. So yes, this has been Nick Wong's CS50 seminar for Django. And I'm going to leave us on this cool matrixy background, because I think it's nifty. Thank you very much. I appreciate it.