04. Django
04. Django
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.
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.