BRIAN YU: Using HTML, CSS, and JavaScript,
we were able to create web pages that users
could view by serving them using HTTP server, a simple server that
just listened to prefer requests and sent back those web pages as responses.
But those responses were always what we might call static content.
Every time HTTP server sent back an HTML page,
the actual HTML content of the page app was always the same.
But in practice, modern web applications are often
sending back dynamic content, content that's changing.
Social media sites like Facebook and Twitter
are always sending back HTML that contains for the users their latest
posts and their latest news feed.
And news websites are always sending back
HTML that contain the current day's news articles, for example.
And so how do we create something like that?
Well, to do that we're going to need to write our own web server,
our own application that is listening for web requests
and responding to them so that we can decide what HTML we want to send,
and we can send dynamically generated HTML
based on the data that's currently stored inside of our web application.
To do that here, we're going to use Flask,
a Python based framework that makes it very easy to set up and create a web
server that's going to listen for requests and respond to those requests
as well.
So let's go ahead and create our very first Flask web application.
I'll go into CS50 IDE, and I'll create a new folder
where I'm going to store all the files for this web application
that I'm about to create.
And I'll call the folder Hello, which will
be the name of this particular application.
And inside of Hello, I'll create a new file
that I'm going to call application.py.
And this is just a Python convention and Flask
that when you have a Flask application, it is often served out
of a file called application.py.
What am I going to put inside of application.py?
Well, the first thing I'll need to do is import the Flask module.
So from Flask, I'm going to import Flask.
Next, I'm going to create a variable that's going
to represent my Flask application.
So I'll say app equals Flask, and then __name just means that I'm going to be
serving this flask application from this file that I'm writing right now.
And now what I'm going to include in application.py are routes.
And routes are just what you might type at the end of a URL
in order to decide what page you want to visit.
So on Google, for instance, you might be visiting /search or /settings or /maps,
for example, to get to various different pages.
Likewise, our web application can respond
based on what route the user is trying to access.
So we'll go ahead and say at app.route and then slash, where slash is here
just going to be the default route.
When you just visit the web page, this is the route that's going to run.
And every route in Flask is associated with a.
Function I'll go ahead and call this function index.
And I'm going to return from this function hello world.
And that's it.
With just seven lines of code, I've now been
able to write a Flask web application.
And the gist of the idea is that line five,
I'm saying, when you go to the slash route on this web application,
you should call this function on line six, index,
though it could have been called anything you want.
And what does the function do?
Well, it's going to return the content that should be responded to the user.
And so in this case, when the user visits slash,
I would like to send the message hello world to the user.
How do I now run this web application?
Well, inside my terminal I'll CD into my Hello directory
to go into the Hello directory.
And now to run my Flask web application, I
can just type Flask space run to run this Flask web application.
It's running on this particular URL.
So if I click on that and go ahead and click Open, what I'll get now
is a web page that here just displays the words hello world.
So that was it.
When the user visited slash on the web application,
the index function was called inside of Flask,
and the index function returned hello world,
and so that's why what the user saw when they went to the web page
were the words hello world.
Let's make our web application a little bit more interesting, though,
and add another route to the web application.
So here I'll add another route.
App.route.
This time, instead of just slash, which is the default route, I'll do /goodbye,
for example.
What should happen when the user visits /goodbye?
Well, here we'll say we're going to call another function that I'll call bye.
But again, you could call it anything.
And the bye function is just going to return goodbye exclamation point.
So now I have two routes.
A default route accessible by just going to slash on my web page
and getting hello world as the response.
And then if I go to /goodbye on my web application, I call the bye function,
which returns the word goodbye.
So we can try it.
If I go back to my web application and just refresh,
I'm still on the default route.
So what I see are still the words hello world.
But if I go up now to the URL bar and instead of going to the default route
of just slash or nothing there, if I instead type /goodbye, for example,
what I now see is the word goodbye.
That was what was located when I went to the /goodbye route.
It called the bye function, and that returned this content
that I now see on this web page.
And it turns out that what I return can be any arbitrary HTML that I want.
Instead of just returning hello world, for example,
you might imagine surrounding it in H1 tags
so that it displays hello world as a bigger, bolder heading.
So if I go back now to just the slash route, the default route on my web
application, I now see hello world in bigger, bolder letters
as a big heading at the top of this particular web page.
But as you might imagine, the HTML I'm returning
might be a little more complex than just one big heading at the top.
I might have beneath that more information, other paragraphs,
other images, other HTML content as well.
And if I had to type all of the HTML content
that I wanted to return when the user visits the slash route
inside of this string right here, that's going to eventually start
to get pretty tedious and pretty messy.
And so what Flask allows us to do is separate some of this information
out into other files, in particular into what are called the template files.
How does this work?
Well, inside of the Hello folder, I'll go ahead
and create a new folder that needs to be called Templates.
And this is where Flask is going to look for template files.
Inside of Templates, I'll create a new file called index.html.
And inside of index.html, I'll just include some HTML content.
Doc type HTML, HTML, inside of which is the header
section of the title that says hello and then
a body section that says hello world.
All right, so now inside of application.py,
rather than returning this string of just H1 hello world end H1,
I can now return a template.
In order to do that up at the top from Flask,
in addition to just importing the name Flask,
I'll also need to import a Flask specific function called
render template.
And I'm going to call that from the index function.
I'm going to return render template.
And the argument to the render template function is the name of the HTML file
that I would like to render in this case.
In this case, I want to render index.html.
All right, so now when the user visits the slash route on my web page,
it's going to run the index function.
And what the index function is going to do
is it's going to render a template that's called index.html.
So if I go back to my web application, refresh the page,
I now see this index.html file, which has a body of hello world
and also has a title of hello, which I see at the very top.
OK, so we've now been able to create a Flask web application that
is able to handle routes and when a user visits a particular route,
it's able to return to them some HTML content.
But of course, this HTML content is still
what we might call static content.
It's always the same HTML all the time.
So now let's leverage the fact that we've written this web application
in Python, a programming language that allows for constructs like variables
and loops and conditions, to be able to dynamically generate
some more interesting content.
So how might this work?
Well, let's go back to application.py, where here you'll
recall that when a user visits the slash route,
we call the index function that returns a template called index.html.
But it turns out that this render template function also has the ability
to pass in variables to our HTML files.
And previously, our HTML files haven't been able to deal with variables.
They've just had static content and tags and elements
and information within those elements.
So here I might be able to pass the index.html some additional values
of variables where I might same comma name equals,
let's say, Emma in this case.
Where here I'm going to give index.html access to a variable
called name, which in this case is going to be equal to Emma.
What can I then do with that?
Well, inside of index.html, instead of saying hello world,
I'd like to say hello to Emma.
And I'd like to effectively plug in the value of the name variable
right here in the body of my web page.
To do that, what I'll use is a syntax of double curly braces, name, and then
double curly braces.
This syntax is technically a syntax called
Jinja, which is the templating language that Flask uses in order to display
information and render these templates.
But here effectively all I've done is inside of application.py,
I've said render the index.html file and give index.html
this template, access to a variable called name which is equal to Emma.
And inside of index.html, I'm now saying hello
to whatever that name happens to be using double curly braces to say plug
in the value of the name variable here.
I'll go ahead and restart my Flask server by running Flask run.
And if I go back and refresh the page, you'll
notice that now my page says hello Emma.
It took the value of the Python variable name
and it substituted it in to my index.html template.
So this gives us access to a lot of very powerful features.
So one feature of Python, for example, is
that Python has the ability to generate random numbers.
So let's say that I wanted a variable number to be equal to a random number.
So I'll first the top of my file import random,
which is a Python module that allows me to generate random numbers.
And I'll set number equal to random.randint 1 comma 10,
which will generate a pseudo random number between 1 and 10 inclusive.
So 1 or 2 or 3 or 4, 5, 6, 7, 8, 9, or 10 pseudo randomly.
And now what I'd like to do is pass in this variable into index.html.
How might I do that?
Well, instead of the variable being called name,
I'll go ahead and call it number.
And it needs to be equal to, well, in this case, it's still called number.
So number equals number, which is a little confusing.
But to be clear, this number on the right hand
side represents the Python value that I want to pass into the template, which
used to be Emma the string, and in this case,
is the variable number, which I defined here on line nine.
The number here on the left side of the equal sign is the name of the variable
that the template should have access to inside of index.html.
And it just so happens that in this case, both the name of the Python
variable and the name of the variable I want the template to have access to
are both called number, but there will be situations where these two don't
necessarily have the same name.
So inside of index.html now, rather than say hello name,
I can say something like your random number is and then in double curly
braces number, providing whatever pseudo random number was
generated by my application.py file.
I'll go ahead and reload my Flask server.
I can press Control C to stop the web server
and then Flask run again to run it again.
And now if I refresh the page, your random number is 7.
And if I refresh the page again, your random number is 1.
If I refresh again, your random number is 8.
Every time I run this application, it's generating a pseudo random number
between 1 and 10 and then displaying that content to me
inside of this HTML page.
And if I view the source code of this HTML page
by going to View, Developer, View Source,
I can actually see here is the HTML that is getting returned back to me.
Doc type HTML, HTML, this is all HTML that I wrote,
but your random number is 8.
When flask is running render template, it
sees that I want it to substitute the value of the variable number here.
So it does that substitution.
And then the HTML that gets sent back to the user
is just this new dynamically generated HTML.
I didn't write the number 8 inside of the HTML file,
but flask was able to render a template that
inserted that value where I wanted it to be by specifying
using those double curly braces where I wanted
a placeholder for a particular value.
And so there's a lot that you might be able to do here.
So let's create a program, for example, a web
application that simulates a coin flip, flipping heads or tails, for example.
So how do we simulate a coin flip by flipping heads or tails?
Well, inside of application.py, rather than generating
a random number between 1 and 10 inclusive,
let's just generate a random number between 0 and 1 inclusive.
So 0 for tails, 1 for heads, maybe.
And inside of index.html, if we left things exactly the same
and I were to reload, what I'd see is your random number is 0
or your random number is 0 or your random number is 1
depending on the time.
And so you can rerun this and get various different coin flips.
But what I'd really like to be displayed is something like heads or tails.
So how could I do that?
Well, in addition to specifying variables inside
of our Jinja templates, we can also have things like conditions and loops.
So let's take a look at a condition now.
So double curly braces is the syntax we use to insert the value of a variable
somewhere.
To have a condition, we're going to use curly brace percent
sign as the syntax we use for that.
And inside of the curly brace percent signs,
it's going to be syntax that looks a lot like Python.
I can say something like if number is equal to 1,
then I'll go ahead and display the HTML content.
Your coin flip is heads.
Else if the number isn't 1, the only other option is 0.
So your coin flip is tails.
And then at the bottom, Jinja also requires
me to have an end if, specifying here is the end of the if statement.
And so here I've been able to add conditional logic to my HTML file.
The ability to render different HTML depending upon whether
or not a condition is true.
I'm here saying if the number is equal to 1, the coin flip is heads,
and otherwise the coin flip is tails, and we should just
display one of these lines in the resulting HTML based
on the value of the variable number.
All right, so let's see what this actually
looks like when a user tries to visit this web page.
I'll restart my web server running Flask run.
I'll go back now and refresh the page.
And they're right.
Your coin flip as heads.
Presumably the value of the variable number was equal to 1.
And so the result I got was that my coin flip is heads.
But if I refresh the page, I now see my coin flip as tails,
because it generated a different random number, 0 in this case,
and now it's tails.
And if I keep refreshing, sometimes I'll get tails, sometimes I'll get heads,
and that's going to indicate a difference
in what the value of the number variable was
that determines what HTML ultimately gets sent back from my Flask web
server.
And again, if I view the actual source code here,
I only see your coin flip as heads.
I don't see a your coin flip is tails.
I don't see the if statements that were there inside
of the index.html template.
The render template function handles the process of looking for variables,
looking for conditions, and generating dynamic HTML that
then gets sent back to the user.
Let's now make our web page even more interactive by letting the user type
something in, for example, and being taken
to a page that uses what the user typed in as part of the response that gets
sent back to them.
So inside of index.html, I'll replace what we currently have with a form.
This form is going to have an input field whose
type is text where the user is going to be able to type in their name,
for example.
And we'll also have an input field whose type
is submit that will let the user submit the form when
they're done typing in their name.
Now, in particular what should happen when we submit the form?
Recall that every form can have an action of what route we're
taken to when the form is submitted.
Before we were writing our own web servers,
we could specify the form of action to be google.com/search, for example,
to search for something.
But in this case, we can make the action a route of our own.
And I'll make the route /hello, a route that I haven't created yet,
but the idea of this is going to be that when I submit this form it is going
to submit a request to /hello, and then the /hello route is going to need
to figure out how to deal with that request.
But inside of my Flask application, when my /hello route gets a request from
a user, meaning the user submitted the form,
I would like for inside of my application me to be able to access
what it is the user typed in into the input field.
And in order to do that, when you have a form where you're submitting some data,
you'll generally want to give each of these input fields
an attribute called name that is going to specify a name for the input field
so that you can reference it later.
Here I'll give this input field a name of, in this case, just name,
because that is what I want the user to be typing in.
I want the user to type in their name.
If I instead wanted the user to type in their email, for instance,
I might set the name of the input field equal to email
instead or something else.
But in this case, I'm asking for a name, so I'll
give a name to this input field of name, because that's
what I want the user to type in.
So now let's add to application.py the /hello route that we're going to need
in order to handle that form submission.
So we'll go back here, and I'll remove the random number information
from before.
We're no longer going to be generating any random numbers.
And I'll replace the /goodbye route with the /hello route,
which is the route that we're going to go to when the user submits the form.
I'll call the function hello.
And when the user submits a form using a request
method of GET, which is the default, if I
want to access the parameters of that form submission,
I can use syntax like request.args.get and then whatever input field
I want to access the data for.
Recall that in index.html, this input had
a name of name, which was the name I gave to this particular input field.
And so if I want to now access the user's name, whatever
they typed into the form, well, I'll go ahead and go to request.args.get,
and then I'll specify name as the input field
that I'm trying to get information about.
I'll save that information in a variable called name.
And now let me return render template a new template called hello.html,
for example, passing in to the HTML template a variable called
name, which is set equal to whatever the current value of name happens to be.
So what am I doing here?
When the user submits the form in index.html,
they'll be taken to the /hello route.
What does the /hello route do?
Well, it looks at the request and gets the name argument of the request.
In other words, what it is the user typed in to the name input field
inside of that form.
We're saving that result in a variable called name,
and then we're rendering hello.html and providing that name as a variable
that the hello.html template is going to have access to.
So inside of templates now, I'll need to create a new template.
I already have index.html, but I'm not just rendering index.html.
Here I'm rendering hello.html.
So I'll create a new file called hello.html.
And hello.html is going to look very similar to index.html.
It's still going to have a title of hello.
Except instead of a form, what I'm going to have here is hello comma,
and then I'll plug in the value of the name.
Curly brace, curly brace, name, exclamation point.
So now I have an index.html template that has a form that I can submit that
takes me to /hello.
And when I go to /hello, application.py gets the user's name and then renders
hello.html providing that name as an argument.
And hello.html says hello to that person.
Let's try it out.
I'll run Flask run, which will start up my web server.
And now if I refresh the page, I now see an HTML form,
a place where I can type in my name, and then a place where I can submit
the form in order to go to the /hello route.
So let me try it out.
I'll type in my name, and I'll go ahead and press the Submit button.
All right, internal server error.
Something went wrong.
Let's try and figure it out.
Up in the URL bar, I was taken to the /hello route.
And you'll notice that here too we did include the GET parameters.
Name equals Brian means that name was provided as an argument
to this particular route as a GET parameter.
So where's my server error coming from?
Whenever you get an internal server error,
you can often just look in the terminal section of your IDE
where you were running Flask run to try and see what it is the error was.
And here, all right, I'm getting a name error,
request is not defined in application.py on line 13.
All right, so let's go to application.py and take a look at line 13
and see if we can figure out what went wrong.
I set name equal to request.args.get name,
but it's saying request is not defined.
And all right, it turns out that request happens
to be a feature that Flask gives us access
to in order to give us information about the current request, the current person
who is trying to request a particular page.
But in order to use it, much like I needed
to import render template to be able to use the render template
function in Flask, I also need to import request.
So that was my mistake.
I need to import request as well in order to allow for Flask and Python
to know what request actually is at this point inside of my function.
So I've imported request, and now I'll go back to my web page
or go back to the slash route, just the default route
when I visit my web application for the first time.
I'll type in my name, Brian.
I'll press Submit.
And now I'm taken to a page that says hello Brian.
And it's all dependent upon these URL parameters.
If I change the arguments, if I change the GET parameters
by saying name equals Emma, for example, well, now the page says hello, Emma.
And I can change those GET parameters or resubmit the form if I instead
type David and then press Submit.
Now the page says hello, David.
And it's constantly generating new dynamic HTML,
plugging in the value of name based on whatever it is
the user submitted inside of that form.
All right, so what might go wrong here, and where
is there room for improvement?
Well, if I go back to the main URL, go back
to the default route of just slash, what would happen, for example, if I
didn't type a name into the field and I just pressed the Submit button?
Well, as you might guess, when I press this,
I just get hello comma and then an exclamation point.
All right, it seems like my web application right now
isn't handling very well the situation where no information is provided inside
of the variable that I pass in.
All right, so let's look at application.py.
And here inside of the hello function, I say name equals request.args.getname.
But what I'd like to do is add some additional logic
to handle what should happen if there was no name provided.
And so here I can just use a Python condition.
This is a Python function where I can use any arbitrary
logic that I would like.
And so here I could say something like if not name,
meaning if no name was provided, what should happen?
Well, here I'll go ahead and return a new template, render template.
We'll call it failure.html, just indicating that something went wrong.
I'll create a new template.
New file.
Call it failure.html.
Inside of which it's going to look very much like hello.html, but instead
of saying hello name, I'll instead display a message.
You must provide a name.
If I wanted to, I could even add some CSS styling here.
I could add a style block and say, you know what, for the body,
change the color to red, just to display some red text instead.
But really all I want to do is display some information that says,
you must provide a name here.
All right, so let's give this web application a try.
I'll go back into CS50 IDE and run Flask run to start up my web application.
And now I'll go back to the default route of my web application
where I see a form where I can type something in.
Again, if you do type in an actual name like Emma and press Submit,
you get hello, Emma.
But if I go back now and just leave it blank and press Submit,
now I get taken to an error message that just says, you must provide a name.
And that's all because of the logic that's
happening here in application.py.
I'm able to return different templates depending upon different situations.
I'm accessing the value of the name that was provided as an argument.
And if no name was provided, then I'm rendering a failure.html template.
But otherwise, I'm rendering hello.html, passing in that name as an argument
as well.
So your function doesn't just need to return one template.
It can have conditional logic that determines
that based on certain situations, you should return one template,
but under other situations, you might want
to return a different template instead.
But one thing you might be noticing is that across all of these HTML files,
I'm doing a fair bit of copy pasting.
I copy pasted index.html into hello.html and I copy pasted hello.html
into failure.html.
So it feels like there's room for improvement
here too in order to factor out some of this common code
into just one HTML file.
And in Flask with its templating system built in,
there is a way for us to be able to do that.
So here's what I'm going to do.
Inside of templates, I'm going to create a new template that I'm just
going to call layout.html.
And what I want to include in layout.html is all of the HTML
that's shared in common among all of my HTML files.
So all right, what do I have in layout.html?
Well, I'll have doc type HTML, HTML language equals English,
a header section where I have a title that just says hello,
and a body section.
And important here is that inside of the body section,
this is where information might differ.
But every HTML page has a different body block, so to speak,
where index has a block of information that belongs there,
hello.html has a different block of information that belongs there too.
And so what I'm going to include in layout.html is some syntax like this.
I'm going to in curly braces and percent signs say block body
and then end block.
In other words, there is going to be a block of HTML
called body that is going to be inserted right here in this part of layout.html.
So what does that mean?
Well, it means that inside of index.html,
I can get rid of all of this stuff that isn't really relevant.
The only relevant portion of index.html is the content of the body,
this HTML form that I want to display.
So everything else I can delete and just leave myself left with this form.
And now inside of index.html, what I want to say
is that this index.html file is going to extend layout.html.
And then how is it going to extend layout.html?
Well, it means we're going to include all
of the content from layout.html inside of index.html with the change
that we're going to add something into block body.
And this HTML form is what we're going to put inside of that body block.
So we've extended the basic layout file that
just defines the general structure of the page,
and then we've said inside the body section of that page, insert this form.
All right, so now we can do a similar thing with hello.html.
Instead of just saying hello name, I could instead
extend layout.html and then inside of block body say hello comma name
and then end block.
And we can do a similar thing here too with failure,
where I can get rid of everything other than the body,
extend layout.html, and then inside of block body include the message,
you must provide a name, and then end the block.
So now each of my HTML files is much shorter,
because I'm only providing the information that
is different across each of the HTML files.
I factored out the HTML that's shared in common among all three of these HTML
files into layout.html and put just the unique stuff
inside of each of the individual HTML templates themselves.
So let's try this.
I'll go ahead and rerun the application by using Flask run.
And now if I go back to the default route of my web page,
I still see the same form.
I can type in Emma.
I still see hello, Emma.
But now the advantage here is that if I ever
wanted to change something about this application,
for example, I wanted to change the title that appears
or I wanted to change some styling across the entire application,
I could just change the layout.html file,
and that would change all of the HTML files
that my web application uses, because each and every one of these HTML files
is extending layout.html.
Of course, what you might have noticed is
that now if I leave the input field blank and just click on Submit,
I see you must provide a name, but I've lost something.
I've lost that red styling that I had before where
the message showed up as red as well.
How do I solve that problem?
Well, inside of failure.html, I only have access to this body block.
I don't have the ability to put something
in the head section of the website anymore,
because that's all inside of layout.html.
But you can solve this just by adding a new block to layout.html.
You can create as many blocks as you would
like where you can insert custom code, and those blocks
can have any name that you want.
So up here in the header section of my web page,
I'll go ahead and add a new block.
I'll call it block style, because it's where I'll include the style code,
and then end the block.
And in fact, I can put that block style inside of style tags
as well so that I can just include the style tags once
and then display all the style information inside of it.
So now inside of failure.html, I'll add a block style, inside
of which I'll just say for the body, go ahead and give it a color of red,
for example.
So now if I go ahead and reload my web application
and then reload this error message, you'll
now see that you must provide a name is colored red.
I was able to add a style block to my page
where I was able to display some custom style information that
got rendered as CSS in the page that the user sees
when they go to this particular page.
So this is the power that Flask gives us.
The ability to define the routes that are able to look at information
and then render templates that are dynamically generated.
Different HTML based on the situation, taking advantage of features in Python
like conditional logic and variables and other information as well.
So now let's try and use all of that to build a useful application.
Maybe we want to build an application that keeps track of tasks, like a to do
list, for example.
So let's give that a try.
We'll go back into CS50 IDE.
And let's create a new web application.
Instead of hello, I'll create a new folder
and we'll call it Tasks, inside of which,
again, every Flask application needs a file called application.py
where we're going to include the application
code for what the routes are and what each of those routes should do.
So all right.
From Flask, import flask.
And I'll import render template and request, because I'll probably
end up needing those as well.
I'll define my app, which is a Flask app, same as before.
And now let me add a couple of routes.
I'll add a default route, just slash, which
is going to show me all of my tasks.
For now, I'll just return tasks, but I'll come back to that.
And I also want a route that lets me add a new task, for example.
So app.route, we'll call it /add that lets me add a new task.
Return add a new task.
So far it doesn't actually work, but just want
to give us a test to see how this might work.
I'll go ahead and change directories into the tasks directory.
And now I'll run Flask run to run this application.
And now if I load the page, go into the default route, I see tasks.
And if I go to /add, I see add a new task.
All right, so let's try now to be able to actually implement this application.
The first thing that I'm probably going to need are a couple of templates.
So instead of Tasks, I'll create a new folder that I'll call Templates.
And I'll create two templates, one to show me all of my tasks,
and one template where I can begin to add things.
But just so that they share some HTML in common,
I'll create a new file called layout.html
that is going to have all the HTML that these two
templates are going to have in common.
So doc type, HTML, HTML, head, the title is
going to be tasks, and then the body.
And here I'm going to add a block called body
that I can use to plug in some information
here in each of my individual templates.
So I'll create a new template that all call tasks.html.
And inside of tasks.html, I'm going to extend layout.html
and then just include some custom information inside of block body.
Here I'll just have an H1, a big heading at the top that says Tasks.
And then maybe I want here a list of tasks.
And to create a list in HTML, you can use the UL tag,
UL short for Unordered List, inside of which
you have a whole bunch of LI tags, LI standing for a List Item.
So LI item one, LI item two, LI item three.
So this will create a bulleted list of items for me.
And maybe at the bottom, I want to link to the page
where I can create a new task, for example.
So let me add a link, a href.
Where should this take me?
Well, let's have it take me to /new.
That's the route that lets me create a new task.
And let's say create a new task.
So a button here at the bottom that will basically act as a link that will take
me to /new.
So now tasks instead of just returning the string tasks,
I'll have it return a template.
In particular, tasks.html.
What should add do?
We'll have this render a template as well called add.html.
And here I'll create a new template called add.html.
And add.html will also extend layout.html.
And then inside of block body, what I'd like to do is add some sort of form.
And this form is going to have an input whose type is
text and an input whose type is submit.
All right, so let's take a look at this web application.
I'll restart by running Flask run and see
what this web application actually looks like, at least right now.
So I'll go to the default route of the web application.
All right, so when I visit this page, I see tasks at the top,
and then I see a bulleted list of items.
Item one, item two, item three.
And then down at the bottom, I see a link that lets me create a new task.
Let's try clicking it and see where it takes me.
Create a new task.
All right, not found.
This is one of those 404 errors where I'm trying to request a page,
but it's not there.
So what went wrong?
Well, I'm trying to access the root called /new.
And let's go back to application.py.
And all right, it looks like I just gave it the wrong name.
I tried to redirect to /new, but the actual name of the route is /add.
And so I tried to visit a route that didn't exist.
And when you try to visit a route that doesn't yet exist,
the natural response for the web server is to respond with a 404 not found.
The page you tried to request does not exist.
How do I fix it?
Well, inside of tasks.html, instead of linking to /new,
I'll instead link to /add to be the page where I want to create a new task.
All right, now if I go ahead and reload this web server by running Flask run,
let's try it.
If I go back to the default route, I see a list of tasks in addition
to a Create A New Task link where when I click on that Create A New Task link,
all right, now I'm taken to a page where I can actually
type in the name of a new task and submit it.
Of course, it's not really doing anything just yet,
but let's get to that now.
Let's go back to CS50 IDE and take a look at add.html.
There are a couple of things I'll want to do here.
One is that I might like to signal to the user
that inside of this text field, they should be typing in the name of a task.
So we can add an additional attribute to this input field called placeholder
and just say task name, for example.
Some placeholder that's going to be there by default so that the user knows
when they visit this page what it is that they should be typing in,
for example.
And additionally, just as before, if I want my program
to be able to access these input fields, I can give them a name.
So I'm going to call this input task so that later on on the receiving
end in my application.py file, when I receive a form
submission from add.html, I can look for a form field called task
to get at that information.
So what should my form do in order to actually submit data?
Well, here we can take advantage of a convention,
recalling again that web routes can accept requests using multiple request
methods.
GET is usually the request method we'll use by default when you just
want to get a page.
But there's another request method that's
often used when I want to send data to a particular route,
and that method is called POST.
So GET for just getting information for a page
and POST if I actually want to send data to update my task list, for example.
And so here my form's action I still want to be /add to add a new task.
But the method I'm going to use, which by default is GET,
I'm going to instead say is POST.
I want to use the POST request method to indicate
when I'm making this request that I'm trying to send data to the server.
So all right, how can I leverage that information now inside
of application.py?
Well, first and foremost, by default, routes in Flask
only accept the GET request method.
And what I would like is for /add, this route,
to be able to accept both GET and POST requests.
If I send a GET request to /add, meaning I'd like to get the page,
then I just want to do this.
Return this template.
The display is the form where you can type in a task
that you would like to add.
But if the /add route receives a POST request,
meaning someone submitted data to /add, then instead of displaying a form,
I went to instead actually add that task to my list of tasks.
So how am I going to do that?
Well, first I'm going to specify for this route /add that I should accept
multiple request methods.
And I can do that by adding an argument called methods
that's set equal to a list of the allowed request methods
that I want to support.
By default, it's just GET.
But here I want to say allow both GET and POST requests.
So GET and POST requests can be received by this /add route.
And what we'd like to do is add some logic,
such that if the request is a GET request,
I just display the page that will let the user type in a new task what
they need to do.
And if the request method is a POST request,
meaning they're sending data to /add, then what I want to do is add that task
to their list of things to do.
So let's actually try and write this.
So let's go back to our add function.
And I want to add some conditional logic here
that says that if request.method equals GET, meaning if it's a GET request,
then the user is just trying to get this page,
so I should return the add.html page, the form that
lets the user type anything they need to do and click the Submit button.
When they actually do submit, though, then the request method
is going to be POST.
They're sending data to us.
So here I could say elif request.method equals POST,
but I'm only accepting two request methods.
So if the request method isn't get, it must be POST.
So I'll just say else.
What do I want to do?
Well, where is this task stored?
The thing that I need to do is going to be stored in request.form.get.
request.form.get lets me access data from the form that
was submitted via POST.
And what was the name of that input field?
Well, its name was task.
So all right, inside of application.py, I'll say the thing to do
is request.form.get and then whatever thing had a name of task.
That input field is what's going to be stored inside
of this variable called to do.
And now what I'd like to do is add this to do to a list of things
that I need to do.
So I'll go ahead and create, in this case, just a global variable
in application.py called to dos, which is going to start out just
by being an empty list.
Starting out with no things that I need to do.
But when I submit this form via POST, I'll get the task,
and then I'll say todos.append to do.
I'm adding to the end of the to do list the thing
that I need to do that was just submitted via that form.
But what should happen next?
All right, so I've added the thing to do to this list of to dos.
So what I need to do now?
Well, what I need to do now is I'd really
like to just display the list of tasks that the user needs to do.
In other words, I'd like to just redirect the user back
to this default route which displays their list of tasks, for example.
And I can do that.
Rather than returning a template, I can first import redirect from Flask.
And now here rather than return render template something,
I can return redirect followed by the route
that I would like to redirect the user to, which in this case
is just the slash route.
So all right, I'll go ahead and rerun the server by running Flask run.
And now let's take a look at this.
I'll refresh the page.
And here I see that I have a list of tasks, item one, item two, item three.
Let me create a new task and type in a task name,
something like do the dishes.
I'll press Submit and all right, I'm redirected back to this list of tasks.
But of course, this list of tasks right now isn't actually showing me the task.
It's not showing me do the dishes among the other things that I need to do.
It's right now only showing me item one, item two, item three,
because I literally said inside of tasks.html, what should you show?
An unordered list that has three items, item one, item two, item three.
So let's fix that now.
Let's go ahead and replace instead of all these list items, item one,
item two, item three, let's loop through all of the things that I need to do.
How am I going to do that?
Well inside of application.py, here's my route that renders tasks.html.
And what I would like to do is give tasks.html access
to this list called to dos, which is my list of all the things
that I need to do.
So I'll add to render template tasks.html a variable that task.html
is going to have access to.
I'll call that variable to dos and just set it equal to to dos,
the name of this variable in Python that represents the list of all the things
that I need to do.
Now, inside of tasks.html, let's implement this logic.
Instead of these three list items, item one, item two, item three,
let me actually loop over this list of tasks to display each one one by one.
How do I do that?
Well, in the same way that in this template before we could add conditions
by using curly brace percent sign and then if something,
the syntax will be very similar here where
I can just say for to do in to dos.
Loop over this list of things to do and call each individual item in the list
to do.
I'll have to end it with an end for in the same way
that I ended my if statements with an end if.
And now what should I do for each one of these to dos?
Well, for each one of these to dos, I'd like
to create a list item for it, an LI element.
And the content of that list item should be whatever the to do actually is.
And so here, I'll use curly brace syntax to say, take that to do
and plug it in right here.
So the effect of this is going to be that I'll create an unordered list,
and then I'll loop over each one of the things in my list called to dos,
calling each individual item in that list to do.
And for each of those individual items, we will create a new list item
element in HTML, and the content of that list item element
will just be the name of the thing that I need to do.
All right, so let's give it a try.
I'll run Flask run to go ahead and run this web application.
We'll go back and we'll refresh.
And we'll notice now the task list is empty,
because I start out with no tasks.
Let me create a new task, and I'll just say do the dishes, for example,
and submit that.
And all right.
Now I see a task list that has one bullet point that
just says do the dishes, the first thing on my list of things that I need to do.
I can add a new task.
Create A New Task.
I'll add one like answer email, for example.
Click Submit.
And all right, we've added a new task to the list of things to do.
And we can see what the actual HTML looks like here.
If I go to View, Developer, and then View Source,
we can see that here inside the body of my web page,
I have that big heading at the top that justice tasks,
and then I have an unordered list where I'm
looping over all of the items in my list of things to do
and showing each one at a time.
Here's a list item that says do the dishes.
Here's a list item that says answer email.
So I've been able to create dynamically generated HTML
based on the values of a variable.
And when you submit a form, we're updating that variable
so that your task list always shows you the list of all of the things
that you currently need to do.
And so this is the power of Flask, the ability
to generate these dynamic web pages.
Now, let's try and make one final modification just
to optimize this a little bit more.
Right now if you go to create task and go to a task name
and just click Submit without typing something in, what happens?
Well, we just get an empty bullet point.
Because as you might imagine, what's going
to happen is that we're submitting the form, the name of the task
is just the empty string, nothing was typed in,
and so we just get this empty bullet point, which
probably isn't what we want to do.
So let's try to fix this now.
And we can fix this by adding some JavaScript, for example, to add.html.
Here below this form, I'll go ahead and add a script
where I can add some JavaScript code.
And what I'd like to do is I only want to enable this Submit button if there's
actually some text inside of this task.
So what I need to be able to do is somehow
access both of these input fields.
So I'll give each of them an ID.
I'll give this one an ID of submit, and I'll
give this field where I'm typing in the task name an ID of task.
Recall again that these IDs are just unique identifiers for these input
fields so that I in my JavaScript code can then reference that information.
So inside of script, what am I then going to do?
Well, what I want to say is document.queryselector
the thing with the ID of task.
Any time I type something into the task, whether it's typing a new character
or typing the Delete key, I would like to check
is there something currently inside of the text field?
And if not, then you should disable the Submit button.
So I'll go ahead and say on key up, which
is an event that happens whenever I lift my finger off of a key,
for example, whether it be a character key or the Backspace key.
And when that happens, I'll go ahead and run this function.
What should the function do?
Well, the function should check, is there's something actually typed
in to this task input field?
How do I express that?
Well, it's going to be a condition where I say if document.queryselector
task.value is equal to the empty string, if I take the input field,
look at its value, and it's equal to nothing,
there's nothing there, well then what do I want to do?
Well, I want to disable the Submit button.
So I want to say, all right, let's go ahead in document.queryselector
find the part of the HTML page that has an ID of submit
and set its disabled attribute equal to true.
Else, in other words, if there is something typed into the task field,
then we're going to take the Submit button
and set its disabled attribute to false.
So let's talk through the logic here and just get an understanding
for what it is that's happening.
I'm adding an event listener to the thing
that has an ID of task, which is that input
field where I can type in a task name.
And I'm saying every time I lift my finger off of a key,
you should run this function that follows here, this anonymous function.
What the function is doing is saying, all right, check that task input field
and see what its value is.
If its value is the empty string, in other words,
nobody has typed anything in, then disable the Submit button.
So you can't submit the form.
Otherwise, set disabled equal to false, meaning do enable it.
And then the last step is just to say, all right, for this input field here,
do I want it to be enabled or disabled by default?
I probably want it to be disabled by default,
because by default, there is no text inside of the text field.
So I'll add a disabled attribute to this input.
Let's try it out now I'll go ahead and rerun the application, Flask run.
And now I'll reload.
Create A New Task.
And notice now that by default, the Submit button is grayed out.
It's not until I actually start typing something, like answer email,
that you start to see the Submit button now enabled.
But if I go back here and backspace, backspace, backspace
all the way until there is no more characters, watch what happens.
Press Backspace one more time, and the Submit button disables.
You're no longer able to click it, because now I
have some JavaScript code that's performing effectively some validation.
JavaScript code that's checking whether or not something
is actually typed into this input field and then responding appropriately.
So here now we've been able to mix HTML as well
as JavaScript inside of these templates that Flask is rendering to the user
so that we can really create an interactive experience on this web page
that we're creating.
I press Submit, and we now see that answer email
is added to this list of tasks.
Of course, what you might notice here is that every time I restart the Flask web
server, I lose access to all of the tasks that I previously created.
If I go back here, Control C to exit out of the Flask web server,
and then I rerun Flask run, for example, now when I go back to my task list
and reload the page, my tasks go away.
Why is that?
Well, every time I reload the web server, it's rerunning application.py.
And this to dos variable, which is my list of things to do,
is getting reset back to the empty list.
It's just being stored as a variable in memory in my program.
And so when I quit the web server and restart it,
it gets reset back to the empty list again.
In practice, though, if you really want to store data then
in your web application, you're probably going
to want to store it somewhere else rather than just
inside of a global variable inside of your web application.
And to do that, we can connect Flask with databases using SQL in order
to be able to run SQL queries in order to select data or insert data or update
data inside of our Flask web application itself so that users
can interact with that data on the pages that they visit.
But this is the power of Flask.
The ability to build these dynamic web applications that
are able to listen for requests and respond with dynamically
generated HTML, which give us the power to create
all the more interesting and more expressive
web applications on the internet.