SPEAKER: OK welcome back, everyone, to Web Programming
with Python and JavaScript.
So thus far in the course, we've taken a look
at a couple of different technologies.
We took a look at HTML and CSS which are languages
we can use to describe the structure of a web page
and then style it appropriately.
And then we also took a look at Python, a programming language
that we could use to get programming features, features
like loops, and conditions, and variables, and functions and more.
And what we're going to do today is introduce Django,
which is going to combine these two.
Django is a Python web framework which is
going to allow us to write Python code that is able to dynamically generate
HTML and CSS, ultimately allowing us to build a dynamic web application.
So what is this now going to enable us to do
that we weren't able to previously do using just HTML and CSS?
Well, using HTML and CSS, the web pages that we created were static web pages.
They were the same.
And every time you visited the web page, the page you were looking at
was identical.
But ultimately, if you think about websites
you interact with on a day to day basis, say like the New York Times,
for example, it's not the case that every time you
go to the New York Times home page you're seeing the exact same HTML.
It changes.
The next day, you might see the new date.
You might see the next day's articles.
Whenever someone comments in an article, you can see those comments.
And you can see how many people have commented on an article, for example.
And it's probably not the case that someone
is sitting there updating the HTML of the New York Times website
every time something changes, but rather more likely
there is some program, maybe written in Python, maybe written
in some other language, that is dynamically
generating that HTML and CSS and allowing the web
application to respond based on how users are interacting with it.
And this is ultimately what we're going to be trying to create
using this Django web framework.
We're going to be creating software that's going to run on a web server
such that clients, running in their web browser,
can make requests to our web server.
And our server is going to respond back with some sort of response.
And so how does this process actually happen?
Well ultimately, it boils down to this protocol,
HTTP, otherwise known as the HyperText Transfer Protocol,
which is the protocol for how messages are going to be
sent back and forth over the internet.
In this case, you can think about this as a computer, some user, which
we might call the client, and then our server,
which is going to be the server that is going to contain our web application.
We are going to write a Django web application that
is going to run on this server.
And clients are going to make requests to that server.
The server is going to process that request
and then return some sort of response.
That request might look a little something like this.
Underneath the hood, it might start with the word "get."
Get is just an example of a request method,
a way that you might try to get a page.
So get just means, I would like to get a particular page.
In this case, the page I'm trying to get is the slash page,
just denoting the root of the website, usually
the default page for the website.
HTTP 1-1 is just the version of HTTP that we're using.
And the host is what URL we're trying to access the web page for,
so example.com or some other website, for example, that I
might be trying to make a request to.
So this request ultimately gets sent by my web browser, when I type
in a URL and press Return, for example.
The server processes that request and then gives back some sort of response.
And the response will generally look a little something like this, beginning
with the HTTP version 1.1, or in some cases 2, version 2 now,
and then some sort of response code.
200 is a response code the just means OK.
Everything was OK.
The page was returned successfully, for example.
And here on the second line, we see Content-Type text/html,
which just means the format of the data that's coming back in this response
is HTML data.
It's HTML data that the user's web browser on the client
should then render as HTML, for example.
And there are other content types as well
and other information that comes back in that response,
but the key idea is just thinking about the web
in terms of requests and responses.
One user makes a request to get a particular web page.
And what they get back is a response like this that hopefully includes
status code 200, which means OK, but there are
a number of other status codes as well.
Some of the more popular--
200 means OK.
You've probably seen 404, the status code
that means not found, if you try to request a web page that doesn't exist,
for example.
Others you might see or 301, moved permanently.
That often happens if you're redirected from one website
to another, for example.
We'll take a look at an example of that shortly.
403 generally means forbidden.
You're trying to access a page you're not supposed to have access to.
And 500 usually refers to some sort of internal server error,
usually meaning that whoever was writing the server,
maybe our Django web application, has something that's buggy in it.
And we might need to go back and figure out
where that bug is in order to fix it.
And again, there are other status codes as well,
but these are some of the most common that we're
going to be seeing and interacting with as we go about building
web applications using Django.
So Django is a web framework.
It's a set of tools that is already built
for us that's just going to make it easy to get started by writing
Python code in order to design a fully fledged web application by taking care
of some of the stuff that most web pages all
have to do such that we can focus on the interesting logic of building
our own web applications.
So the first step you'll need to do in order to use Django is to install it.
If you're using Python, you may be familiar with pip, the Python package
manager, which makes it easy to be able to install new packages.
And the first thing we'll probably want to do is install Django.
And here we're going to be using Python 3,
so you might need to specify pip3 install Django
to install Django on your system.
And after you do, we can run a command to be able to create a Django project.
And that command looks something like this,
django-admin startproject followed by the name of the project
that we would like to create.
When we run this command, Django is automatically
going to create some starter files for us
just to get started with the process of building a web application.
So let's try creating a project now I'll go ahead and go into my terminal
window.
And what I'm going to do is type that command.
I going to type django-admin and then startproject.
And then I need to give this project a name.
And I'm just going to give this project a name of lecture3, for example.
You can name the project whatever you'd like.
And when that happens, when I type ls, what I see
is that a lecture3 directory or folder has been created for me by Django.
And if I go ahead and open this up inside of my text editor,
we can take a look at what files are inside of this lecture3 directory.
So actually, I'll go ahead and cd into lecture3 and open it up.
And what we see inside of lecture3 are a couple of files
that we're given just to get started.
So there are a couple of files.
And not all of them are really interesting to us right now.
But first and foremost, manage.py is a file
that Django is going to create for us.
We'll generally not need to touch that file,
but we're going to use the manage.py file to be able to execute commands
on this Django project.
And we'll see a couple of examples of that now
and a couple of examples of that in future lectures as well.
And then the other important one that we'll be looking at
is settings.py, which just contains important configuration
settings for our Django application.
Settings.py comes preloaded with a couple of default settings,
but we might want to change those settings to be
able to add features to our application as well
or make some modifications to how the application behaves.
And then the other important file that's pre-created for us now here is urls.py.
And you can think of urls.py as a sort of table of contents for our web
application, that on any given web application, on a website,
there are a number of different URLs or routes that you can visit.
On Google, for example, you can visit /search or /images.
And urls.py is just going to be a table of contents
of all of URLs on my web application that I can ultimately visit.
And so if I'd like to try running this web application just
to see what it looks like, the way to do it in Django is, in the terminal,
I'll run python manage.py followed by a command.
And the command is called runserver.
And so this is generally how we'll use manage.py.
We'll manage.py followed by an argument specifying
what command we would like to run.
And python manage.py runserver means go ahead and actually run this web server.
And if I do this, I'm now running this web server locally.
And I see a bunch of debugging output.
But the interesting point to me is that it says down here,
starting development server at http://127.0.0.1:8000
And so this is where my web application is currently
running, 127.0.0.1 is an IP address, an address on the internet that
just refers to my local computer, the computer I'm looking at right now.
So only I can access this, not anyone else on the internet.
And 8000 is what we would call a port number.
It just refers to what type of service is being run.
And you might have multiple different internet
services running on different ports.
In this case, our Django application is running on port 8000.
So if I copy this URL and just go ahead and go into my web browser
and paste that URL in, what I see is just Django's default page to say,
our installation of Django works.
And this now is the default page the Django
is going to give to us to say that Django has
been loaded for this web application.
And we can now actually start to begin building this web application
by adding the features we want to it.
And so what we've created here is what we call a Django project.
And in terms of the way a Django project is structured,
is that every Django project generally consists of one or more Django
applications.
So one project may have multiple applications within it.
And the reason for this division is, if you think about big websites,
oftentimes a big website, a big project has
multiple different apps that are sort of separate services
that operate within it.
Google, for example, has Google search, but also Google Images, and Google
News, and Google Maps, where you can think
of each of those individual services as a separate app
all under one larger project like Google, for example.
Or Amazon's website, for instance, might be one big project
that has a couple of different apps within it, one app for shopping maybe,
and one application for Amazon's video service, for example.
And so Django comes preloaded with its ability to take a project
and divide it into multiple distinct apps.
Maybe for simpler apps, we're only going to have
a project that has one app inside of it instead of multiple.
But it has the ability to allow us to create these separate apps that
have different capacities.
So if we want to get started with Django,
the first thing we'll need to do after we create a project is create a Django.
App and so the way to do that, so go back to the terminal.
I'll go ahead and exit out of this server
by pressing Control C to say I would like to exit, stop running the server.
So now if I go back to that URL and refresh it,
it won't work, because the Django server is no longer running.
But I'll run the command python manage.py startapp and then
the name of the app that I would like to create.
And for our very first app, I'll just call this app hello, for example.
So I've now created one Django project called lecture3,
and inside of which I've created the very first app that I've now
called hello.
And so if we take a look at our files now,
you'll see that, in addition to a lecture3 directory which
existed before, we also now have a hello directory the Django created for us
that is going to contain the information needed for the hello app to work.
And there are a number of files, again, that are generated by default.
We're not going to look at all of them today.
We're going to focus primarily on views.py--
we'll see why in a moment-- which is going to be the file that lets
us describe what it is the user sees when they visit
a particular route, for example, where we can
decide what gets rendered to the user.
And the other files, we'll take a look at over the course of this term,
taking a look at what additional features Django has to offer.
So we've now created this hello app that we would
like to install into this project.
And in order to install it, we need to go
into the settings for this particular Django project.
So if I go into the lecture3 directory and open up
settings.py, what I'll see here is a big file all created for me by Django.
I didn't write any of this code.
But this is all just the default configuration for a Django project.
And if you scroll down, somewhere there you'll see a variable
called INSTALLED_APPS, which is where Django configures what
apps are installed on this project.
And there are a whole bunch of individual apps
that are installed by default that are just created by Django.
One, for example, manages sessions, which we'll take a look at a little bit
later.
But if I want to add my new app that I've just created called hello
to these installed apps, I'll go ahead and just
add to this list of strings, this INSTALLED_APPS variable,
I'll just add hello.
And so now hello is going to be an installed app on this particular Django
project.
So now what I'd like to do is actually make this app,
this hello app do something and display something
when I try and visit a particular route, for example.
So how can I go about doing that?
Well, let's go ahead and go into the hello directory.
And let's take a look at views.py inside of my hello app.
So here is the default file, again, from views.py.
I see comment that's given to me by default
that says, create your views here.
And you can think of each view as something the user might want to see.
So let's go ahead and just create a default view.
In order to create a view in Django, we're going to define a function.
So this function I'll just call index.
This function, by convention, takes as argument a request argument.
And this is going to be an argument that's
going to represent the HTTP request that the user made in order
to access our web server.
So if we want information about that request,
we can look inside of this requested object
to get access to some other data.
And we'll take a look at that momentarily too.
But now, what would I like for this request to do?
Well, what I want to do for now is, let's just do something simple
and return an HttpResponse of, "Hello, world!," for example.
Now, HttpResponse is a special class created by Django.
And so if I want to use it, I'll generally need to import it.
And so Django is going to give us a lot of features
that we might want to use in our web applications,
but if we want to use them, we generally have to import them first.
And it just so happens-- and I only know this
from reading Django's documentation-- that I can include a line like this,
from django.http import HttpResponse, to say I would like to import the ability
to give an HTTP response.
And now what I have is a function representing my view.
This function is called index.
And what the index function does is it just returns
an HTTP response of, "Hello, world!"
And so this is the response.
But now we need to tell the app, like, when should you
actually return this response?
What URL is the user going to visit?
And this is where we now begin to create some URL configuration, some sort
of setting to tell Django when a particular URL is visited,
then this function should be run in order to return that particular HTTP
response.
So how to do that--
well, in order to do that, we're going to need to create
a urls.py file for this particular app.
So Django has one urls.py file that works for the entire project.
But oftentimes we'll have each app contain its own urls.py file just
for the sake of separating things out into different places,
such that if we have multiple different apps, each of which
is doing something independent, we can have each of those individual apps
have its own urls.py file to control what URLs
are available for that particular app.
So I'll go ahead and go into the hello directory.
And I'll create a new file inside of the hello directory called you urls.py.
And what urls.py needs is, it needs to define a variable called urlpatterns,
which will be a list of all of the allowable URLs that can
be accessed for this particular app.
And the way you create a URL is by first importing from django.urls import path.
And let's create our first URL.
I'll say path.
And then the first argument here, I'm going
to give the empty string to mean no additional argument--
and we'll see what that means in just a moment-- meaning nothing
at the end of the route.
And then the second argument to path is what view should
be rendered when this URL is visited.
And so if I want to render my index view--
recall that in views.py over here, I have this index function--
then what I want to render when someone visits this URL, the empty URL,
is going to be views.index.
Views represents views.py, that file where I've defined all of my views.
And an index just so happens to be the name of the function
that I want to call when someone visits this URL, for example.
And then you can optionally provide a path with a string name .
To represent this URL I'm going to give this a name of index.
We'll see why this can be useful later on,
but the idea is, giving a name to a particular URL pattern
makes it easy to reference it from other parts of the application.
So later when we might want to link to things
or have forms that are submitting to different parts of the web application,
giving a name to a path can be a useful tool.
In order to use views, last line I need to add to urls.py
is to say from dot, in other words, from the current directory,
go ahead and import views.
Anytime I'm using a variable name like views, I'm using that name,
I need to import it from somewhere.
And it just so happens that views.py and urls.py
are located in the same directory.
So I can just say from dot import views to import all of that
into this particular file.
So this now is the urls.py file for this application.
But the last step in order to get all of this working for the very first time
is to go back into the lecture3 directory and open up urls.py here.
This is the urls.py file for the entire project,
for all of the apps that might be contained within this project.
And it just so happens that there is one path already given
to us by default called admin which runs a default Django application called
the admin application.
We'll see more about that in the next lecture.
But for now, I'd like to add my own route here
to say that I would like for a particular path
to lead to not the admin app, but the hello app that I have just now created.
So I'll go ahead and give this a path of hello.
And what I'd like to do is say that, after you've
included the path hello, go ahead and include all of the URLs
from the urls.py my hello application.
I'd basically like to link these two URL configuration files together.
And so the command to do that is include hello.urls.
Inside of the hello module, get at the urls file there.
And that is now what I would like to add as a URL pattern.
And in order to do this, I also need to import include since I'm
using include inside of URL patterns.
So that was a lot of steps just to get things started.
But just to get a high-level overview of how this is all working,
I have a Django project called lecture3.
And inside of lecture3, there is a URLs file
that decides what URLs I can access.
I can access /admin, which takes me to the admin application which is created
by Django.
We don't have to worry about that just yet.
But now I've just added that you can go to /hello to go to the hello
application.
And when you do that, I'm telling Django to look
at the urls.py inside of the hello directory
to figure out what additional URLs I can get to from there.
So this is one master urls.py file that might
connect to multiple different other URL configurations that exist as well.
Then inside of the urls.py for my app, for the hello app,
I've said that when someone visits just the default
route on this particular application, go ahead and just run the index
function that is one of my views.
So all that's to say, now that that is done,
I should be able to go back into my terminal
and run python manage.py runserver to actually start up this web server.
And now I can go back to this URL, but instead of just going to that default
URL, I'll go ahead and go to that URL /hello.
And when I go to /hello, what I see is hello world.
So what was happening there is that I typed in a URL.
That URL went to Django.
And Django looked at that URL and looked at my urls.py file and said, you know,
anytime something starts with /hello, that belongs to the hello app.
And inside of our hello configuration, we
said that when we go to the default route,
we should run the index function.
And the index function returns this "Hello, world!"
response.
And now we can begin to change that response.
We can begin to adjust what it is that views.py is actually doing.
Right now it says "Hello, world."
But if I want to change it to just say, like,
hello, for example, I can just edit it.
Now say the index function just returns and HTTP response of "Hello!,"
for example.
And so now I have to refresh this page.
Now it just says "Hello!"
So I can edit my files.
When I do, Django notices that a change has been made.
And it will re-update the server with my latest code such that now I can visit
/hello.
And I can see that an HTTP response of "Hello!"
is what ultimately comes back to me.
So now let's take a look at the idea that, in addition to just
having one view inside of views.py, I can have as many views as I want.
I can create additional functions that each return different responses.
Maybe I'd like a route that says hello to me, for example.
So let me define a new function that I'll call
brian that accepts a request argument.
And this function is just going to return HttpResponse "Hello, Brian!,"
for example.
And now I need to associate this new view that I've created with the URL.
So I'll go ahead and go back to urls.py inside of my hello directory.
And so I have a default route represented by the empty string
that loads the index function.
But I can add to this, just add to this list, add a new path,
where if I type brian into the URL instead,
that will load the brian function.
And I'll go ahead and give that a name as well.
So now if I refresh the page-- and I'm still on just a /hello route.
And that just displays "Hello!," for example.
But if I go to /brian, for example, now I see "Hello, Brian!"
I have two different URLs, one that is just /hello with nothing after it,
which was that empty string, and one which was /hello with brian following
it that loads a different view function that returns a different HTTP response.
And so we could imagine continuing to do this.
I might want a web application that has a number of different URLs
that I can visit.
So I could go ahead and add, like, a david function,
for example, that returns in HttpResponse of "Hello, David!"
And then again, inside of my URL configuration,
I would say, go ahead and give me another route called david that loads
the david function.
Each time, I'm giving it a URL, so what comes after the /hello.
I'm giving it a function to run, the david function inside of views.py,
and giving it a name just to make it easier to reference a little bit later.
And so now I have /hello/brian, and now I have /hello/david,
each of which displays a different HTTP response.
So you can imagine starting to use this feature
to begin to build a web application that has a number of different routes.
It has different routes to do different things.
And many web applications are parameterized
by what it is it's actually in the URL.
So for example, on Twitter, you can go to a Twitter.com slash someone's user
name to see all of that user's tweets, for example.
Or GitHub, a service we've taken a look at now,
you can go to GitHub.com slash your GitHub user
name to be able to see all of the repositories for a particular user,
for example.
So if you think about how that might be implemented if either of these services
were using Django, they might have a urls.py file or something like it
that is just defining a whole bunch of URLs
and saying what route should be associated with them when
someone visits that page.
But you can imagine ultimately that this is probably going to start to get
tedious, that if I want to say hello to anyone,
if I want to not only support /hello, /brian, and /david,
but I also want to support arbitrary names like /harry, or /ron,
or /hermione, well then it would seem that I need to create a whole bunch
of different functions, each of which says hello to a different person,
and then create a whole bunch of different URLs to be able to do that
as well.
But it turns out that what I can do is create URL patterns that
have placeholders, in effect, things that
allow me to parameterize the path via certain path converters, so to speak.
So what does that actually mean?
Well, let's instead of creating functions
that just say "Hello, Brian!" and "Hello, David!,"
let me create a new function that's just called greet,
for example, that also takes a request, but takes an additional parameter.
It takes a parameter like someone's name, for example.
And this greet function is going to return
an HttpResponse of "Hello comma," and then I'll go ahead and plug in the name
here.
And I'll need to add an f to the beginning of the string to indicate
that it's a formatted string--
this is just Python syntax--
to say that I would like to say hello, not just to anyone,
but to whatever the value of the name variable happens to be.
I'd like to substitute the name variable into that greet function.
And then what I can do is, inside of you urls.py,
I'll go ahead and add another path, but instead of saying a name like Brian,
or David, or Harry, for example here, I'm going to say in angled brackets
str:name.
And when someone visits that route, let's go to the views.greet function.
And the name for this route will be greet.
And so what's going on here is we have a fundamentally different kind of path.
Rather than prescribing exactly what the URL should look like, like, nothing
after the end of the route, or Brian at the end of the route,
or David at the end of the route, this route here on line seven
is saying this route code be any string that we're going
to give a variable name of name to.
But you could replace a name with something else entirely.
And this could be any string.
So it could be Brian, or David.
Or it could be any other name.
And when that happens, we'll call the greet function.
And when that function is called, it will pass in this argument, this name,
as a parameter to that function.
So now I'd be able to create a custom route that
allows me to specify any string and figure out
how to deal with that appropriately.
So now if I visit /hello/harry, for example,
not a route that I explicitly created, but that is a string--
harry is a string.
I press Return.
It says, "Hello, harry!"
I can go to /hello/ron and say "Hello, ron!," and /hermione to see "Hello,
hermione!" as well.
And you know what?
Maybe just for good measure, I'd like to capitalize this name,
like Hermione, for example.
Well, the way to do that is that I can add arbitrary Python logic.
It turns out that with any Python string,
there is a function or method on that string called capitalize that I can say
is just dot capitalize.
And if I can do it in Python, then Django
allows me to incorporate it into the response that I'm giving back.
So I'm now using Python to take the name,
capitalize it, and use that in the response that
gets sent back to the user.
So now /hermione returns "Hello, Hermione!" name with a capital H,
likewise for "Hello, Ron!," likewise for "Hello, Harry!"
I've now been able to add a route that takes an HTTP request
as well as a parameter, that name, whatever was in the URL,
and return an HTTP response that just says hello to that person.
So these HTTP responses can be any HTML content.
Right now we're just using text, but you can imagine
adding lists or tables to this as well.
But you might imagine that if I have to include an entire HTML page just
inside these double quotes, that that's going to get unwieldy very quickly.
Very quickly we're going to find that there
is a lot of HTML to just go in a single string inside of a Python program.
And if we think back to the principles and the ideas
that we've been looking at in this course so far,
we've generally tried to separate out different parts of our application
wherever possible.
And there is a lot of value in that, one,
in just keeping things clean, two in making sure
that people are able to collaborate.
If you want one person working on the Python logic and one person working
on the HTML, you'd rather they not step on each other's toes
as they're working.
And so what we can do in Django as well is separate the response, the HTML,
from the actual Python code here as well.
And so the way we can do that is, instead of returning an HTTP response,
I can instead--
let's say for this default route, instead
of returning an HTTP response of hello, let me go ahead and render.
And when I render something I need to pass in the HTTP request.
But I'll also pass in the name of a template.
And I'll go ahead and call this template hello/index.html.
So if I don't want to render just a string,
but I want to render an entire HTML file, I can call this render function,
pass in the request, but then also pass in the name of a template.
And now all I need to do is create that template.
So let me go ahead, and inside of the hello directory,
I'll create a new folder called templates.
Inside of that template, I'll create a folder called hello,
because the template name here is hello/index.html.
So I need to create a folder called hello, inside of which is a file
called index.html.
I could have just called it index.html without the hello, but the reason
we often want to prefix our templates with a directory name
is to so-called namespace them, to make sure
that if we have multiple different index.html files
in multiple different apps, to make sure they don't conflict with each other.
So Django best practice is to use hello/index.html,
or whatever your app name is.
And inside of that hello directory, I'll then create a file called index.html.
And this index.html can contain any HTML content, same as we've seen before.
So I could add that doc type, the HTML tag, a title of hello.
And maybe inside the body I'll have an h1 that says, "Hello, World!,"
for example.
So I can have an entire HTML page just like this.
And now when I visit the default route of my web application by going back
into my web browser and just going to /hello, for example,
now I see that HTML that I've defined inside of index.html.
It's a big h1 that just says, "Hello, world!"
And it turns out that these HTML pages, these templates that I can render using
Django are parameterizable as well.
Maybe I want to implement hello/hermione as some sort of HTML page, an HTML page
that says, "Hello, Hermione!"
And using HTML out of the box, you can't really do stuff like this.
HTML is a markup language, not so much a programming language,
which means it doesn't, by default, have support for things like a variable
to represent someone's name.
But using Django's ability to take an HTML page and treat it like a template
that we can render, Django has added its own templating language,
so to speak, on top of the existing HTML.
And I can take advantage of that to be able to render an HTML page that
actually has a variable inside of it, or a condition inside of it,
or a loop inside of it, as we'll see in just a moment as well.
So let's go ahead and go back to the views.py.
And the function that I would like to change
is this greet function here that right now is just returning an HTTP response,
but I'd like to render an entire page, for example.
So what might I render?
Well, let's render a template called hello/greet.html.
And then this render function can take an optional third argument
which is called the context.
And the context is all of the information
that I would like to provide to the template, all the variables,
for example, that I want the template to have access to.
And so one thing I might want my template to have access to,
for instance, is something like a name.
And so this is a Python dictionary, just a sequence of key value pairs.
And this name might be associated with what value?
So name is the key.
The value I want to be name.capitalize.
And so what's going on here is that now when I render this template,
this template hello/greet.html, I'm providing that template with some
additional content, some additional information as represented by this
dictionary here, where I'm providing information with a key of name
and effectively giving this template access to a variable called name.
And its value is whatever name.capitalize
is equal to, where this name here is the argument to the function greet.
So now I can return this template.
And inside of greet.html, I can use this variable called name.
So how do I go about doing that?
Well, let's first create that greet.html file.
So inside of the template/hello directory, I already have index.html.
I'll go ahead and create a new file called greet.html.
And inside of greet.html, I'll go ahead and write some HTML.
And inside the body of the HTML, whereas before I
might have said something like "Hello, world,"
I don't want to say hello to world.
I just want to say hello to whoever the name is, whatever is contained inside
of that variable called name.
So how might they go about doing that?
Well, what I can do is use these double curly braces.
And double curly braces are part of the Django templating language
that allows me to say, I would like to plug in the value of the variable
into this particular position inside of my template.
And so if I include the word name here, then what I'm saying here
is that when you render this greet.html template, inside the body of the page,
I would like for there to be an h1, a big heading that just says "hello
comma."
And then inside these double curly braces,
I'm saying plug-in the value of the variable name there.
If name is Hermione, it's going to be "Hello, Hermoine!"
If it's Harry, it's going to be "Hello, Harry!"
If it's David, it's going to be "Hello, David!".
So let's go ahead and give this a try now.
Now when I go to /hello/harry, for example,
I now see a big h1 tag that says "Hello, Harry!"
If I go to /hello/Ron, I see a big h1 that says "Hello, Ron!"
And the reason why all this is happening,
the way we're getting to that final position
is because you urls says that when I have
a URL that just is a string like someone's name,
we'll call the greet function.
The greet function inside of views.py is taking the name as an argument,
rendering the greet.html template, and passing in that name
as part of the context, part of what the template has access to.
And inside of greet.html, here is the actual HTML file.
Here is where we then plug in the value of name.
So it's a lot happening across a lot of different files.
But the reason for all of these various different files
is to help keep things separate, to have one file that
is just responsible for URLs and directing people to what should
happen when those URLs are clicked.
Then we have one file, views.py, that is entirely responsible for deciding, ,
on this particular view what template should be rendered,
what information should be passed in this context.
And then I separately have a file for each of my HTML templates
that are saying what does the page actually look like.
And so if you start to begin to think about the separation of components
inside of a web application, it can help to make the structure of a Django
application a little bit clearer.
So we've now been able to use the Django templating language to put variables
inside of our HTML templates, to be able to create
an infinite number of different routes where
I can visit slash hello slash some name in order
to display hello to that person's name.
But the Django templating language is even more powerful than just that.
There are a lot of additional features that the templating language
is going to give us access to.
And we'll take a look at a couple of those now.
And to start I'm going to introduce you to a website you may be familiar with.
I consider it to be one of the simplest websites on the internet.
It's a real website called isitchristmas.com.
And if I visit isitchristmas.com and press Return,
here is what the website says.
The website says, no.
It is not Christmas.
And you just have to take my word for it.
If you visit this website on Christmas day,
you go to isitchristmas.com, it will instead say, yes.
And so this website is very simple.
You might imagine that it's really just an HTML page that probably contains
a big heading that, in this case, just says the word "no,"
but on Christmas day, just as the word "yes."
Now, how might a page like this be implemented?
Well, one way you could imagine is that on Christmas day
whoever maintains the website goes into the HTML and changes no to yes,
and then afterwards changes the yes back to a no.
But we can be a little bit cleverer about this
if we realize now that we have the ability
to use Python logic to be able to use logic like a condition
to be able to decide how a web page is ultimately going to be rendered.
For instance, the condition might be something simple like,
if today's date is Christmas, then render yes, else render no.
And so we're going to use Django and take
a look at some of the features of Django's templating syntax to be
able to create a website like this.
We're not going to create Is It Christmas.
We're going to create Is It New Year's, that
checks if the current date is January 1 or not.
This is going to be a separate app.
It's sort of distinct from our hello app.
The hello app just says hello to people.
The new year app, for example, is just going
to see whether or not it happens to be New Year's, for example.
So I will now go ahead and create a new app.
And I can do that by some writing python manage.py startapp.
And I'll call the app newyear, for example.
I'd like to create a new app called newyear.
I've now created that new app.
If I type ls, you'll see that not only do
I have a hello directory that represents the hello app,
I now have this newyear directory that represents the fact
that this is a new app called newyear.
As with before, I'll need to go into settings.py inside of my project
directory and add newyear as an installed app.
This is now a new app that exists in my web application,
so it too now needs to be a new installed app.
What else do we need to do for new apps?
Well, I need to go into urls.py for lecture3.
And just as before, I had a path saying when I go to /hello,
then you should go to all the URLs for the hello app,
let me add a new path that says that if I go to my application /newyear,
well then you should go to all of URLs for the newyear file,
go to newyear.urls, representing the urls.py file inside of the newyear app.
Now, that file isn't given to us by default, so we need to create it.
I need to go into my newyear folder and create a new file called urls.py.
And inside of that file, I'll do from django.urls import path, from .
import views, and then define some urlpattern, same as before,
where I'll just have a single path that loads the index function inside
of views.py.
And it has a name of index.
So again, just mirroring the type of structure
that we've already seen before in the hello app--
this app is just going to have a single route, the empty route, that
loads the index function.
Now all that's left to do is to actually create that index function.
So we go into views.py.
And here now I'll define an index function that
takes an HTTP request as an argument.
And now inside of this index function, I would
like to add some logic that checks whether or not it's January 1.
And so how might I go about doing that in Python?
Well, it turns out that there is a date time module in Python.
And you can learn about this just by reading up about its documentation
to figure out how it works.
And the date time module gives me access to things about the date and time,
for instance.
And I can play around with the date time module outside of Django just
to get a feel for how it works.
If I just type python into the interpreter--
into my command line, rather, what I get is the Python interpreter that
lets me just write Python code just to experiment, and test,
and see what the result of running this Python code would be.
So I can try, like, import datetime, for example.
And let me create a new variable called now, which is datetime.datetime.now.
It just so happens that inside of the datetime module
is a function called datetime.now that gets
the current date and time, for example.
And so inside of now, I have access to variables like now.year,
for example, which tells me the year, now.month,
now.day that gives me information about the year,
the month, and the day of the current timestamp based on getting
whatever the current time is right now.
And given that I have this information.
You can imagine us constructing a Boolean
condition to check whether or not today is in fact the new year.
And that condition might look like now.month equals 1 and now.day equals
1.
And I can press Return and see that, OK, the result of this condition is false.
It is not true that both the month the day are true--
or are 1, rather.
And so using that sort of condition, I can now
take that and adapt it into my Django view
that I'm using to try and render whether or not it's the new year or not.
So how might I go about doing that?
Well, what I'd like for my index function to do
is return render a template.
The template will be called newyear/index.html.
And then what context what I would like to provide to this template?
What information, what variables do I want
for this template to have access to?
Well, I want it to have access to a variable that'll
just be called newyear.
And in order to access that, I need access to the current date.
So to do that, at the top of my file, I'll go ahead and import datetime.
And inside of my index function, let me give myself
a variable called now, which is equal to datetime.datetime.now.
And this newyear variable that I'm passing in to my template
will be equal to now.month equals 1 and now.day equals 1.
So if it is the case that after I get the current date
and time by running datetime.datetime.now,
saving the result inside of this variable
called now, if both and the day are equal to 1,
then the value of this new year variable is
going to be true when the template gets access to it.
Otherwise, as it is the case today and on most days,
the value of that variable is going to be false.
Now all that remains for us to do is to actually create this template,
newyear/index.html, and have that template somehow use this newyear
variable in some interesting or meaningful way.
So how do we do that?
Well, if you recall what we did with the hello application, when
we want a template, then inside of our application, this newyear app,
we'll need a new folder that we'll call templates,
inside of which I'll create a new folder called newyear.
And inside of that, I'll create a new file that will be called index.html.
Here will be the index.html file for this new year application.
The structure will be very similar.
I'll give it a title of Is it New Year's?
And inside the body, now here is where the interesting logic is.
Sometimes I might want a big h1 that says, YES.
Other times, I might want a big h1 that says, NO.
And what I need to do is conditionally decide when to say yes
and when to say no.
And inside Django's templating language--
and again, you'd only know this by reading Django's documentation--
just as we used double curly braces to say plug in the value of a variable
here, the syntax for logic inside of a Django template, like conditions,
is curly brace percent sign.
So we use curly brace percent sign, some logical statement, and then
percent sine curly brace to include any kind of logic.
And the logic in this case is an if statement.
And this is very Python-like.
We'll say if newyear--
newyear is the name of that variable that I passed into this template.
And if it is a new year, then I want to display an h1 that just says, YES.
And then else, also inside of curly braces and percent signs,
I want a big h1 that just says, NO.
So if it's the new year, then yes.
Else, no.
And then Django also requires me to give an endif tag
to say this is the end of the if statement.
Unlike Python itself that uses indentation to denote when the if
statement is beginning and ending, in Django,
the indentation is optional inside of the template.
But in order to distinguish when the if statement is
happening from when the if statement is over,
we need this endif tag at the very end.
So this here is a condition inside of our Django template.
We're saying, if a particular variable is true, then render this in the HTML.
And else render something else.
If it's the new year, say, yes, it's the new year.
Otherwise say, no, it is not the new year.
So we can try this out.
Right now we're on the route /hello/ron.
If I instead, not go to /hello, but go to /newyear, for example,
this site can't be reached.
OK, so why did that happen?
127.0.0.1 refused to connect.
So what must be happening is, for some reason, my web server isn't running.
And it turns out that, in order to create the new application,
I had stopped my web server.
And so if ever that happens, to start it up again,
we can just rerun python manage.py runserver
to say I would like to now start up this web application again.
And all right, it looks like now I have another error.
There is some sort of syntax error.
And the syntax error is a newyear/views.py So let me go ahead
and go back into views.py and see if I can spot where the syntax error is.
And all right, it looks like render is a function.
Function arguments need to be enclosed in parentheses.
And while I have a starting parenthesis here,
I had forgotten a closing parenthesis.
So I'll go ahead and add.
That and now that will complete the render function.
And now I should be able to load /newyear.
And indeed what I see is that, no, it is not the new year.
And so what happened is we ran some Python logic, calculated
the current date and time, checked to see is the current month 1,
is the current day 1.
And if that's not true, then we're here displaying, no.
And if we look at the actual HTML of this page,
like, what HTML is actually making up this particular page,
which I can do by right clicking or Control
clicking and just clicking View Page Source--
this will show me the HTML of this page.
So this is the HTML that came back from the web server
that my web browser, Chrome in this case, is rendering.
And what you can see is it looks very similar to the index.html template we
wrote before, but it only contains no.
It doesn't contain any of that if logic.
It doesn't contain the yes.
It just contains the HTML content that we wanted
to respond with back to the user.
And so you might imagine based on this that would
Django is really doing is taking the index.html template
and then manipulating it based on the input we get,
based on whether or not it's the new year
or not, to say that, if it's not the new year,
then all we should do inside the body is display an h1 that says, no.
And the user when they get it, they don't see the conditions.
They don't see there was another branch that could've been taken.
They only see the final result of rendering
the template with whatever variables, and logic, and conditions happened
to be inside of that rendering process.
So when the user sees it, all they see is the word "no."
And if we were to run this program on New Year's Day,
it would indeed say, yes.
And we can test it just to see what it would be like if that
were the case by cheating a little bit.
Instead of now.month equals 1 and now.day equals 1, let me just go ahead
and replace this condition with newyear is equal to true.
Just to test it, let's try passing in the value true as the value of newyear
and see what happens instead.
And now when I run this page, now it does say yes,
as we might expect it to on New Year's.
And so that can be a nice way of just testing what
would happen if you were to replace a particular variable
with a particular value.
We can substitute inside the context just
for development purposes what value we would like that variable to take on.
Now we've rendered HTML, but we haven't really
added any styling to this website just yet.
The real isitchristmas.com has the text centered a little bit.
It's in sans serif font instead of having the little serifs or the glyphs
at the edge of each of the characters.
So maybe I'd like to add some custom CSS to this file as well.
And we could do this conventionally the way
we've seen it done before by just including a CSS file.
And that is what we're still going to do.
But Django has a special build system for dealing
with what are called static files, files that aren't going to change.
The HTML of this page, that's not a static file,
because it changes depending on whether it's New Year's or not.
If I visit it on New Year's, it says, yes.
If I visit it when it's not New Year's, it says, no.
And so this is a dynamic page.
But static files, files that don't change, like our CSS--
the CSS doesn't change whether it's New Year's or not.
And because that file is unchanging, Django calls it a static file.
And it means that Django can be a little bit cleverer about it.
If you start to think about projects of scale,
you might store your static file somewhere separate
just in some place that makes it easy to access, where they're cached for faster
reads later on.
We'll talk about that a little more later in the course
as we start to delve into topics like scalability,
as you begin to build larger web applications on the internet.
But long story short, Django contains a lot
of features that make it easy for us to deal with static files, files
that don't change like CSS files.
And generally the way that we'll add static files is,
inside of the newyear folder, in addition to having
a templates/newyear/index.html, we'll also create a new folder called static
that will contain all of the static files that we would like to include
inside of this application as well.
Inside of static, I'll create a new folder called newyear,
inside of which is a new file called styles.css.
And so inside of styles.css, I can now write all of the same CSS
that I would want to have written before.
So maybe I want all my h1s to have a font family of sans serif, a font size
of 90 pixels, maybe, and I want the text alignment
to be centered, just a couple of CSS properties and values, same
as we've already seen, just to say I would like
to give h1 tags this particular style.
Now what's left to do inside of index.html is, at the top of our page,
I'll go ahead and add a command that says load static to mean go ahead
and load static files for this particular HTML page.
And now I'll go ahead and link a style sheet.
You'll recall that this type of command inside the head section of my web page
where I would like to link some particular URL as a style sheet
is how I add CSS to a page.
But what I'm going to include in here as the link is,
I'm going to include, rather than hardcoding a URL, which I could do,
Django best practice says that, instead, let's just
say it's going to be a static file.
And the static file is called newyear/styles.css.
So I'm not specifying exactly what the URL is,
but I'm saying it is a static file that is inside of a newyear folder
called styles.css.
And Django is going to figure out what that URL ought to be.
And this is often better than hardcoding a specific URL,
because maybe you might imagine in larger web applications, where
you're static files are might change.
You might move your static files to a different domain
or to a different route.
And so in order to deal with that, this static keyword
just means Django will figure out where your static files are located and will
replace this command here in the curly braces and percent
signs with the actual URL for this particular file.
So now that I've said that I want to link this particular static file,
styles.css, into this web page, what I might first
need to do just to let Django load static files is restart the server.
So you might sometimes need to Control C and then
go ahead and rerun python manage.py runserver.
That will rerun the server.
And now if I go back to the /newyear route inside my web browser,
now I see the style look a little more close to what I actually wanted it
to be, centered, sans serif, in a larger font that, here, just says, no.
And if we look at the underlying HTML of this page,
we see that Django has actually for us filled in what the static URL is
for these particular static files.
And by default, Django just uses /static/ and then whatever the files
happen to be.
And so anytime we're dealing with static files, files
that don't change like CSS files or JavaScript files, which
we'll take a look at later in the course, that's generally where they're
going to go, in some sort of static file that we're going to ultimately link
to this particular page.
So now we've seen a couple of examples of web applications
we can make using Django.
We've seen the hello app in Django that can parameterize
URLs, that, depending on the URL we visit,
can say hello to Brian, or hello to David, or Harry, or Ron, or Hermione.
And we've seen the ability to, using Django, add some conditions,
to be able to conditionally say, if something is true,
render this page, if something else is true, render another page.
Let's now use these features plus some additional features
to begin to build a more sophisticated web application, something like a to do
list, that maybe I want to web application that gives me
a list of tasks, that lets me add new tasks to my to do list,
and lets me view all of the tasks that are currently on my list.
And I'd like to build this up one step at a time.
So where can I start with this?
Well this, again, is going to be a new app.
Inside of my lecture3 project, I have two apps so far,
a hello app and an app for the newyear.
And now what I'd like to do is create a third app
that I'm just going to call tasks.
That is going to be my task management app that's all
under this umbrella lecture3 project.
So I'll go ahead and run python manage.py startapp tasks
to say I'd like to go ahead and create a new app called tasks.
Anytime I create a new app, a couple of steps I need to follow--
recall that I need to go into settings.py inside of lecture3
to say that, in addition to installing hello and newyear,
I'd also like to install the tasks app, for example.
Then I need to go into urls.py inside of lecture3
to say that, in addition to hello and newyear,
I'd also like for us to include the URLs for tasks.
This urls is, again, the table of contents for my entire web application,
where in my web application, if I visit /hello,
then we go to the URLs for the hello app.
If I go to /tasks, then I go to the URLs for the tasks up, so on and so forth.
It tells me all of the different URLs that I now have access to.
And as with before, the URLs file isn't created
for me inside of my application.
So I will need to go into the tasks directory.
And inside of the task directory, I'll go ahead and create a new file
that I will call urls.py.
And the format of this is going to be very similar to what we've already
seen, from django.urls import path from . import views.
And now I define my urlpatterns, where I would
like a path that is just the empty string, the empty path for now,
that loads the index function whose name is index, for example.
So now let's actually write this index function inside of views.py.
What I'd like to do is define a function called index that accepts a request.
And what I'd like to do is render a page that
displays a list of all of my tasks.
So before we get to the idea of adding tasks,
let's just see if we can get our program to display a list of tasks,
for example.
So I'll, up at the top, create a global variable
that I'll just call tasks that the entire application is
going to have access to.
And I'll add three tasks to it.
For now, we'll just use foo, bar, and baz, just sort of nonsense names
that are just useful strings for testing purposes,
just placing those as sample tasks that I might
want to have inside of my application.
And now what I'd like to do is render a template.
The template I'd like to render will be called tasks/index.html.
And then I'll provide some context to it, some information
that index.html needs.
What information does index.html need?
Well, index.html needs access to all of my tasks.
And where are all of my tasks?
Well, they're inside of this variable that just so happens
to also be called tasks.
And this will be something we see quite often as a paradigm in Django,
where we see a key and a value that look like they have the same name.
The key distinction is, whatever is here on the right after the colon,
this is the value the variable takes on.
This is a Python variable, like this Python variable tasks here.
On the left, this key, this string tasks, that is the name of the variable
that the HTML template will have access to when Django is rendering it.
So Django has access to this variable name on the left that
has this value on the right.
So if you see this paradigm, that's ultimately what that's going to mean.
And now what I need to do is create this index.html file
and make it use this tasks variable somehow.
How might I to go about doing that?
Well, I can go back into my tasks directory,
create a new folder for my templates.
So I'll create a templates directory, inside of which is a tasks directory,
because the template that I'm rendering, again, is tasks/index.html.
And inside of tasks, I'll create a new file called index.html, inside
of which we'll include some HTML.
I'll include just the standard starting HTML.
The title will be Tasks.
And now we'll have the body of the HTML page,
that I want to display all the tasks inside of an unordered list, perhaps.
And if you recall from HTML, to create an unordered list,
it looks a little something like this. ul starts the unordered list.
And each list item is an li tag, like item one, and then item two,
and then item three.
Something like that gives me an unordered list
that has three elements inside of it.
But I don't want to do this now, because I don't want to hard code
or exactly specify what all of the tasks are going to be.
What I really want to do is loop over this tasks variable
here, looping over all of the tasks inside of it
and creating a list item for each one of those tasks.
And just as in before where we could use if inside of curly braces and percent
signs to use a condition, likewise we can
use for to say something like, for task in tasks,
I would like to display a list item.
And then using these double curly braces, I'm saying plug in the task
here.
In between these list item tasks, I would
like to plug in whatever the value of the task variable is.
And then I'll go ahead and end the for loop there.
So what I've done here is, rather than add a condition into a template,
which we've already seen, I now have the ability
to add a loop into an HTML template using
Django to say that, for task in tasks, I'd
like to loop over all of the elements inside of this sequence called tasks.
And for each one of those individual tasks,
I would like to display a list item in HTML that includes whatever
the value of the task happens to be.
An endfor, just like endif in the if statement,
endfor is going to end the for loop.
So this syntax now, rather than give me exactly the same number
of list items every time with exactly the same content, will be dynamic.
Whatever the value of tasks is, we will now see on this page
each one as a list item.
And we can test this right now if actually run this application
by going to python manage.py runserver.
I'll go ahead and go to this URL.
The default URL has no page, but if I go to /tasks, well,
then here is what I see.
I see an unordered list that has foo, bar, and baz, each one as a list item.
And if I view the page source to see what the actual HTML is,
here is what I see.
I only wrote one li tag inside of my template,
but because I put it inside of a for loop
that iterates over each of the tasks, what I ultimately get in the HTML that
comes back to the user is one list item for each of the elements
that was originally inside of that list.
So I now have the ability to loop over a list
in order to generate that list of tasks.
Of course, there is still no way to modify any of these tasks.
What we have here is just a fixed list of tasks
that just so happens to be rendering using a loop.
What I'd maybe like is another page that will
allow me to add in new tasks, a form, effectively,
that lets me type in a new task and press add in order to add a new task.
So let's do that.
Inside of views.py, I'll define a new function that I'm going to call add.
And would the add function is going to do is render tasks/add.html.
In order to know when to run the view, I need to give this view a URL.
So I go into urls.py and add a new path.
When I go to the add route, I want to go to the views.add function.
And I'll call this function add, for example.
And so now when I go to /task/add, it's going to call the function inside
of views.
And inside of views here, it's going to run
the function that will render add.html.
So let's go ahead and now write add.html.
We'll go into templates.
I'll create a new file called add.html.
And you know what?
Add.html syntax is very similar to the syntax from index.html
in terms of what the HTML content is.
So I'll go ahead and just copy this whole page from index.html,
paste it into add.html.
The only thing that's different is the body
of the page, where instead of an unordered list that
displays all the tasks, I'd instead like a form, where that form has
an input whose type is text--
and maybe this input field has a name called task such
that I can access that input data later--
and an input whose type is submit, for example.
And maybe I'll give it a big heading at the top that says,
like, add task, for example.
So now I have a new route that adds a task.
So my default /tasks route just displays a bulleted list of all the tasks.
And if I go to /tasks/add, here now is my form that gives me a place where I
can type in a task, press Submit.
That right now does nothing, but that ultimately, I hope,
will actually add a new task.
Of course, something I just did there should
strike us as not the best design.
In particular, the decision I made was because the general structure
of this HTML page is similar--
it's got the HTML tag.
It's got a head.
The title of the page is Tasks.
Ultimately I just copied over the content of index.html
and pasted it into this new HTML page add.html.
And any time you find yourself copy pasting,
this should be another area where you start
to think there is probably a better way to do this.
And with just pure HTML, there kind of wasn't.
If we wanted in multiple different HTML pages that displayed similar content,
we needed the same HTML on all those different pages.
But now in the world of Django, we have the ability
to use something called template inheritance.
What I'm now going to do is define an HTML file called the layout, just
some file that the other files add.html and index.html
are going to inherit from.
They're going to inherit from my layout all of the structure of the page that's
the same on both of the pages.
And all I then need to write is what differs between the pages.
And as I mentioned before, the only thing
that differs between add.html and index.html
is the content of the body of the page over here.
So what can I now do?
Well, I will create a new file inside of my template/tasks directory called
layout.html.
And layout is going to have the basic layout that
is common to both of these pages.
I have a title whose title is Tasks.
I have the body of the page.
And maybe there is more in common with both of the pages
as well that I could add.
But right here, this, in between the body tags,
this is the body of the page that is going to change
between each of the different pages.
So to denote this inside of it in layout in Django,
I'll again use the curly brace and percent signs.
I'll call this a block.
And then I'll give this block a name.
I'll call it body, just because it's the body the page,
but I could give it any name.
And then I'll add an end the block at the bottom here.
And so what I've now said inside of this layout file
is that this layout file has a body inside of this structure of the page.
And inside the body is this block called body.
And what I'm saying here is this block might
change depending on which file we're using, add.html or index.html.
The rest of the structure won't change, but the content of this block,
this block called body, might change.
And so now what I can do inside of index.html and add.html is,
instead of including all of this logic, I can get rid of everything
other than the key part of the page that's
going to be different about index.html.
The only thing different about index.html is this unordered list.
And what I will now included the top of index.html is I'll say that this HTML
page extends tasks/layout.html.
So this is now this idea of template inheritance.
I'm inheriting from the layout.html template,
basically saying use the layout.html template, except inside of block body,
I would like to include all of this content.
And maybe I'll give it a h1 that just says Tasks just as a title as well.
And so now what index.html is saying is, rather than need
to include all of that HTML, all I need to say
is this HTML file is based on the layout.html file,
but the difference is that inside the body of the page,
it's going to be this particular content here.
And for add.html, I can do exactly the same thing.
I can just add this line, extends tasks/layout.html to the top
of add.html.
And then I can get rid of all of this boilerplate code,
so to speak, and just include the part of the page that
differs inside the body of the page.
And so it seems like we've done a fair bit more work for just these two pages.
But if you imagine more complex websites,
and they have dozens or hundreds of different pages,
the ability to factor out the HTML the pages have in common
can definitely be very helpful just for good design,
to be able to make sure we're not repeating ourselves.
And if we ever need to change the structure,
rather than change it in dozens or hundreds of different places,
we just change it in one place inside of the layout file.
And the result of that is that it's going
to change in each of the pages that inherit from that page as well.
And we can test this by just going back to /task/add, which looks fine.
And back to tasks looks fine too.
Both of these pages now are inheriting from that basic layout.
Now, it's a little annoying that anytime I want to switch between this page
and the add page, I have to go to the URL and know that I need to go
to /tasks/add in order to get back and forth between them.
So I might like to add a link that takes me from one page to the other and vice
versa.
And I can do that.
if I go into index.html, you might imagine
that I could just add a link here, a href to create a link.
Let's go to /tasks/add.
And, like, Add a New Task would be the name of that link.
Except the problem is or the reason why this isn't necessarily good
design is that Django is designed to make it such
that it's very easy to change the structure of the pages
in terms of how the URLs all relate to each other.
And I have here hardcoded that, when you click this link, we go to /tasks/add.
And if ever I wanted to change that URL--
maybe instead of /tasks/add, I wanted to /new instead of /add--
well, then I need to change it in two places.
I'd need to go back to the urls.py file to change
the actual URL to say, instead of add, it should be called new.
But then I'd need to find every place where I use that URL
and I'd need to change it there as well.
So in order to deal with this, Django has an additional feature
that basically lets Django figure out what the URL should be instead.
And we do that by using the name that we gave to each of those routes.
And so this is where that name becomes relevant, that I can hear just
say, link to a particular URL, link to the URL called add.
So I just said, link to URL called add.
And how Django figures this out is based on the contents of my urls.py,
that inside my urls.py file, I defined a number of different paths.
And I gave each of those paths a name.
This one was called index.
This one was called add.
And so when I did that, I was able to say, if you link to a URL called add,
Django will find a URL whose name is add and link me directly to that route.
And so if ever I were to change the route to something else,
Django would just figure out what the new URL should be.
And I wouldn't have to worry about it.
Django would fix the problems for me.
And so now if I go back to the tasks site,
I can click the Add a New Task button.
And that will take me to the add task.
And maybe now I'd like to add a link that goes back.
So I can go to add.html and maybe add a link down at the bottom, a href equals.
And what URL would I like to link to?
Well, my default page was just called index.
So I'll go ahead include the word index--
I'd like to link to the URL index--
and then view tasks, maybe, as the name of the link.
So now if I go to just the default tasks page,
I see a link to go Add a New Task.
And now I see a link that's going to take me
back to the ability to view tasks.
And when I click on that link, I see "NO."
And now, that's probably not what I wanted.
I wanted to go back to the index page for my tasks application,
but it seems that when I clicked on View Tasks, I'm taken to "NO."
And what's going on here?
Well, if you look at the URL, the URL is /newyear.
Somehow I was on the tasks app.
I clicked a link.
And I'm back at the newyear app.
How did that happen?
Well, it turns out this is an example of a namespace collision, where I have
two things that all have the same name.
And in this case, what's happening is that I
have a urls.py file for my tasks application
where I have a route called add and a route called index.
But it just so happens that inside of the newyear folder,
if I go up to newyear and I look at newyear's urls.py file,
the newyear's urls.py file also has a path whose name is index.
And so what I said was, create a link.
And I would like that link to link to the thing that
has the URL with a name of index.
And it turns out there were multiple things that all had a name of index.
And so Django didn't know which to choose.
And it just chose the newyear one.
And you might imagine that linking between apps
is something you might reasonably want to do.
You want to, from the Amazon shopping page, for example,
be able to click a link that takes you to Amazon Video.
Or you want, from Google search, to be able to click
a button that gets you to Google Maps.
But in this case, this isn't quite what I wanted.
There is a namespace collision where two things have the same name that I
would now like to be able to fix.
And the easy way to fix this is inside of urls.py for my tasks application,
let me just give each of these URLs an app_name called tasks.
This just helps uniquely identify all of the URLs,
because now inside of add.html, rather than just link to a URL whose name is
index, I'm going to instead link to tasks:index, to mean, from tasks,
the tasks app, get the index URL.
And likewise, inside of index.html, I'll link to tasks:add to get at that
particular route from this particular application name.
So now if I go back to the site, go back to my tasks page,
now the links work as expected.
I can get to the new tasks.
And I can get back to the list of all of my tasks as well.
So this now works.
I now have these two pages, one that displays my list, one that
displays my ability to add a new task.
But the form to add a new task doesn't really do anything right now.
I type in a task.
I type in, like, foo, if I want to add a task called foo.
And I press Submit.
And like, nothing happens.
Nothing changes meaningfully on this site.
So I'd like for this form to do something.
And we've seen that we can add an action to a form to be able to take that form
and submit it somewhere.
And that's what I'd like to do when I add a new task.
I'm going to add an action to this form.
And when I submit the form, what URL would l like to submit it to?
Well, I'll go ahead and submit it back to the URL for tasks:add.
I'll send it back to that add URL when I submit the form.
And I'm going to give this form a specific request method.
Its method is going to be post.
And so we've already seen a request method of get.
Anytime you type in a URL or click on a link to go to another page,
the request method implicitly associated with that request
is called get, which just means I would like to get a particular page.
Anytime you're submitting data that has the potential
to change some state inside the application
like changing the state of the list of tasks that
is stored inside the application, then we'll generally use a different request
method called post.
Post is generally used for submitting form data.
It doesn't include parameters inside the URL
the way a get request does, as we saw with Google, for example.
But this post ability is going to give us
the ability now to send data via a different request method
to my add route.
And so let's now give this a try.
Now I'm going to go to task/add.
I see the ability to add a task.
And maybe I'll add a task like check email or something and press Submit.
And all right, I get an error, forbidden.
This 403 in parentheses means that is the response code that came back.
This is an error that Django has generated for me.
So this 403, as we saw before, means forbidden.
I don't have permission to do this for some reason.
Now, why don't I have permission to submit this form?
It says, CSRF verification failed.
So CSRF stands for Cross-Site Request Forgery.
And what that means, it is a security vulnerability
that is inherent in some forms if they're not designed in a secure way,
meaning that someone could forge a request to a particular website using
some form on their own separate website, for example.
You might imagine that someone on a different website
might trick the user into submitting a form that
submits its data to our add task function that
adds a new task to their task list.
And maybe that's not a big deal for tasks,
but you might imagine in more secure context, more sensitive context
like a bank, for example, they might have
a form on their website for transferring money from one user to another.
And if they're vulnerable to this sort of attack,
cross-site request forgery, someone else on a different website
could trick the user into submitting a form where it submits form data.
And it goes to the bank's website to say,
I would like to transfer money from this one user to another user.
So we would like to be able to design forms that are not
vulnerable to that particular security vulnerability, that
don't allow for requests to be forged by another website.
So how can we go about doing this?
Well, one strategy that can be used in order
to deal with these sorts of attacks is to add into our form
a hidden Cross-Site Request Forgery token, or CSRF token,
which would just be some unique token that's generated for every session.
So every time a different user visits this particular form,
they see a different CSRF token.
And then when the user submits the form, they're
submitting that token with the form.
And our web application is going to check to make sure
that that token is indeed valid.
And if it is valid, then they'll allow the form submission to go through.
But this means that an adversary wouldn't
be able to fake a request to our website,
because that adversary doesn't know specifically
what the generated token is so they would
fail the check for CSRF validation.
And it just so happens that Django has CSRF validation turned on by default.
And it's done so via specific add-on known as Django Middleware.
Middleware refers to the ability in Django
to be able to sort of intervene in the request response processing of a Django
request.
And if I look at the settings.py file, if you're curious,
for this particular web application, inside of settings,py,
if we scroll down, we see there's a whole bunch of middleware
that's installed by default inside of a Django application in terms of making
sure that we have various different features hooked into this request
response processing.
And one of those is the CSRF view middleware, this feature of Django
that allows it to make sure that our requests,
whenever we're submitting data via post, something that has the potential
to change the state of the application in some way,
that we need to have CSRF validation.
We need to add some sort of token to our form
to make sure that Django is able to authenticate the validity of this form
to make sure they know the form actually came from the web application itself.
And it's quite easy to be able to add this token into our HTML page.
Django has it built in.
Inside the curly brace and percent sign, we can just say,
I would like to add into this page the CSRF
token, for example, to go ahead and fill it in the CSRF token right there.
If I now go back to the page and refresh the page, now I see add task.
But if we're curious, I can actually go and view the page source.
I can look inside this page.
And here now is the form.
And this is the form same as we saw before,
but you'll notice that Django has inserted this additional input
field, this input whose type is hidden, meaning
we won't be able to see it normally, whose name is CSRF middleware token.
And here is its value, some long string of characters
that Django has generated for me, such that when I submit this form,
it's going to check to make sure this token is valid.
And if it doesn't find this token, it is not going to accept my form submission.
And if someone else goes to this website,
they are going to see a different token presented to them as well.
And that helps to make sure that nobody can forge these sorts of requests.
So now if I type in a task that I like to add , something like check email,
and press Submit, now the form does submit without errors.
Of course, it doesn't do anything.
If I go back to my task list, it's still empty.
But at least I've now been able to submit that form.
It's worth noting that inside of add.html,
we created this form sort of from scratch.
I created an input field whose type is text and whose name is task.
But creating forms is something that happens
so often in the world of web programming, oftentimes
with many different fields that you might want to change over time,
but Django has added a number of ways to make it easier to create forms,
to validate the data inside of those forms
just to make our lives a little bit easier when it comes to dealing
with and interacting with forms.
And so now we'll explore an alternative way of doing the same thing.
What we did here just works.
We can create a form just using raw HTML, as we've seen before.
But Django also has the ability to create forms for us.
So in order to do this, I'll go into views.py and at the top,
add from django import forms.
And now I'm going to create a new class to represent this form.
I'll create a Python class that I'll just call NewTaskForm,
since I'll use it to create a new task.
It will inherit from forms.form.
And now inside of this class, I need to define
all of the fields I would like for this form to have, all of the inputs
that I would like the user to provide.
And so I want them to provide the name of a task which
will be a character field, or a CharField,
meaning I want the user to type in characters.
And I can give this a label, call it New Task, for example.
And now what I can do is, when I render add.html, I can add some context
and say, give this template access to a variable called form which
will just be a new task form.
So I'm going to create a new task form, pass it into this add.html template.
And now inside of add.html, instead of writing the input whose type is text,
name is task, and having to do that on my own,
I can just use double curly braces and say, plug in the form here.
And that will automatically take care of--
Django will generate the necessary HTML to make that form work.
So if I refresh this page, now I see that here is the form
that Django has created for me.
It's created an input field.
It's given it a label of New Task, so I know that it's where in new tasks
should go.
But now rather than needing to edit the HTML anytime
I want to change the form data that's involved inside of this application,
I can just change this new task form.
If maybe I want to eventually make upgrades to my application,
where in addition to specifying a text field where I can type in the new task,
maybe I also want to be able to specify a number indicating the priority
that task should have, I could additionally give this form access
to a priority variable, which is an integer field whose label is priority.
And I can even add constraints on this.
To be able to ensure that it's valid data,
I can give it a min value of maybe 1 and a max value of 10, for example,
in order to add all of that.
And now without touching anything in my HTML,
I just changed the form itself inside of my Python code,
now if I refresh the page, I see an additional field.
I see an opportunity for me to type in a new task.
And I see an opportunity for me to specify some priority.
And of course, you could add CSS in order to style this up
a little bit nicer.
But now Django will automatically do client-side validation,
where if I type in a new task like check email but I don't specify a priority,
and submit it, it tells me to fill it out.
If I type in a number that's in an invalid range--
I only wanted numbers from 0 to 10--
it fills it out.
And this is all what we would call, again, client-side validation.
The server isn't getting any of this data.
It's just the web page has been encoded to know what the valid values are.
And it's going to constrain me to make sure
that I'm typing in something that matches those values.
But in general, when we're doing form validation, when we're making sure
that forms are valid data, we want to somehow
make sure to include not only client-side validation, but also
server-side validation.
We also want to check on the server to make sure that inputs are valid
as well, because there are many reasons why
we might want to be able to do this.
One is that it's very easy to disable this sort of client-side validation,
or just submit a request without doing any of the client-side validation.
And maybe if the user is looking at an old version of the page,
the validation we're doing on the server is more up to date
than this client-side validation as well.
And Django's forms will make it very easy for us to do both of these things,
both the client-side validation and the server-side validation as well.
So how does this work?
How do we do that?
Well, inside of the add function, this add
function now gets called in two different ways
depending upon the request method.
If I try to get the add page by just clicking on the link for Add a New Task
or going to the URL of /add, then I want to just render a new blank form.
But if I post data to this page by using the post request method instead of get,
that means I'm submitting the form.
And I now want to submit a new task to be added to my list of tasks.
So I'd like to add a check for that.
I'm going add a condition here that says, if request.method equals POST,
well, then here what I'd like to do is process the result of that request.
And the way you do that and a Django form is by creating a new variable
called form which will be a NewTaskForm.
And if I just use NewTaskForm with two parentheses like I did before,
that creates a blank form.
But you can also populate that form with some data.
And if I populate it with request.post, what that is going to do
is request.post contains all of the data that the user
submitted when they submitted the form.
And so what I'm doing now is I'm creating a form
variable by taking all of that data and filling it
into this new task form, which will contain now all of the data
the user submitted.
And you could imagine checking this manually,
but I can just call if form.is_valid, and inside this if statement,
use some logic using the cleaned data as a result of using this form.
And so inside of a variable called form.cleaned_data,
that will give me access to all of the data the user submitted.
And so if I want to get what task they submitted,
because I had a variable here called task inside of NewTaskForm, well,
I'll just go to form.cleaned_data and then task.
And I'll go ahead and save this inside of a variable called task.
And now what I might like to do is add this task to my list of tasks,
go tasks.append this new task.
So that's what we do if the form is valid.
If the form is valid, we take the data from the form, get the task,
save it inside this variable called tasks, and add it to my growing list.
But else if the form is not valid, what should we do instead?
Well, then I should return the add.html file again.
But instead of providing the form back to them, a new form back to them,
I'm going to send back the existing form data back to them.
So we can display information about any errors that might have come up as well.
So what does this now look like?
Let's show an example.
And then I'll go back to the code so you can see in better detail how it works.
Here is task/add.
Recall that if I type in a task like check email
and a priority that's not valid, that's out of range, like 11,
and press Submit, it says, value must be less than or equal to 10.
But let's now imagine a situation where the client and server are
validating different things, that maybe I've now decided, you know what?
Priority, instead of being from 1 to 10, can only be from one to five.
That's now the valid range for priorities.
But this client, which is still an older version of the page, doesn't know this.
So it thinks that a priority of eight is still valid.
And it's going to past client side validation.
But now I press Submit.
And the server is going to process it.
And because it's invalid, it gives me back the form and gives me an error.
Ensure this value is less than or equal to five.
And so this now is why we generally want both client-
and server-side validation, to make sure that the data we ultimately get
is going to be accurate and it's going to be clean,
matching whatever specification we sent out when we were
creating that form for the first time.
And so for the purposes of now, we're not
going to really worry about priority too much, because we just
really care about what the task is.
But just know that if you wanted a form that
had multiple fields, that you can add additional fields to this form input
as well.
So now we've added some application logic
to our route that checks to make sure that the form is valid or not.
If we take a look at what the add function is really doing,
we're checking if the request method is POST,
meaning if the user submitted some form data.
Then we figure out all the data they submitted and save it
inside this form variable.
We check to see if the form is valid.
Did they actually provide a task?
Are they providing all the necessary data in the right format?
If so, then we get the task and add it to the list of tasks.
Otherwise, if the form is not valid, then we
go ahead and render that same add.html file back to them,
but we pass in the form that they submitted so that they
can see all of the errors they made.
They can make modifications to their own form submission if they'd like to.
And then otherwise, meaning if the request method wasn't POST
at all, if the user just tried to get the page rather than submit data to it,
then we're just going to render to them an empty form.
And this sort of paradigm is actually quite common
when we're dealing with requests and responses,
that oftentimes pages that have forms will want you to first
be able to get that form via the GET method,
to just get the page in order to display the contents,
but those routes will also often support a POST method, where you can post data
to those routes in order to say, I would like to now submit data
to a particular route in order to get some sort of results some,
sort of addition to a list of tasks, transferring money
in a bank from one account to another, for example,
or something else entirely.
But watch what happens if I now try inside of /tasks/add to add a new task
that's valid, something like check email, for example.
I press Submit.
And all right, nothing quite seems to happen,
because I just get back this NewTaskForm.
But if I go back to view tasks, now, I see
that check email has been added to my list of tasks,
because the original list of tasks just foo, bar, baz.
And now we've added check email to it.
But this wasn't quite the behavior that I might have expected.
Maybe I wanted that, after I submitted the task form to add a new task,
I would like to be redirected back to this page as well.
And it turns out Django makes it easy for us to be able to redirect users
from one page to another.
In order to do that, after we add a new task to my list of tasks,
I'm going to return an HttpResponseRedirect
and redirect the user to a particular route.
I could redirect them to just, like, /tasks, for example.
But again, we generally try not to hardcode URLs into our application.
So better design would be to say, let me give you the name of the route
and go ahead and reverse engineer what the route actually is from that.
And so in order to do that, we can use the function called reverse built
into Django, and say tasks:index, to say figure out what the URL of the index
URL for the tasks app is.
And use that URL as the one that we ultimately
redirect to when we return this HttpResponseRedirect.
In order to use these, we need to import both of them.
So from the top, I'll say, from django.http import
HttpResponseRedirect.
And from Django.urls, I'll go an import reverse.
So now I've imported both of these.
And now the effect of this is that, after I submit a new task
and add it to my list of tasks, I'm going
to be redirected back to the index page of my tasks application.
And for good measure, I'll go ahead and start us off with an empty list
to get rid of the foo, bar, baz that we saw there originally.
So tasks start out as the empty list.
Now refresh the page.
I see no tasks in here by default. But if I
add a new task, I type in something like check email, press Submit,
I now see that added to the task list.
And I get redirected back to the task page.
I can add new task, something like do laundry, press Submit.
And that gets added to my task as well too.
So by maintaining was global variable called tasks
and updating it anytime I submit the form,
I've been able to dynamically grow this list of tasks inside of my application
and display all of those tasks here inside of my HTML page.
However, there is still one big problem with the application that I've built.
And it goes back to the idea that I've stored these tasks
inside of a global variable.
This variable is something the entire application has access to,
which means that anyone who visits my website
is going to be able to see the same exact list of tasks.
And we can simulate this by imagining someone else
visiting this URL, which I can simulate in Google Chrome
by opening an incognito window to simulate
a different session, a different person interacting with the page,
and going to the same URL.
What they see when, any incognito window, they go to the same URL,
is they see the exact same list of tasks.
Both me and another person see the same list, because there is just one tasks
variable across the entire application that
is shared among all of the requests that come in to that particular application.
And that's probably not what I want when I'm dealing
with something like a list of tasks.
I probably want it to be per user, such that if a different user visits
the page, they have their own list of tasks as well.
And so in order to do this, we'll introduce
the concept of sessions in Django, or in the web more
generally, where sessions are away way for,
one, Django to be able to remember who you are, such
that on subsequent visits, it remembers who you are and knows who you are.
But more importantly, it's able to then store
data about your particular session.
It's able to store your user ID or information about you.
Or in this case, it's able to store all of your tasks.
And so in order to take advantage of sessions,
instead of having a global variable called tasks,
we're going to go ahead and delete this and instead store
tasks inside of the user's session.
So inside the index route, I'll include a line like this.
I'll check to see if tasks is not in request.session,
meaning if I look inside the session-- and you can think of the session
as like a big dictionary representing all the data we have on file inside
the session about the user--
and if tasks is not in that session, well,
let me add to request.session tasks and set that equal to the empty list.
And so what I've done here is I'm looking inside of the session.
I'm looking inside the session to see is there already
a list of tasks in that session.
And if there isn't, if there isn't already a list of tasks in the session,
well, then I'd like to create it.
Then I'd like to set request.session square bracket
tasks equal to the empty list.
If the user doesn't already have a list of tasks,
go ahead and give them an empty list of tasks.
And now here in tasks, instead of rendering the variable tasks which
no longer exists, I'll render request.session tasks
to pass in that list of tasks to this particular template.
And now, index.html, we're still looping over that list of tasks.
And now we'll see if I go back to Tasks, I see, no such table django_session.
So this is a bit of a strange error.
What's going on here, no such table django_session?
Well it turns out, as we'll see in the future,
Django tends to store data inside of tables.
And we haven't yet gotten to what that ultimately means
or how to manipulate or interact with data stored inside of tables.
But Django stores data about sessions that would happen inside of a table
by default. And you can change to have Django store data about sessions
elsewhere.
But ultimately, Django is keeping data about who you are
and what your tasks are.
And that data needs to be stored somewhere.
And by default, Django wants to store it inside of a table.
And right now that table doesn't exist.
So we need to create it.
And the way to give Django access to that table that it wants to create,
that it has been waiting to create, but it hasn't yet,
is to run this command inside the terminal, python manage.py migrate.
And we'll learn more about what that means in terms of what a migration is
and what it means to migrate data into a database.
But for now, just know that python manage.py migrate
will allow us to create all of the default tables
inside of Django's database.
Next time we'll take a look at actually creating databases of our own,
and adding tables of our own to store our own custom data.
But Django has some initial default tables that it wants to create.
And running python manage.py migrate allows for those tables to be created.
Now that those exist, I first need to run the server, so python
manage.py runserver.
I load the page.
And here now is my list of tasks.
There is nothing here, so when I loop over all the list items,
it's sort of empty, which maybe isn't the best user
experience or user design.
So what I might want to do is add an additional condition.
It turns out inside of index.html, whenever
I have a for loop inside of a Django template,
I can also add an empty condition to say, if I run the for loop
but it doesn't run at all because the sequence is empty, well,
then let me just say, like, no tasks, for example.
And this is just a nice to have feature of the Django
language that just makes it easy for us to be able to deal with situations
where we're iterating over a list and there is nothing in that list.
So now I refresh the page, No tasks, exactly what I would expect to see.
And now this index route seems to be working fine.
What now needs to change when it comes to adding a new task?
Well, rather than append the task to my list of tasks, let me go ahead
and say, request.session tasks, because that is my list of tasks.
And let me add to request.session tasks this new task.
And I'm adding to it a new-- sort of adding
a list to the end of it that just contains the one new task
that I got from the form.
So when the form is submitted, we check to make sure the form is valid.
We get the task that the user submitted.
And we append that to the list of tasks that's
already stored inside of the session before redirecting the user back
to the index page.
So let's give it a try.
We'll go back to this URL, back to Tasks.
I see I have no tasks initially.
And now I can go ahead and add a new task, type in something
like check email, press Submit.
And right, now that's added.
Add our new task, something like do laundry, Submit.
And now both of those tasks are there.
But now, importantly, if you imagine someone in an incognito window
or on a different computer visiting that same website going back to this page,
what they see is an entirely different list of tasks
because they have a different session.
Their sessions are determined by cookies,
these little hand stamps that help the browser to be
able to give some information to Django's web server to say, here
is who I am, so Django knows what data to show you.
And in this case, my original case, Django
knows who I am, knows to show me these tasks.
And in this case, it's a different user in an incognito window, in this case.
And so what they see is a list that has no tasks inside it at all.
So now we've really just scratched the surface of what Django has to offer.
But we see now the ability it has to be able to create dynamic web
applications.
Instead of just displaying HTML and CSS that's the same every time,
using Django, we now have the ability to be
able to generate programmatically custom HTML and CSS, either saying hello
to a person's name based on what name is provided inside of URL,
or the ability to say-- to check the current date
and conditionally display something if the date is one thing versus another,
and the ability to store data on a session basis,
to be able to store information about a user's to do list,
for example, such that on subsequent visits,
they can see their list of things they need
to do with a different list for each of these possible users.
And here really is just the beginning of where Django has to offer.
Where Django gets very powerful is when it comes towards storing data inside
of databases, and manipulating that data,
and interacting with that data in various different ways.
And that's ultimately where a lot of the power of a web framework like this
ultimately comes in.
We'll explore more of that next time.
But for now, that was just a look at Django
and how we can use it to be able to build these sorts of web applications.
This was "Web Programming with Python and JavaScript."
We'll see you next time.