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.
