Django - Lecture 7 - CS50's Web Programming with Python and JavaScript 2018

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC PLAYING] SPEAKER 1: Welcome back, everyone, to Web Programming with Python and JavaScript. So we've been spending the last couple of weeks working particularly on JavaScript, working on using JavaScript to be able to write code that runs on the client, runs inside the web browser, and allows us to have dynamic web applications that allow for animation, for instance, or for sending messages back and forth in real time without the user needing to reload the web browser. And so what we're going to do today is transition a little bit back to Python. So in order to build these more complicated web applications, we've been using a Python web framework known as Flask, which let us using relatively few lines of code pretty quickly set up a web application. We were able to control routes where users are requesting information at a particular URL. And we could direct them one place or another to show one page or another with whatever information we wanted. And we were able to hook Flask up to a back end database using PostgreSQL in order to make sure that our web application could manipulate data in order to add rows to tables, remove rows from tables, edit them, and so on and so forth. But a Flask is what we would call a micro framework. It's a pretty lightweight framework that doesn't have a whole lot to it, just enough to get us up and running. And so as people begin to work on more complicated web applications, there are a lot of things that start to become repetitive, that Flask will just start to get frustrating to have to deal with the complexities of trying to build things out for yourself. For instance, in many web applications you'll find that you need to add users and have systems for users to log in and be authenticated and log out. Or you'll want a way for users of your website, managers of your website to be able to add data to it, update existing information, add information to any of your various tables. Or you might have more complicated relationships between your data that you want to manage. And if you are writing a web application in Flask, you would need to build all of that out for yourself. You would need to design pages that let people add data to your database, have separate pages and let people edit them or remove that data. And all of that would be manual work that you would need to do. But because this is so common, there are other web frameworks out there that give you a little bit more out of the box. And so today what we're going to be introducing is a new web framework that looks similar to Flask in spirit but it is much heavier weight, has a lot more features and capabilities that it has built into it, known as Django. And we're going to be exploring Django and some of the features that it has to offer when it comes towards building more complex web applications. And our goal as we look through all of this is that we'll start by looking at simple web applications where it seems like we're doing the same things that Flask could do, but it seems like it's just a little more complicated. But we'll soon see how using a framework like Django, we can actually take advantage of the leverage the Django gives us in order to add a whole lot of additional features and complexities and nuances to our application that would take much more time to build out for yourself by hand using something like Flask and that Django just makes much simpler for us to build. So let's go ahead and get started by building our first Django application. And so once you've install Django, and in this course we're going to be using the latest version of Django-- Django 2-- which was released pretty recently, you'll run a command that looks something like this. You'll get access to a command called Django Admin. And Django divides all of its web applications into what are called projects. So think of a project as just your entire web application that might be composed of different parts, which we'll soon see. But to create a new project, you'll run a command like django-admin, which is the name of the command, startproject to create a brand new project, followed by the name of the project that you want to create. So let's take a look at an example of that. If I go under the command line and I type django-admin startproject and I'll just call it mysite, for instance. What that's going to do is it's going to create a new directory for me called mysite. And if I cd into it, I see that I have a file called manage.py and another directory inside of it also called mysite. And if I go into that directory, I have a whole bunch of other files that have just been created for me. I didn't create any of these files-- init.py, settings, URLs, and WSGI. These are just files that are created for me by default when Django first creates a project for me. So what are these files and what do they mean? Well, this init.py file tells us that the mysite directory is a Python package. And a Python package is just a collection of a bunch of different Python files that are grouped together for some purpose. And Django was really built on the idea of these Python packages where we're going to end up with multiple different Python packages, multiple different groups of files that each are serving a slightly different purpose. And Django is going to allow us to have a web application that uses and integrates with all of these. Manage.py is a particularly important file. You won't need to edit manage.py. But manage.py is going to be a Python script that you can use in order to perform some very useful operations on your web application as you're working on it. And we'll see multiple examples of using manage.py later. Settings.py is a file that's created for us that just has all of the settings for our web application, things like what time zone the application should be in or what other applications are installed within our project, for instance. Or what sort of database do we want to use with our application. These are all things that are defined in the settings. urls.py is interesting. We'll take it urls.py in a moment. But urls is going to do is act as the file that takes care of determining what URLs or what routes the user can go to when they visit a web application. So in a Flask, if you recall, at the top of every function, how did we indicate how to get there? How did we in associate functions with what URL the user had to type into the web browser? Yeah. We used this route decorator. We say, atapp.route followed by the route that we wanted to go to. And when a user typed in that route and press Return, that would be associated with that function. And every function had the route on top of it that corresponded with that function. What Django does is it separates out this functionality into two different places. So we have one file called urls.py which only contains the URLs that people can go to. And it associates those URLs with the particular functions that are ultimately going to run in order to render the response that we want to give back to the user. Wsgi.py, you don't need to worry about too much. But if you were to ever take a Django web application and deploy it to a web server, for instance, so that it could live and hosted online. wsgi.py is a common way to allow that to happen. And all of that, of course, is stored inside of this project name directory. So now that we have all of that set up, the next step is to create what's called a Django application. So a Django project is our big picture project that contains all of the files needed for our website. And a Django project consists of one or more Django applications or apps where each one of these individual apps usually just serves a particular purpose or has some particular function. Django has some built in apps for us. And what we're going to do is create an app within this project that's going to handle the routes that we care about in this case. And for this simple project, we're just going to create a single app that's going to allow us to take care of some basic routing, very similar to what Flask allowed us to do. So to create an app, what we're going to do is inside the mysite directory, we're going to go and we see this manage.py file, which is the file that allows us to run some basic commands on this Django project. And the command here is a python manage.py startapp followed by the name of the app that we want to create. So let's create a basic Hello World app, much like we did when we created our first Flask app. And we'll call this app hello. And so what that's going to do is give us a new directory called hello inside of mysite. And inside of this hello directory are a whole bunch of other files. And we won't worry too much about these just yet. But we'll get to them soon. And so now, let's actually edit some of these files and see what's going on inside of all of this. So hello is the name of the app that we've just created, this hello app. And inside of views.py, this is analogous to what application.py was inside of our Flask application. It's the place where we're going to be writing the code that ultimately decides what the user sees when they go to a particular route. And so in this case, I'm going to define an index function much like I have before. And in Django, all of our view functions are going to take as an argument this object called request. And you may have seen something like this in Flask where we had a request object that contained information like what were the arguments that were passed into the request and what sorts of cookies, for instance, might be associated with that request. And so this request objects are as much the same behavior in this case. And all our index function is going to do right now is return some basic HTTP response that says hello, for instance. And so in Django that's going to look something like this, something like hello world. And of course, HTTP response is not an object that we've created. It's something built into Django. So in order to use it, I'm going to have to import it. So from django.http import http response. So immediately what we're noticing is that even just to get something as basic as hello world, it looks like there is a lot more complexity involved in it. Soon the reason for that complexity will become clear as we begin to build more sophisticated, more complex web applications. But for now, try to focus on drawing the parallels and analogies between this and Flask rather than try to worry about all the specific syntax. So we've added an index function that says hello world. Is this going to be enough now for someone to be able to go to a URL and see hello world appear in their web browser? What are we missing, at least if you're comparing this to Flask? AUDIENCE: HTML, or-- SPEAKER 1: There is no HTML, certainly. And so later on what we'll see is we'll modify our view slightly so that we can return a fully fledged HTML file. But even if we were to add HTML here, what else is missing if you think about how this file compares to a file in Flask for instance? AUDIENCE: A route. SPEAKER 1: A route, yeah. I didn't specify at what URL the user is going to. Is it just the empty route like the default slash? Or is it something else they have to type in the URL in order to get to this function? I haven't defined any of that yet. And so what I'm going to do inside of my app is create a new file. And we're going to call it urls.py. Notice this is a different urls.py from the other urls.py we had. The first urls.py, which is inside the mysite directory, is the project's URLs file. Whereas this urls.py file that I just created is a urls.py file specific to this particular app. In this case, this Django project, I've only created one app inside of it. But you'll see, as project start to get larger, that oftentimes Django projects will be composed of multiple different apps, each of which has their own routes. And often this is a good way to separate different types of functionality into different places just to keep your entire project organized as projects tend to get big. And separating things out into separate applications helps to keep things organized, makes those individual apps reusable if need be, and can ultimately be a good organizational tool for a Django project. So inside of urls.py, we're going to need to include some code that looks something like this. We're going to import this path variable. And also, we're going to import from dot, in other words, the current directory, the current package, we're going to import our views. So we were just editing views.py over here where we defined this function called index inside of it which returns hello world. And now inside of urls.py I'm going to need to tell Django that I want to associate a particular URL with a particular view. And the way Django does that is through a variable called urlpatterns. And urlpatterns is just going to be a list of all of the URLs that are supported by this particular app. So in this case, I'm going to say, path empty string meaning empty route. Just user type slash. They don't need to have anything after the url in order to get to this particular view. And then I need to associate it with what function, what view function, do I actually want to run when someone goes to this empty route. And so in this case, it's going to be views.index because views is this Python module that I just imported, this views.py file that I have here. And the name of the function inside of views is called index. And so I'm saying, when the user goes to this empty route, here's what you should run, go to the views file, and run the index function. And here is views.py. Here's that index function. And it returns an http response, hello world. So that's what that's doing. And we're almost ready to make this application actually work when we run this project, except we're missing one last piece, which is that when the Django project starts up and starts looking for URLs, it's only going to look at the URLs file of the entire project, this mysite urls.py file which you'll notice already has a whole bunch of code in here already. And we'll take a look at some of what this means later. But what I need to do is this last final step is to inside of the projects urls.py file, link it to my app's urls.py file. So this was the urls.py file that was created for me when I first created this project. And the hello urls.py file is the file I just created. And I need to tell these two files about each other, make sure my project file know that my application's URL file exists. And the syntax for doing that is a little unclear at first. But what we'll do is add another path that just says if someone goes to empty string the empty route, we're going to just route them to the hello app's URLs and have hello take care of all of that. And so the reason you might do something like this is that oftentimes if you separated things into multiple different apps and you want those different apps to be handled by multiple different routes, for instance. You might say, later, something like if someone goes to the app one route and goes to app1/foo or app1/bar, then you might want to direct them to app1'd URLs. And if someone likewise goes to app2 slash something, you might want to route them to some second app's URLs. And so this file is the first place where URLs are dispatched from, that when someone goes to URL, we look through this file. And if it turns out we should send them one way or another, then we'll send them to potentially a different URLs file after that. But for now, this is all we need to say, just direct the person to the hello app's URLs. Yeah, question? AUDIENCE: How does it know where [INAUDIBLE] parallel to mysite directory [INAUDIBLE]. SPEAKER 1: So good question. The question was, how does the application know where the hello module is. When we're running the application, we're going to be running the application from the parent mysite directory so that the parent directory can see all of the individual Python packages. Great question. All right. So after all that complexity, we're finally ready to actually go ahead and run the application. And the way we run the application is again using manage.py. We do python manage.py high and there is a special command called run server such that when we type run server, that's going to start up this application. Here's the URL where it lives at. We'll go ahead and copy that, paste it into a web browser. And now we see hello world. So that was our very first web application in Django. It seemed quite complicated. But we'll soon see in just a moment how we can actually use the additional features given to us by Django in order to make our application a whole lot more powerful. But before we move on, questions about anything we've gone ever so far? OK. So what I thought we'd do now is take a look at that Flight application that we created way back in one of the earlier lectures when we were first introducing Flask and databases where we wanted to create an application that could maintain flights and passengers and so on. And we'll take a look at how we can quite easily build this application in Django and actually make it even more featured than our Flask version using potentially fewer lines of code, in fact. And so what I'm going to do is I've already created a basic application called airline. And all this airline Django project does is it has an app within it called Flights. Inside of Flights is a URLs file that looks almost exactly like the one we just created that just directs the empty route to the views.index. And inside of my views file, I return an http response that just says Flights. And so all my application does right now is sort of what we just created in our hello world example, is that if I run the server and go to this URL, it just says Flights instead of hello world in this case. But what we want to do now is start to set up the database for this application. And so Django was designed with wanting to interact with data in mind. And so it makes it very easy for us to do something like this. So what I'm going to introduce now is this models.py file that's sitting inside of my Flights application. Right now it's empty. And Django by default just says, create your models here. This is the place where I'm going to start to define the classes that are going to define the types of data that I want to be able to store inside of my database for this particular application. And so, this is very analogous to what we were doing with Flask SQL Alchemy, which was a separate package that we had to install in order to allow us to do these same sorts of things. Django has this all built in into its own ORM which functions very similarly. So you'll see a lot of parallels, although the syntax is a little bit different. And so if we want to create a model for representing a flight, we might define a class called flight which is going to be a type of model. And if we recall from last time, what properties did a flight need to have? What stuff did we include inside of our flights table? AUDIENCE: Origin and destination. SPEAKER 1: Origin and destination. Great. So our origin, for now we're just going to represent as a character field. And we'll say the maximum length is going to be 64. Not all databases require a maximum length. But for some databases, it can often be helpful to do so. So if you know that there is a bound upon the length of a particular field, that's often good design practice to enforce that in there. I believe that there are no cities that are longer than 64 characters. So that feels like a reasonable limit to place on the origin of a flight. For a destination, that's likewise going to be a character field that we'll say has the maximum length of 64. And finally, we might also add a duration field that is going to be an integer field. And that is going to represent how long the flight is. And I could do other things, certainly. And Django comes in with all of these built in types of fields, so character fields, integer fields, Boolean fields, and other types of fields that map to different types of data inside of a SQL database, for instance. But this will be quite simply a basic flight class that is going to represent origins and destinations and durations. And so now what we're going to do is introduce the concept of migrations. And so what will often happen is you build a web application is very rarely is it the case that you will, when you first start building the application, define all of the tables exactly the way that you like them with all the columns that you want. Because usually you'll start out with some basic framework. But as your application starts to grow, as people start to use it, as you start to want to add features, you'll want to make modifications to your database. You're going to want to add tables. You're going to want to modify the columns in those tables or modify the relationships between them. And it would be quite tedious if every time you wanted to make a change to your database, you would need to not only change this sort of code, this model code that defines your classes, but also likewise actually run the the like Alter Table commands in SQL on your database in order to actually make those changes happen. What Django does is it uses something called migrations to solve that problem for us. What Django is going to do is automatically detect any time we make a change to our models.py file and to automatically generate the SQL code necessary to run on our database in order to allow for those changes to happen inside of our database. And so I'll show you what that looks like. So what we're going to do is-- oh, and before we can do this, there is one other configuration step that we just need to take, which is that we need to make sure that this Django project knows about these models, that my project know is that this application exists and has these models. And the way that I do that is by going into the settings file for the application and going to this installed apps variable, which is a list of all of the apps that are installed on this Django project. There are already a whole bunch of apps in here. These apps just happen to be built in apps into Django that serve easy purposes. So we have an admin app, which we'll look at later, an authentication app which we'll look at later, and other apps that serve other purposes that many web projects might want. And we'll see examples of those later. But right now, we can just say that we have this Flights app that is installed. And the way to indicate that we actually have this app installed is to include flights.app.FlightsConfig. Reason being, inside of our flights directory here, we have inside of it apps.py which is a configuration file for the application. And inside of that is FlightsConfig, which is the class that defines the settings for the application. So no need to worry too much about that. But just know that we do need to insert this line here just to say this application is installed on this project. But what we really want to do now is to create the table for managing flights inside of our database. So what is that going to look like? What I'm going to do is, using this manage.py script yet again, I'm going to run python manage.py makemigrations. What python manage.py makemigrations is going to do is look through my model files, look for any changes that have been made to those model files, and automatically generate a migration, which is going to represent the changes that need to be made to the database in order to allow for these new changes that I've made to the model file to actually work. So I run python manage.py makemigrations. And what we see is that it's created this new file called 001_initial.py. And it said it's created the model Flight. It's detected that I've added this new Flight model. And now it's telling me that it's now been created. So what's inside of this 001_initial.py file? Well, if we take a look at it, we see that it's created this class called migration. And inside of it, it's got all this information about things that should happen to the database. In other words, we should add an ID, origin, destination, and duration column to this new model that we're creating. And notice that I never actually specified in Django that I wanted an ID column. Django adds an ID column by default because it assumes if I'm creating a table, then it's going to have some sort of primary key column by which I can identify individual items in that table. So I don't need to explicitly create an ID column for every single one of my tables. Django takes care of that for me. So what would actually happen if I were to apply this migration to a database? Well, we can take a look at what that code would look like. You likely don't actually ever need to run this command unless you're curious to take a look at it. But what if I run python manage.py sqlmigrate followed by the name of the app, Flights, followed by the migration number 001, what I'm going to get is the SQL code that actually corresponds to this migration, migration 001. And what we see is it's a command that looks very similar to the types of things that we were doing way back when we first introduced SQL. It's the Create Table command. It's creating this table that has an ID column that's a primary key, has an origin column, a destination column, and a duration column. And that's all SQL code that I didn't need to write. It was generated for me by Django's migration system just by looking at what I said I wanted in my database and generating the code to run that for me automatically. And so now, if I want to apply this migration to the database, actually now creating the table inside of my database, I'm going to say python manage.py migrate. And what that did is it applied to all of these migrations, including some default Django ones, but it also applied this flights 0001_initial migration. And now my database is going to have this table that represents flights. Questions about the migration system so far, before we move on? Yeah? AUDIENCE: How does it know what database to make changes to? SPEAKER 1: Great question. How does it know what database to make changes to? That's all defined inside of the settings for the application. And so if we go into the settings.py file and scroll down to this databases variable, what we'll see is that we've said that the engine for this database should be SQL Light. And SQL Light is just a slightly different version of SQL, much like PostgreSQL, that happens to store everything locally on a file instead of running separately on some server somewhere which makes it very useful for local testing. If you wanted to use PostgreSQL as we did in project one on a project in Django, you could change the engine to just be a PostgreSQL database, which Django supports. And in this case, we've just specified what the name of the databases is. In this case, it's db.sqlight 3 which happens to be a file that exists inside of my directory. But if you change this to a PostgreSQL database rather than providing a name here, you would likely specify the host where the database exists, the port on which it's on, and a username and password, much in the same way that you did in Project 1. But that's all configured inside of the settings file. Good question. So that database file now has the models that I've created. So let's go ahead and actually try and create some of these flights by looking at how that happens. And so what Django allows us to do is it gives us access to what's called a shell. So much like Python's interpreter, but that is tied into this Django application and lets us directly modify the database without too much effort. And so to run the shell, again I'm going to run python manage.py followed by the word shell. And now I'm inside of Django's shell, where I can run Python commands. And in particular, I'm going to say from flights.models-- remember I had a Python package called Flights, my app, inside of which was a file called models? I'm going to import Flight. And now what I'm going to do is I'm going to create a new flight. I'm going to say f is going to be equal to a flight whose origin is New York, whose destination is London, and whose duration is 415 minutes, for instance. And so I've now created that flight. And much like in SQL Alchemy where any time you add something or edit something, you need to save that change, likewise in Django you need to do the same thing. And the syntax for doing that is f.save. That is it effectively running that commit operation on a bunch of lines of SQL. And so f.save says actually save this new flight that I've created to the database. And now if I want to query for something in the database, if I just want a query for all flights, I can run Flight.objects.all. It's just a simple query that will get me all of the flights. And that returns to me a query set-- which you can think of as a fancy type of list that has additional features to it-- of all of my flights which right now is a list of just a single flight, this flight object 1. So this flight object 1 in parentheses, isn't very helpful for me to look at if I'm just looking at this in the Python terminal trying to understand it. And so very often what you'll see is that we'd like a better string representation of this flight. I'd like some easier way to look at this flight and understand what it means. And so what we can do is inside of our models file, I can add a underscore underscore STR function or a STR function. And what the STR function does, and this works for any class not just classes in Django, is it defines what the object should look like when it's printed out to a screen. And this will apply whether it's printed out to a terminal or even to an HTML page, as we'll see shortly. So if I want the string representation of the flight, I'm just going to say you should return the ID of the flight followed by the origin of the flight to the destination of the flight. And so I'm just going to return that. And so now if I go back into the shell, go to from Flights.models import flight and go Flight.objects.all, now I see that in the response that's come back to me, I have this Flight 1 New York to London. And I can very clearly see that that flight is there. And if I wanted to access it, I might do F equals Flight.objects.first to get me that first flight. F is now flight from New York to London. I can say F.origin, that's New York. F.destination, that's London. F.id is 1. And so I've now created my flight. If I want to delete that flight later, it's as simple as a F.delete. And that deletes the flight. Questions about anything so far? All right. So now, we'll build on this idea of migrations, build on the idea of being able to change the contents of our database after we've already gone ahead and created some basic tables. Let's say that now rather than just defining the origin and destination as character fields where I type in text for the origin and destination, what would make more sense from a design perspective if I'm going about designing an application managed by an airline? If I want to maintain flights? Rather than a text field for the origin and destination, what might I use instead? Yeah? AUDIENCE: [INAUDIBLE] fields [INAUDIBLE].. SPEAKER 1: Yeah, something like an ID field where I actually have a table of airports, for instance, that maintains all the different airports. And my Flights table now rather than just be the text New York, the string New York as the origin could really be referencing the ID of some table of airports. So certainly that would be a good design decision that we could make if we're going to start to manage additional information about our airports. So let's take a look at that. So I go ahead and exit the shell. We'll go back into this models file. And I'll create a new class. We'll create a new class that's going to represent an airport. And my airport is going to have a code which is just going to be that three character code that represents an airport. And it'll also have a city, that whatever city the airport is in. And that's going to be a char field of max length 64. And if someone tries to access the string representation of an airport, I'm just going to return to them the name of the city and then in parentheses the code of that airport. And so define a new class called airport. And what other change do I now need to make? AUDIENCE: Change Flight. SPEAKER 1: Yeah, I need to change Flight now because origin and destination are no longer character fields. What origin is going to be instead is a models.Foreignkey. And so, it's going to be models.Foreignkey referencing some other class. And the class I'm going to reference is airport, meaning origin is going to be a foreign key. It's going to point to some other airport. And I'm going to add a special on delete property called models.cascade. And so, what Django models allow me to do is determine what happens when I delete an airport, for instance. Because if I have airport JFK International Airport, and I have flights that have an origin of JFK International Airport, what should happen to my data if I were to ever delete the airport at JFK? What should happen to any of the flights that have an origin of JFK? You know, there might be multiple options. And depending upon how your application is designed, you might want things to operate differently. Maybe you want there to be some sort of error where we say, don't allow you to delete the JFK airport if there are flights that have an origin of JFK. Or maybe you want the behavior to be, when I delete the JFK airport, go ahead and just delete all of the flights that have an origin of JFK because those flights can no longer exist. And there are other options as well. But what models.cascade is going to say is that if I delete the airport that has that particular origin, then delete the corresponding flights as well because those flights are no longer valid. And so these are just decisions that you'll have to make based on the desires of your particular application and what you need for that. And I'm using models.cascade in this case. And I'm also going to use a related name argument. And I'm going to call it departures. And what related name is going to allow me to do, which you'll see later, is that if I have an airport and I want to access all of the flights whose origin is that airport, I can use the name departures to be able to access that. And we'll see an example of that later probably. And likewise for destination, that's also going to be a foreign key that points to another airport, also have it cascade upon deletion. And it's related name is going to be arrivals instead. Because if I have an airport and I get its arrivals, that should get me all of the flights whose destination is that airport. Now notice here that I am not saying origin ID or destination ID. I'm just saying the origin is an airport and the destination is an airport. And I'm leaving it up to Django to worry about how actually to name the columns and how to do the associations of having an ID map to something else. All I have to worry about here is the data that I want to deal with, that the origin should be an airport and the destination should be an airport. And it's up to Django to figure out how to make sure those relationships work. So now, I've made those changes. And if I want to apply them to the database, what do I need to do? Migrate them. Great. So I'm going to run python manage.py makemigrations. That's going to detect any changes that I've made to my models. And in this case, it's detected that I've created a new model called airport. And I've altered the destination and the origin fields on my existing flight. And now if I go python manage.py migrate, it's going to apply that migration. And now my database is updated with this new format that I've just created. And so, let's go ahead and go into the shell and experiment with that a little bit. So let's go from Flights.models, let's import Airport and Flight. Let me define a new airport called JFK which is going to be an airport whose code is JFK and whose city is New York City. And let me define a new airport called LHR whose code is LHR and whose city is London, for instance. And now if I want to create a new flight, all I need to say is f is going to be a flight whose origin is-- or actually, let me first save JFK and save LHR because I've just created those. But if I want to create a new flight, I can say I want a new flight whose origin is JFK, whose destination is LHR, and whose duration is 415 minutes. The origin is, in fact, JFK Airport. The destination is London Heathrow Airport. I don't need to specifically specify use the ID column. Django is trying to abstract some of this away so that I don't need to worry about it as much. So I create that new flight. I'm going to go ahead and save that flight. And now I have two airports and a flight saved in my database. And so if I go to f.origin, I notice that f.origin is this airport object. Now I can just say f.origin.code, for instance, to get JFK as the code of the origin of this flight, or f.origin.city to get New York City. And likewise, if I have JFK, and I go to jfk.departures.all what that's going to do is query for all of the departures that are departing from JFK. It's going to give me this query setback that has flight from New York City to London. So questions about the models that we created or the migration that we made or how our data is now going to operate? Let's move on to talking about rendering templates. And so one feature that we talked about just a moment ago was that we might want for our application to be able to not just return text but to actually return an HTML template, much like we saw in Flask. And so just like in Flask where we used an HTML file to say, render this template, we're going to do something very similar in this application as well. Rather than return HTTP response flight, I'm going to return render. And render takes a couple of arguments. The first argument is the request. And the second argument is the name of the HTML template that we want to render. And so let's go ahead and render flights/index.html as the template I want to render. Of course, now I need a flight/index.html file. And by default, those are going to be stored in a directory called templates. So I'll create a new folder, called it Templates. Inside of Templates, I'll create a new folder called Flights. And inside of Flights, I'll go out and create a new file called index.html. And that's just going to be a basic HTML file that is going to be the template that I want rendered when I run my application. So right now, it's just going to say flights. And now if I exit and run the server, when I go to this URL, I now see HTML page that has a big heading called Flights and a title up here that says Flights. And so this is very analogous to what we had in Flask. We're rendering this particular HTML file. And here's the contents of the HTML file. But now, of course, we don't just want our Flights page to say Flights in this big heading. We want it to actually show us the flights that exist inside of our database. So in Flask, how would we have done something like that? AUDIENCE: [INAUDIBLE] SPEAKER 1: Yeah. With Jinja. We would pass information into this template in order to allow it to render. And Django has its own templating system that looks very, very similar to Jinja. And in fact, in many ways, it looks identical. And we'll go ahead and use that now. And so the way Django passes information into the template is through a special context dictionary. And so this context dictionary is just going to be a Python dictionary that will maintain keys and values. Keys as the things we want to plug in to the template, as the names of those things. And the values are what we actually want to plug in. So let's say Flights is going to be something we want our template to know something about. And it should have a value of flight.objects.all. I just want a query for all the flights. Save that inside of Flights. And we're going to ultimately pass that context in to our template as a third argument to it. Of course, now I'm using this variable flight which doesn't exist in this file. So I'm going to need to import it. I'll say, from .models import Flight. In other words, from this current packages models package, our models module, import the flight variable. And so now I can query for all of the flights, save it inside of the context, past the context in to my template. And now in my template, I can create an unordered list. And this syntax is going to look basically identical to Jinja which we saw from Flask. I can say, for flight in flights, create a for loop that's going to loop over all of my flights. And for each one of those, let's have a list item where that list item is going to be just plugging in .flight. And so now, if I refresh the page, what I get is Flights with this unordered list of the single flight that I have. And notice that I didn't specifically say, use the ID of the flight and the origin and the destination. I just said, in curly braces, plug in the flight here. So why is it rendering nicely with the ID and the origin and the destination with the word to in between? AUDIENCE: [INAUDIBLE] string. SPEAKER 1: Yes. Exactly, from the string function, the underscore underscore STR function that defines what should the object look like when you just paste it into the terminal or when you just render it on an HTML page. It will use that STR function to determine how that flight actually shows up on the page. So now we've recreated a lot of that Flask functionality, of taking context information, passing it into a template and then using it. Questions about anything so far? Yeah? AUDIENCE: Why is the [INAUDIBLE]? SPEAKER 1: Oh, good question. So the question was, why is this directory structure inside of templates a separate directory called Flights inside of which is index.html as opposed to just having index.html there. You could do it where you just had index.html inside of Flights. And you change the template name and views.py to just be flights.html. It's generally good practice to what's called namespace templates by putting them in a directory with the name corresponding to the app. Because if in larger Django projects, you had multiple different apps, each of which had an index.html file, that would potentially create ambiguity, where if you tried to render index.html Django wouldn't know, do you mean index.html from app number one over here or app number two over there. And so what namespacing the templates does is it means that I can explicitly say, flight/index.html is the index.html template of the Flights app. And that way, even if there are other apps that have an index.html, there's no ambiguity as to which one I actually mean. AUDIENCE: So this template's folder is already specific to the Flights app? SPEAKER 1: Correct. So this template's folder is specific to the Fights app. But when Django is rendering a response, it's not looking only in this templates folder. It could look in templates all across the entire project. So that ambiguity is still possible. Good question, and certainly something that's not very intuitive at first and it seems sort of redundant. Other questions about the structure of things so far? So what we'll take a look at now is what we need to happen if we wanted to allow ourselves to add or edit data inside of our database. And so right now, we've been doing this all via the command line, that I logged into the shell. I then use that shell to create new flights, create new airports. What I'd really like to do is not have to write Python code to be able to do that, but to be able to go to part of my website where I can click add a new flight from this location to that location and be able to save that information or update existing flights. And if you were doing this in Flask, it would be a non-trivial task. You'd have to build out a page to list all the flights, build out a page to list all of the airports, figure out a way to make sure that when you add a new flight you can choose from one of the valid airports. And this would be quite a fair bit of code. But what Django does, because it's designed for applications that use a lot of different types of data and they know-- the designers of Django-- knew it would be quite tedious if anytime you wanted to have an application that let us add or modify information, you had to build that yourself, Django comes with a built in app called Admin which makes it very easy to add and modify existing data. And so I'll show you what that admin interface looks like right now. And this is perhaps one of the most powerful features of Django, especially when it comes to dealing and manipulating with data. So I'm going to go into admin.py now, this file inside of my Flights app. And the first thing I'm going to do is say from .models import Airport and Flight because I want my admin interface to know something about the airport and flight. And what I'm going to do is say, admin.site.register.airport and admin.site.register.flight. So the Admin app is an app that's built into Django that makes it very easy to update existing models, to modify the data inside of models. And what I'm doing here is registering these models with my admin site, saying that I want my admin site to be able to manipulate Airports and Flights. In order to access the Admin site, you're going to need to log into the Admin site. And see, here's another case where in a Flask application, if you wanted a way to log into something, you'd have to build this out for yourself, design a users table, figure out how to let people be authenticated. But in Django, this is all built for you out of the box. And so what I'm going to do now is just create a first user account inside of my web application. And there's a special command for that, python manage.py createsuperuser which is going to create a super user account, an account that has access to everything in my database, so to speak. And so I'm going to create that. It asks me to type in a user name. I'll do brian. Email address, I'll type in email address. Password is just the password I want to use. I'll type in a password. Type it in twice. And now it's created that user for me. And so that's been saved into a users table that I didn't have to create. It was built into Django, built into this project without me needing to do anything. So now let's take a look at the Admin site itself. So if you were paying very careful attention to the urls.py file inside of the project, you'll notice that what we did is we first created a path that just took us to our application's URLs. But there was also this admin route that was built in to the URL patterns for us, that came with the application, that I didn't put there. And so let's go there now and see what it is. So I'm going to say python manage.py run server. I'll go ahead and go to this URL. And I'm going to go to /admin, because that was the URL that was given to me. And when I go there, I'm taken to this login page that I didn't write. It's an app built into Django called Django's Admin App. I'll type in that username that I just created, that password. And what I'm taken to now is a user interface that lets me add and manipulate the data inside of my database, that I can click on airports for instance, and see here my two airports, London and New York. I can click on London if I want to change the code for London airport, I can change that here, change the city. But let's just say now that I want to create a new airport, for instance. So let's say I add a new airport. I want the code to be PVG and the city to be Shanghai. And I save that. And now I've created a new airport inside my database. And this is all stuff I'd have to build for myself in Flask but you don't need to in Django because it all comes for you inside of Django's admin interface. You just click Add Airport. You type in the city and code that you want. You save it. And then that airport is added. Likewise, I can do the same thing with individual flights. I can click on Flights. I see that I have this existing flight from New York to London. If I want to create a new flight, I just click Add Flight here. And the whole process of the origin needs to be an airport is auto populated for me. I can just select, OK, this is a flight from Shanghai to Paris. And the duration of that flight is going to be 760 minutes. And I can save that and now I've created a second flight inside of my admin interface as well. And so this just makes it very easy for me to manipulate data using a web user interface that's created for me, without me needing to go through the effort of writing that for myself. Questions about this interface? So this comes out of the box with all Django applications. And all you need to do to be able to use it was for Airports and Flights to show up here. I just needed to register these models with the admin site on that admin.py file just a moment ago. So yes? Question? AUDIENCE: So this is on the project level? Or do you have it on that [? app ?] [? level? ?] SPEAKER 1: So the Admin interface you're looking at here, this is part of an Admin app. So it is an app within my project. But it's not an app that I wrote. It's an app written, that's built into Django. Yeah. AUDIENCE: But it's not an each individual-- Flights1 and I could have another app that did something else [INAUDIBLE]?? SPEAKER 1: Oh, good question. So what would happen if I had multiple different apps with multiple different models? Where would these show up? They would all show up on this Admin site. What you'll notice here is that this Flights heading, that corresponds with the name of the app, that these models Airports and Flights, these are models inside of my Flights app. And likewise, I have this authentication and authorization app-- which I didn't create, it's just built into Django-- that has users already and groups of users if you want a group users into different permission levels, which is a common thing that you might want to do in a web application. That's built into the authentication authorization app that comes with Django. And so those models are already registered without me needing to do anything. And so if I added yet another app and had models that I wanted to interact with, it would show up as a separate heading underneath it with additional models below that. AUDIENCE: Is there ever any error checking of what you do? Like if I could enter a flight that went from Paris to Paris, which I could, is there a way to stop me from doing this [INAUDIBLE]?? SPEAKER 1: Great question. Is there a way to stop us from doing something stupid like adding a flight from Paris to Paris, which right now we could do? And the answer is yes. So this is Django's default Admin interface. But Django lets you customize this Admin interface in almost any way you want adding your own logic and your own code to it. It's a little bit beyond what will have time to go into here. But certainly it's something that you could do. You could modify this Admin interface to add constraints, make it behave the way that you want it to behave. And certainly that's a possibility. So this is often just a great place to jump start the process of allowing yourself to modify data. And this Admin interface isn't meant to be used by all users of your website. It's generally meant for content managers of your website, the people that are managing the information that other people are seeing, where you will likely use the Admin interface to populate your models, add the data that you want. And the users will be looking at a page sort of like this that is rendering information that comes from the data that's been populated into your site already. Let's take a look at adding some more routes to this application. Maybe I now want the ability to have arguments in my URLs, like an integer saying if I go to myurl/1, that should take me to information about flight number 1. And likewise /2 should take me information about flight number 2. So to do that, I'll need to add a new URL. I'll go into my urls.py file. And I'm going to say whenever someone goes to a particular URL-- and I could just type a URL are here-- but I want someone to be able to type in any number, any integer. And so the syntax for this looks a lot like Flask. If they type an integer, we'll call it flight id, then the function I want to run is views.flight. Of course, view.flight doesn't exist yet. So let me go ahead and create it. I go into views.py, add a flight function. And inside of the flight function, I'm going to say let's try and say flight equals flight.objects.get pk equals-- and then I would need the flight id. So actually, sorry, the flight id is going to be provided as a second argument to the flight function. Because it was a parameter in the URL, it gets passed into the function when it gets called. And so the flight, I'm going to get the flight with PL, Primary Key, flight id. I could also say, id equals flight id. They're almost the same thing. There's some slight, subtle differences because your primary key might not be called id for instance or you might have a more complicated primary key. But here I'm saying, get the flight number whose number is flight id. And what could go wrong here? Well, if the flight doesn't exist, if I tried to access flight number four but I don't have a flight number four, for instance, or flight number 28, then it's going to throw an exception. So I'm going to say, except Flight.doesnotexist. This is a special exception built into all of my Django models that is thrown whenever I try and access a flight that doesn't exist. So if the flight doesn't exist, then what I want to do is raise some sort of error. There should be some sort of error. And Django comes in with a built in http 404 for 404 not found error. Or I can just say, flight does not exist here. And if the flight does exist, then let me just go ahead and put it inside of a context object. The flight is going to be the flight. And I'm going to render a response. I'm going to render the flight.html page, which I haven't created yet but I will in a moment, passing in that context. And so, what I'll do here is I need to import this http 404 function. And I'll import it up top. And so now I have this http 404 function. And now what I need to do is actually create this flight.html file, the file that is going to show me what's happening on any individual flight. And so I'll go ahead and inside my Flights directory, add a new file called flight.html. It's going to look a lot like index.html so I'll just go ahead and copy it for now. But the fact that I'm copying this code should suggest to you that there may be design improvements to be made that we'll take a look at it a moment. And this is going to be flight, flight id. And let's go ahead and include some information about its-- origin is going to be flight.origin. And its destination is going to be flight.destination. So now, if I go back here, this default route shows me all of my flights. If I go to /1, now it shows me flight 1 whose origin is New York City, whose destination is London. And likewise if I go to flight/2, that shows me Shanghai to Paris. If I go to /5, for instance, a flight that doesn't exist, then I get page not found, the flight doesn't exist. This is Django's default 404 error page when I have the debug settings to true. You can customize the error pages, of course, if you'd like to as well. But questions about anything we did so far? So the three steps that we needed to do to make this happen were add the URL. So when the user goes to this URL, what should happen when they type in a number? Send them to the views.flight function. Then in the flight function, we said try to get the flight. If it doesn't exist, raise a 404 error. And then render this template passing in the context of the new flight that we just wanted to display information about. And inside of our html page, we were then able to, in the body of the html, display information about that particular flight. Questions about any of that? We'll take a short break. And when we come back, we'll take a look at some additional features of Django and some more powerful things you can do with its model and database structure. Welcome back, everyone. So let's pick up where we left off. We were working on this airline application and working on building a system for people to look at-- an airline could use to see a list of flights and look at individual flight pages. And right now, our airline program has two main routes. We have the default route which just displays a list of flights. And then if we go to slash something, it displays details for that particular flight. What we might like now is the ability to link between these two such that the individual flight page can link back to the original page and the original page can link back to the individual flight page. And a common paradigm for doing this in Django is to name our URLs. So right now, each one of our URL paths has two things. It has the route you would go to and the function that would be called when you were to go to that route. But what we're going to do now is add a third thing to each of these paths, which is a name. I going to call the-- I'm going to name the default route just index. And I'm going to name the individual flight route, flight. And the reason I'm giving it a name is such that when I link to a different route at some other page, I would like to be able to refer to that route by name rather than by route. I certainly could link to just, if you click here, you go to flash. /1 or /2. But Django is designed such that what the user sees and the code are as separated as possible such that if you wanted to change the route that the user sees, you wouldn't need to go into the HTML template and actually change any code in order to make that possible. You could change the route and leave the name in the same. And then it would work just fine. So now that I've named these routes, how do I actually use them? Well, inside of Flight.html here, let's at the bottom offer the user a way to get back to where they were before. So let's add an a href which is going to be equal to something, we don't know quite yet. And it's going to be back to full listing, taking me back to the full listing of all the flights. And the link that I want to link to in Django syntax is to say I want to link to a URL, and then in quotation marks-- single quotation marks because I've already used double quotation marks here-- I'm going to give the name of the URL I want to link to. So you URL index to say, when you click here, take me to the URL for the index page. So now if I am on this page on the flight 1 page, I had this back to full listing click button, which takes me back to the original Flights page. Likewise, on the original Flights page, on index.html, instead of just printing out the flight, I'd like to link to that specific flight page. And to do that, I wanted to say, URL flight, because that was the name of the flight URL, but what's missing here? Why is this not going to work? AUDIENCE: You need the ID. SPEAKER 1: Exactly. I need the ID because the flight route isn't just a route that stays the same anytime I refer to it by name. It has this variable in it, this flight ID variable, that I need to somehow provide to Django. And in Django the way to do that is just include the ID right after the name of the URL. So URL to flight, and the argument to pass into it is the flight's ID such that now if I refresh this page flight 1 and flight 2 are links. If I click on flight 1, that takes me to the detail page for flight 1. I can go back, click on flight 2, now here is the detail page for flight 2. So that was linking between different pages. Questions about that? One other thing that I alluded to before that I'll talk about very briefly is that inside of this HTML, this flight.html template and my index.html template, there is a lot of repetitive code. This header is basically the same. I have body tags in both of them. And so just like in Flask, we could use template inheritance to say I want some base template from which other templates are derived from. I can do the same thing in Django as well. And so the way I'll do that is by creating a new template, which we'll call base.html. And base.html is going to contain all of this original code except the body I'm just going to call block body. This looks very, very similar to what Jinja syntax for the same thing is. And then end block. And I'll make the title-- I'll actually make the title a block title as well. And then end block there. And so now inside of index.html rather than have all of this, I can just say, first of all, I want to extend the flight slash base.html template inside of block title. Let me go ahead and say Flights. And that's the end of that block. And now inside of the body block, I have all of this content. End block there. And I don't need to indent this as much. And so now this is my index.html page. And now my flight.html will likewise extend the flight/base.html template as the block title. I can add in a title. Before the title was just Flights. I'll go ahead and be a little bit nicer about it and call it flight.id so the title of the page includes the ID of the flight. And then inside of the block body, I'll go ahead and include this content that was previously in the body. And I'll indent that just for clarity. And so now what I have is the same exact thing except I've divided things up a little bit into templates such that my base template has all the HTML code that is common to both of my files. And then index.html and flight.html are able to modify those blocks in much the same way as we did in Flask as well. And the result is that this page looks basically the same except I now have flight 2 up here in the title. And if I go back, this page is basically the same as well. This was just a nice organizational measure to do that didn't add a whole lot of additional features to the application itself. But now-- go ahead. AUDIENCE: [INAUDIBLE] still Jinja? SPEAKER 1: The templating language is no longer Jinja. It is Django's templating language. Django just happens to have its own templating language that in many ways resembles Jinja. And the syntax for most things is almost identical. And the documentation for Django's templating language is very good and it's all available online. We won't have time to go through all the features, but there are all sorts of filters you can apply to things to make things look precisely the way that you want them to. And it can be quite powerful in that respect. So now what I want to do is think back to our original airline application where, in addition to representing flights and potential airports, we also wanted a way to represent passengers and have passengers on individual flights. And the way that we did this back in Flask was to create a table called Passengers where our Passenger table, if you recall, had a Flight ID column that referenced a specific flight such that every passenger was associated with one flight. And that was how we were able to relate passengers to flights. What is a possible design flaw in that set up for flights and passengers whereby in our Passengers table, we had a Flight ID column that linked to one particular flight? AUDIENCE: You could have multiple flights associated with a passenger. SPEAKER 1: Exactly. We could have multiple flights associated with the same passenger. One passenger might want to be on multiple flights or might be registered for multiple flights. And it would be nice if we could say for Alice here, what are all the flights that Alice is registered for, and to be able to see them. And that wouldn't be quite as easy if we just had a single table of passengers where each passenger can only be associated with one single flight. So how might we get around this problem? How might we structure tables in order to allow for this what's called a many-to-many relationship, where one passenger might be associated with many different flights and one flight might be associated with many different passengers? Any thoughts on how to construct a table for that? AUDIENCE: [INAUDIBLE] table that has just like two ID [INAUDIBLE].. SPEAKER 1: Yeah, exactly. So one conventional way to solve this is with an in-between table that has two columns where we have one column for a passenger ID and one column for a flight ID. So we have one separate table for flights, one separate table for passengers, and this in-between table that maps passengers and flights together. We could have as many of these relationships as we want such that a flight can have multiple passengers and a passenger can be on multiple different flights. So that's what we'd like to do. What Django's actually going to allow us to do is do that without needing to worry about explicitly creating the in-between table and just describing the relationship that we want, describing this many-to-many relationship that we want to define our models. So I'm going to go ahead and open up models.py. We have this existing Airport class and this existing Flight class. But now what I'm going to do is add a new class called Passenger which is going to be a model. And a passenger is going to have, let's say, a first name which will be a char field of max length of 64 again and a last name which will be also a char field of max length 64. And also, a passenger, if we just think about this in terms of what a passenger is associated with, a passenger is associated with flights. They have some flights that they are on. And this is not a single foreign key, not a single flight we're pointing to. This is a many-to-many field that one passenger might be associated with many flights, one flight might be associated with many passengers. So it's a many-to-many field that corresponds to what type of data? Well, it corresponds to a flight. I'm going to add blank equals true, just to allow for the possibility that maybe a passenger is not on any flights. So their flights field is blank. That's certainly a possibility. And I'll also add this related name, just like we had before, such that-- and I'll call the related name Passengers-- such that if I have a flight and I want to get at its passengers, I can use this related named passengers to be able to get at that as well. And we'll how that works in a moment. And finally, I'll go out and add a STR function to the passenger such that if I ever wanted to reference a passenger, it'll just be self.first self.last. We print out their full name when we want to represent a passenger. So this is our Passenger class. And now I would like to just add this to the database. Notice I'm going to-- I want-- the solution to this problem is this in-between table that maps passengers and flights together. But I haven't written a class for this in-between table. I just said I want a many-to-many field. But watch what will happen is that if I now say python manage.py makemigrations, it's now detected that I've created this new model Passenger. It's added a new migration file for me. And if I now say python manage.py, let's take a look at what the actual SQL contents of this migration is. So I'll do a sqlmigrate flights 003. What's actually happening here, if we take a look at it, is even though we only added one model what Django is going to do when I apply this migration is create two tables. It's creating one table here called Flights_passenger, which is going to manage in the Flights app, all of the passengers. And this has an ID field, a first name field, a last name field just as before. And we're also, Django's going to create another table called, oddly enough, Flights Passenger Flights, meaning in the Flights app, mapping individual passengers to individual flights which has an ID field, a passenger ID field, and this other flight ID field such that these tables are created for me. I didn't need to worry about creating the in-between table, defining the names of these columns. I tell Django what my data looks like, that my passengers are associated with multiple flights. And Django figures out what SQL needs to run in order to create the models in such a way that I can use them. And so this is all designed to make it easier on you as the person writing the application to not need to worry about specifically what the tables look like and what the columns look like. But just to be able to deal with the data that you actually want. And so I'll go ahead and migrate this into the database. That's going to apply the migration, actually create all of these new tables. And so now, if I go into the shell and say, from flights.models, let's import Flight and let's import Passenger. Let me go ahead and get the first flight, flight.objects.get PK equals 1 to get the first flight, that's our flight from New York to London. And let me create a new passenger now. p is going to be equal to a passenger whose first name is Alice and whose last name-- I'll do another A name-- Adams. So now I have this passenger. I'm going to save this new passenger. And my passenger has a flights attribute to it. And so I have p.flights. And so if I want to add a new flight to this passenger, I can say p.flights.add f. And now, if I do p.flights.all, for instance, to query for all this passenger's flights, I see that this passenger is now on this flight from New York to London. And this is a query set that could be multiple flights. So this passenger can now be on multiple flights potentially. And likewise, if I have f, I can do f.passengers.all to get all of the passengers that are on this particular flight, which might be multiple as well. So I've been able to encode this many-to-many relationship without needing to worry about the specifics of what goes into each one of these individual tables. Questions about that so far? So what I might want to do now is offer some way to be able to see passengers inside the detail view of my application. So inside of views.py, I might want it such that in addition to displaying the flight, I also display the passengers. So flight.passengers.all in order to query for all the passengers. And that gets passed into my flight.html file. And so inside of flight.html now, I might also want to say, let's add a heading called Passengers and another unordered list where for each one of my passengers in my list of passengers, I display the passenger's name. And Django has a special syntax, this is slightly different from Jinja, called empty for what to do if the for loop never ran, if nothing ever happened. And here we'll just say no passengers. And so I'm looping over all of my passengers, just printing out the passenger name such that now if I click on one of these individual links, after I start up the server, I get here's the flight details and here's my list of passengers. I have one passenger on this flight. I go back to the full listing, click on this flight that has no passengers, I see that this flight has no passengers on it. Questions about any of that? One other thing we can do very quickly just to show you what this is like is if I go into admin.py and say, you know what, admin.site.registser.passenger, I also want to be able to modify passengers on my Admin site. I'll import that as well. And I go to this admin link and I log in. Now I see this additional passengers option where I could likewise create if I wanted to add a new passenger called Bob Baker or whatever, I can add this new passenger. And I can also select what flights I want Bob Baker to be on. I can say, I want to be on flight number 2 and flight number 1. I can select multiple, but maybe just flight number 2. I can save that. And now Bob is on flight number 2. And if I refresh this flight number 2 page, now Bob is on that flight. So I'm able to use the admin interface to be able to manipulate this data however I like. And I didn't need to design that UI for myself. Questions about any of that? As one last edition for now to this application, let's see if we can add some way for people to be able to register for flights via the web UI, to be able to submit a form that allows them to register for a new flight. So what might that look like? Well, let's go ahead and go into urls.py of my Flights app and let's add a new route, the route that will happen when someone tries to book a flight, for instance. So the path for that will be int flight_id/book. In other words, if someone is submitting a request to my URL, /2/book, that will mean I want to book a ticket on flight number 2. So that's going to link to the function views.book, which doesn't exist yet. But I'll create it in just a moment. And I'll go ahead and name this book just for good measure. So now, let's go into views.py and create this book function, this function that I want to happen when I try and book a flight. It takes in the request as its first argument. It also takes in a flight ID. And so what do I need to do inside this book function? Well, several things could go wrong as I try and book the flight. So let me try to do some things and figure out what could happen. I'll say passenger_id is equal to int request.POST passenger. And so I'm going to assume for now, and we'll see how to implement this later, that when someone is booking a flight, they're going to submit a POST request. And one of the arguments in that POST request, the data that passed gets passed in, is named passenger. I'm going to convert that to an integer and store that as passenger_id. Once I have that, now I'm going to say, well, passenger better be passenger.objects.get PK equals passenger_id. I want to get the passenger that has that ID. And in order to make that work, I need to make sure that I import Passenger up here as well. And I'm also going to want to get my flight. So flight equals Flight.objects.get PK equal flight_id. So now I tried to get the passenger ID, I tried to get the passenger, I tried to get the flight. And now I'll handle the possible exceptions that could be thrown, the things that could go wrong. So what could go wrong here? Any thoughts? AUDIENCE: There's no passenger with that passenger ID. SPEAKER 1: There is no passenger with that passenger ID. Certainly. So passenger.doesnotexist. And in that case, I'll return-- in this case, I'll render a special HTML form, error.html which I'll create which will just be my default way of displaying error messages, for instance. And to my error.html, I'll pass in the context of message no passenger. And likewise, I'll go ahead and copy this and say, if the exception was flight.doesnotexist, there was no flight, then my error message will be no flight. And one other thing that could go wrong in this case is a key error. Any idea why I might get a key error by running this code? So if hypothetically someone submits a POST request but doesn't include the passenger data in it, for instance. Or they submit a get request so I don't have the POST request, then I'll get a key error in this case because there was no passenger data to extract. And so here I'll go ahead and also return the error.html with the message no selection. You didn't select a passenger, for instance. So these are things that could possibly go wrong. There are other things as well. But for now, that will work for our purposes. And what I'll do now is say, in order to book the flight, let's take the passenger, let's go to their flights because every passenger has flights that's associated with that passenger, and add that flight. And that's all the code I need in order to add this new passenger to this flight. I take the passenger, I take the flights that are associated with them, and I add this new flight that I've just created. And what I likely want to do now is redirect them somewhere, redirect the user to some other page. And so to do that in Django, the syntax looks like this-- Httpresponsredirect. And where do I want to redirect them to? Well, I probably want to redirect them to this particular flight page. So the syntax for getting the URL if you know the name of the route you want to go to is called reverse in Django. Because I want to go in the reverse direction of going from the name of the URL to the actual URL. So it's reverse followed by flight, which is the name of the URL that I want to go to. This route here has a name of flight. And that's what I want to go to. And it also takes additional arguments. So my argument is going to be a tupple of what arguments get passed into the flight. And in this case, the argument is the flight ID. And so I'll go ahead and pass that in as well. And I will also need to up here import Httpresponseredirect and also from django.urls import reverse, just to give me access to that reverse function. But the long story short of what this is doing is I'm going to first try and extract the passenger's ID, get at the passenger itself and their flight, add the flight to the passenger's collection of flights that they're associated with, and then redirect the user back to the flight page. And so now what I need to do is actually create some kind of form that is going to lead to this book route. So inside of flight.html, I'll go ahead and add that functionality to here. So, underneath this list of passengers, I'll go ahead and add a horizontal row. I'll add a new heading that is like Add a Passenger, for instance. And so what I want to do now is add some sort of form here where I might say, form action is where I want the form to go to. So any thoughts on what belongs in the action for this form? AUDIENCE: URL and then the name of [INAUDIBLE].. SPEAKER 1: Yep. So the name of that function was book, as the name of this particular path. And I need to pass in the individual flight ID for it. So it'll be book followed by flight.id. And so inside this form, and the form's method that I'm submitting it via, is going to be a POST method. What am I going to need? Well, what I probably want to do is offer some way for me to select from the people that are not on this flight. So all the people that are not currently passengers on this flight, I want there to be some sort of select dropdown where I can select this is the passenger I want to add to the flight, and let me add them. So before I get there, what I probably want to do is up here in this flight route, when I render this template also pass in not just passengers but non_passengers as well, people that are not passengers on this flight. And the syntax for doing that, which we'll take a look at, is going to be passenger.objects.exclude flights equals flight.all. And so the reason why this works, passengers.objects gets me all the passenger objects. And then I can additionally filter this information. So if I did .filter, that would mean get me passenger objects that have a particular property. If I did passenger.objects.filter first equals Alice, that would return to me all of the passengers whose name is Alice, which in this case is just one. But likewise, if I say passenger.objects.exclude, that's the opposite, saying get rid of things that don't have this particular property. In other words, get rid of all the passengers who have this particular flight already in their lists of flights. Because they're already on the flight. I don't need to allow them to be added to this flight again. And so I'm saying, exclude from the passenger objects those people and call that result non_passengers. Questions so far? We'll see how this all fits together in just a moment. So now, for Add Passenger, one thing that I might immediately want to do is say if non_passengers, and wrap this whole thing inside of an if block. Because if there aren't any people that have yet to register for this flight, then there is no reason to display a form to allow them to sign up for the flight because there's nobody else to add. And so if there really is no one, then let me just display a div that says no passengers to add. There's nobody left out. But if there are people that could be on this flight, then what I want to do is add a select dropdown whose name is going to be Passenger. I'm going to have a dropdown field called Passenger where I select the passenger that I want. Why is it called Passenger? Well, it's because in views.py, inside of my book function, when I tried to extract the data from the POST request, I'm looking for something whose name is passenger. I want to get that data out of the POST request. And so this word passenger here needs to correspond with the name of the select dropdown or whatever input field of the form corresponds to where the user is actually selecting their data. Now I'm going to loop for passenger in non_passengers, so for each one of those people that are not currently on the flight, well, I want to give them the option of being selected. So the value of this option is going to be this passengers.id. Why is it their ID? Well, it's because when I extract this information, I'm saving it as passenger.id and I'm trying to extract that passenger by their ID. That is the data I'm using in order to extract the passenger. So that's going to be the value of this option. And what should actually be displayed in the option, well, let me just print out the passenger's name. By plugging in the passenger there, we'll use the STR function associated with that passenger that will just be first name, last name. And that way, my dropdown will be populated with their name. Then finally at the end, we'll say input type equals submit. And value, what displays on the Submit button, will just be Book Flight. And that will be my input route. And so now assuming I haven't done anything wrong, if I refresh this page now, I see flight 2, passenger is currently Bob. And now I see this Add a Passenger list where I have a dropdown of just Alice, only the people that are not currently on flight number 2. And I can now book the flight. I go back to the full listing, go to number 1. Alice is currently a passenger. And I can add Bob to the flight. And so, what I would like now is if I clicked on Book Flight for that to then trigger the book route and for that to cause Bob to be added to flight number 1. But in Django, there's a little bit of nuance. When I click Book Flight, that doesn't actually happen. I get this 403, forbidden error, CSRF verification failed. And so CSRF stands for Cross Site Request Forgery. It's a potential type of attack, of a security vulnerability in forms whereby someone might be able to forge where the form was coming from. And Django is built in to try and protect against these types of attacks. We'll talk more about this type of security when we get to the security lecture later. But in particular, anytime you're dealing with a form in Django, you're going to need to add a little bit of extra syntax. You're just going to need to add in these curly brace parentheses, csrf_token. This is just going to be a special value that gets entered here such that when I submit the form, this token is submitted along with it. And Django's server can verify that it is, in fact, my web application that's submitting this request. It's just an added layer of security that isn't present in Flask but that's useful in Django in order to make sure our forms are even more secure. No need to worry about the specifics there. We'll certainly talk about it later. But know that for forms, you do need to include that token there as well. So now, if I go to flight 1, I see the flight's origin and destination, passenger is this person. If I try and add Bob to it by going to Bob and clicking Book Flight, now I was taken back to the original page. Bob is now on my list of passengers. And under Add a Passenger, it says I have no passengers to add because there is no longer any passenger that is not on this flight. Questions about any of that so far? So that was us being able to create forms, being able to handle get and POST requests as well, and using this book function to be able to update our many-to-many relationship between passengers and flights. So with this example, if you look at the source code for the example online, you'll see that I do a couple other things with this example, in particular, adding static files like CSS files and customizing the admin interface a little bit. We'll probably talk a little more about that next week. But for now, this will give you the basic idea of the Django application. But one other thing that I wanted to talk about with regards to Django's features is the log in and authentication system that comes built into Django. So you probably remember that when we were looking at Django's admin interface, we went to slash admin. In addition to seeing these models that we wrote, this Airports, Flights, and Passengers model and being able add and manipulate that, we also have these users, for instance, that are part of this authentication authorization app, this app that I didn't create that comes built into Django. This app is designed to make it easy for you as the web application developer to handle things like user log in and registration and making sure that users stay logged in and making sure users can log out. It comes built in with all of that functionality because so many applications need it and use it. And so unlike in Project 1, for instance, that Book application where people had to log in and rate books where you needed to build out that user model for yourself, the log in and log out systems, you had to write all that for yourself. Django comes out with most of this out of the box. And so what I'll do now is show you an example of us using Django's authentication model to handle things like log in and log out such that when you need to write an application in Django, as you will for Project 3, you'll be able to take advantage of this and not need to go through nearly as much effort as you did in Project 1 to be able to design log in and log out functionality for an application because it is so common. So I'm going to go now to a different application. I'm going to go into the authentication application. And we'll go in and open this up. And this is just another Django project inside of which is a single app that I created called Users. And we'll take a look at the URLs that are present first inside of this application. So URLs that I have inside my Users application are a default route that just takes me to the index page and then I have a special log in route. If I go to /login, that is going to take me to the login view. Log ou it's going to take me to the log out view. And let me go ahead and show you what's actually happening inside of views.py, inside of this application. So up at the top, I import a whole bunch of interesting and useful features from Django's default packages. But in particular, line one is of note. From django.contrib.auth, that is the package that corresponds to Django's authentication library. So this is what contains-- the user model is contained inside of django.contrib.auth.models, and these interesting functions. Authenticate, log in, and log out our functions written for us by Django that we can now use. So what's happening inside of index, this function? This is just the default route when someone goes to my Django application. If not, request.user.isauthenticated. This is a feature that comes with Django. This request.user.isauthenticated evaluates to either true or false. True if the user has been authenticated, they've logged in. False, otherwise. So if the user is authenticated, then I'm going to go ahead and return renderinglogin.html, meaning they have now logged in-- or sorry. If the user is not authenticated, in other words, the user has not yet logged in, then I want to display the login form for the user. So I'm going to render the login.html page. And I'm going to say, pass in message none because maybe my login page gets passed in a message if someone types in their password incorrectly, for instance. And we'll take a look at what login.html looks like in just a moment. But otherwise, if this wasn't the case, if the user is, in fact, authenticated, they have logged in, then let me go ahead and just get the user by using request.user, saving that inside the context, and returning the user.html template, passing in that context as well. And so all of these things of request.user and request.user.isauthenticated, these are things that come built in for me in Django that I don't need to worry about writing. I can just use this out of the box. Questions about the index route so far? Let's take a look at log in and log out. So log in is going to be a form submission. Someone is going to type in a user name and password. They're going to click Log in. And that is going to trigger, ideally, them being logged in. So let me first get their username by extracting the user name from their POST request. I might want to put this in a try except to say if there was no user name key, that's going to cause some kind of error, and I might want to handle that. But for now, I'm just assuming their username and password will be in there. Next to password, I say request.POST extract the password. And now in order to authenticate the user, it's as simple as calling this authenticate function, this function that is given to me by Django where I pass in three things. I first pass in the request. I pass in their username. And I pass in their password. And it's going to return for me the user. Or if it failed for some reason, it's going to return none, no user, to me if there was no successful authentication. So if the user is not none, in other words, if a user came back from this request, then that means the authentication was successful. Let me actually log them in. To log them in, I just call Django's log in function, which is given to me, again. It's built into Django, passing in again, the request and the user to log in. And then I'm going to redirect the user back to the index page, back to that original default route of the page to show them their user page up there. Else, in other words, if user is none, in other words the authenticate function failed, there wasn't a successful authentication, then let me render the login page again, but this time pass in a message that says, invalid credentials. It says invalid credentials as the context that I'm passing into the login page. Questions about that log in route? Yeah? AUDIENCE: Why does, for example, the login method, why does it need the request? SPEAKER 1: So the request object-- so the question was, why do we pass the request object when we are trying to log someone in. So the request object in Django contains a lot of information. It contains information about the current session cookies. It contains information about who is currently logged in, for instance, if anyone is currently logged in. And so in order to be able to modify things like request.user, for instance, you might think that login needs access to that request object in order to make sure that log in can be successful. And this request object is very frequently passed around because a lot of Django functions need access to that request to work successfully. So render, for instance, also requires us to pass in that request as well. It's a common paradigm in Django to require that request in several of the functions. So at the end of that, we render invalid credentials if they typed in their user name wrong. And on the user page presumably once they've logged in they'll have the ability to log out. And what does log out look like? AUDIENCE: I have a question. SPEAKER 1: Yeah? AUDIENCE: Is there a session object? SPEAKER 1: Is there a session object? Yes, there is a-- so the question is, is there a session object. There is. Django does have support for sessions. We're probably not going have a chance to talk about it here. But if you look at the settings.py page for this entire application, you'll notice that one of the default installed applications that comes with Django is this Sessions application that's explicitly designed for helping to manage sessions. And if you go to Django's documentation, you can find pretty clear instructions for how to use these and how to take advantage of Sessions. But just like Flask does have sessions, you can use sessions in Django as well. But unlike Flask, where we needed to explicitly set like session square bracket user ID to say this user is logged in, and we had to manually manipulate the session, in large part in Django you don't need to worry about that. Because Django will take care of that for you via this log in and log out function that's given to us by the Authentication app. So I never needed to explicitly put the user ID inside of a session variable, for instance, in order to make this work. Certainly if you wanted to store other things inside of the session, you could. But for log in and authentication, that's not strictly necessary. AUDIENCE: Does it keep the request scope across requests? SPEAKER 1: Good question. So does it keep the request scope across requests? Each request is a different request because it's a different HTTP request to a different page. But if the user is logged in, that is saved in the session much in the same way that Flask does it such that if I log in and refresh the page or close the page and then reload it, the request will still have the user associated with it. It'll still be logged in. And using the Authentication app, you can modify and configure it such that there is like a maximum expiration time, after a certain point the user is automatically logged out, for instance. Django gives you a bunch of these options for how to configure the users and sessions the way you want to. Yeah? AUDIENCE: On the httpresponseredirect, why did you use that rathering than just rendering [INAUDIBLE]?? SPEAKER 1: Oh, good question. So the question is, why am I using responseredirect rather than just rendering the index page directly. The answer is, I certainly could have just rendered it. But if, in fact, the index page were more complicated, if there was more logic in rendering the index page where for instance for my flight's index page, in order to render it, it wasn't as simple as just rendering index.html. I had to first query for all the flights, put that inside of the context, and then render it. So to allow for the possibility that the index page might get more complicated in the future, I just want a redirect to index such that the index function can take care of any additional complexities of querying the database or performing computations that it might need to do before I actually render that template. Yeah? AUDIENCE: What is the reverse here? SPEAKER 1: Oh, good question. Question is, what is the reverse here. So when I redirect somewhere, I need to redirect to a URL but Django generally doesn't like us putting URLs inside of views.py for instance. Because the idea is that we want to keep things as separate as possible. Django separates its projects into distinct applications. Within applications, it separates URLs from the actual view functions. And so it's generally good Django design practice to in the URLs associate the route itself with the name. And then if we want to extract the route from having a name, that's where the reverse function comes in. It takes index, this name, and gives me back the route that I would need in order to get there. And finally, log out, this log out view, is just going to call the log out function, which again is built into Django in django.contrib.auth. And that's going to log the current user out and say that they are now logged out. So what does this look like in practice? Using just this code, I can now go ahead and run the server. Or actually, let me show you the HTML files first. I'll show login.html, which is fairly straightforward. It displays a header that says, log in. If there was a message like invalid credentials, for instance, it displays that inside of a div up at the top. Then I have a form whose action is this login route. So when I click the form and submit the form, it will take me to the login route. I have this token for security and an input field for a username, input field for a password, and a button to actually log in. Then on the user page, it's just going to say, hello to user.firstname. Again, user is a model that's been created for me by Django. It by default has fields like first name, last name, user name, email address, and like are you an administrator of the site or not. It is possible, if you want to, to extend the existing user class, to add additional attributes to the user if you want to. Though it's often not recommended to do that. But certainly something you could do. But first name is built in to Django. Username is built in. So this is is currently logged in as this user name. And if I want to log out, I can click on this link. That will take me to the log out route. And so now, if I go to this default route, I was not logged in. So it took me to this login page first of all where I can now type in my username. And I already created an account, but I'll show you how to do that in just a moment. And if I type in an incorrect password and click Login, it says invalid credentials by re-rendering this login page. If I type in my correct password, then it takes me to this user page that says, hello to my first name, currently logged in as-- there is my user name. And this Log Out button will log me out. And again, if I close this page, reopen it later, it keeps me logged in. The user stays logged in and you configure the details of how that works. And then if I click Log Out, now I'm logged back out and back at the login page. So, how do you actually create users? Presumably in your application and one thing you'll have to do in Project 3 is allow for some way of registering new users. Well, that just means adding a new user to the database. So multiple ways you can do this. One way is by going into the admin interface. If I log in with my username and password-- oh, this is not a staff account. So if you had a superuser account, you could log into the admin interface using that superuser account and modify things. But you can also make the modifications using the shell. So I'll show you that, python manage.py shell. And we'll say from django.contrib.auth-- remember django.contrib.auth is the name of that app that handles authentication-- we'll do .models because we want their models and we want to import user. And then if we want to create a user, I can say user equals User.objects.create_user, it's a function that creates a user. And it takes three arguments. It takes the user's user name, an email address, and a password. So I go ahead and say Alice as the username. We'll use alice@something.com as the email address. And for the password, we'll just say Alice12345. That's the password. And so that's all we need to do in order to create a user. And once I've created that user, I can modify properties of it. I can say user.firstname is going to be equal to Alice with a capital A for instance. And then user.save at the end to save that user. And now I've created a new user taking advantage of this User model that's given to me by Django. So now that that user is created, if I go python manage.py run server to start up my web server again, and then go to that URL, I can now log in using those credentials. Alice is the username, Alice12345 as the password, and now I'm logged in as Alice. Then with the last couple minutes, what I thought we'd do is it takes some time to talk a little bit about Project 3, which is going to be released today. And what Project 3 is going to be about is it's going to be about building an application in Django and taking advantage of the features we saw here, taking advantage of the fact that we can build a registration and log in and log out system just by using the functions that were given to us without needing to build out those models for ourself, taking advantage of the fact that we can use Django's admin interface to be able to very easily add and manipulate data without needing to actually build out a page that lets me add things and edit things and delete things. And what you're going to be doing is building the web application for handling online orders of a pizza restaurant. And one particular pizza restaurant in particularly, actually. It will be Pinocchio's Pizza which is quite a popular pizza place in Cambridge. And so what we're going to give you access to is Pinocchio's Pizza's menu of all of their different types of pizzas and sandwiches and such. And your job is going to be able to design a web application that allows users to first of all register and log in and log out the website. But once they're on it, to be able to then go in and place an order by adding one or more items to their shopping cart and then checking out such that you can then place that order. And so the first thing that you're going to want to do when thinking about designing this web application is really taking a look at this menu that we'll give you access to and figuring out what are the different models that you might want to create in order to represent all of this information. And so we saw a whole bunch of ways to relate different tables to each other today. We looked at just tables that have different fields for themselves. But we also looked at foreign keys that let us relate one object to potentially many objects where multiple different flights can be correspond-- an airport can correspond to multiple different flights where a flight has an origin and a destination. We looked at a lot of many-to-many relationships whereby we had multiple different passengers that could be on multiple different flights in any number of different combinations. So you might want to give some thought to here are different types of pizza, but also different sizes, where any individual pizza might have one, two, or three different types of toppings. And figuring out what forms those data should take, what the relationship between them should be is going to be a helpful good first step. And once you've created those models, when it comes time for the restaurant owner or the site administrator in your case to actually go about populating the menu that the user then sees, rather than have to build out a web interface that lets a site administrator type in, here is a new topping that I want to now allow for Canadian bacon to be offered as a topping for my menu, you can just go into Django's admin user interface, which is built for you, and ideally be able to just add of topping very easily using the interface that's given for you there. And so, that's ultimately going to be the goal of Project 3, to be able to take advantage of the features that Django gives you in order to simplify the process of building an application like this. And that will be released later today. But questions about that at a high level before we wrap up for today? And certainly, once the specification's out feel free to email the staff or reach out on the [INAUDIBLE] if you have further questions about any of that. So that was sort of a look at Django and the features that Django offers. It looked very similar to a Flask, especially when it comes to looking at rendering templates and having functions and having HTML files where we had these Jinja-like templates that were actually in a slightly different language. What you'll ultimately, hopefully find is that Django is quite a bit more powerful, that it gives you a lot more out of the box, that it gives you the ability to deal with more complicated relationships between data much more easily, that it makes it much more easy for you to be able to add and edit and update and delete existing entries that exist inside of your database, that it makes it easier to make modifications to your database, to change a database, and then migrate those changes so that you don't need to worry about actually applying the Alter Table or Create Table commands on your own, and that ultimately it's going to make it easier to build more sophisticated applications by giving you more of these features right out of the box. So that was a first look at Django. We'll wrap up today today and best of luck with Project 3.
Info
Channel: CS50
Views: 83,691
Rating: undefined out of 5
Keywords:
Id: ZjAMRnCu-84
Channel Id: undefined
Length: 101min 14sec (6074 seconds)
Published: Wed Mar 28 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.