Skip to content

Latest commit

 

History

History
2920 lines (2260 loc) · 128 KB

DevFest Web Development Track.md

File metadata and controls

2920 lines (2260 loc) · 128 KB

DevFest Web Development Track

Learn web development by building a reading list app with Flask.

Written and developed by Raymond Xu, Matt Piccolella, Dan Schlosser, and ADI.

Top

About This Document

Methodology

This guide will teach you fundamental concepts of web development by guiding you through the process of building a reading list app. We will begin by setting up a basic webpage in Flask, and then we'll incrementally add features level by level. After finishing this tutorial, you will have built a fully-functional reading list app that allows you to save a list of favorite books for each of your users.

Prerequisites

Basic knowledge of the Python programming language is suggested. If you don't already know Python, check out this ADI-developed tutorial. No knowledge of HTML, CSS or Flask is expected or required.

Top

Table of Contents


Top

Level 0: Environment Setup

Before we get started, set up your environment using this guide.

Top

Level 1: Making Web Pages: Flask and HTML

Let's begin by creating the landing page for our reading list app. This will simply be the introductory screen for our app that will brief our users about what the app is for. Before we do so, we need to learn about Flask.

Top

1.1 What is Flask

Flask is a Python microframework. Microframeworks are bare-bones, customizable tools that make it easy to build web apps. Flask lets us do this in Python. It is very easy to setup and has excellent documentation on its website; feel free to dig around! There's lots of awesome stuff to read about on their site.

1.1.1 How a Flask App Works

Flask works with a client-server model. The server, written in Python, has functions that take requests from clients (i.e. web browsers) and return web content to be displayed by the client.

Think about it like a restaurant: The patron (the client, or web browser) gets their meal (the web page) by telling it to the waiter (the Flask server). Then, depending on the order (the request), the waiter gives it to the cook (back-end functions, optional) and then the server returns the cooked meal to the patron.

In addition to serving static pages (meals off the menu), Flask servers have the ability of serving dynamic web pages, or pages that are generated every time you load the page (made to order). Dynamic content could include information stored in a database or a user account. For example, when you open Facebook, the news feed is different depending on who's account you are on and what posts your friends have shared.

1.1.2 The Anatomy of a Flask App

This is a very basic directory structure for a Flask webapp.

ProjectDirectory/
├── app.py
├── requirements.txt
├── static/
│   ├── css/
│   ├── img/
│   └── js/
└── templates/
  • ProjectDirectory/ - Everything for your app goes in this folder. Rename this to the name of your app, so maybe reading-list, in our case.
  • app.py - All of the Python/Flask code and server logic gets written in this file.
  • requirements.txt - A list of all of the dependencies for your project. See more about dependencies and installing them in section 6.3 of our Python tutorial.
  • static/ - This folder holds all your static files. Static files include:
    • js/ - Javascript files, which allow interactive web content. We'll talk about these later.
    • css/ - CSS files, which style our app. We'll talk about these later too.
    • img/ - Image files.
  • templates/ - This folder holds all your Flask templates. Our HTML files will go here. There are special features offered by Flask that make templates different than basic HTML files, explored in Section 3.3.

Top

1.2 Hello World in Flask

Download the starter code for level 1 here. Click on the green button that says "Clone or Download" and then click "Download ZIP", and then unzip the downloaded file. You should now have a directory on your computer called devfest-webdev-track-code. Let's rename the directory to reading-list-app. For the rest of the tutorial, we'll assume that you're inside of your virtualenv virtual environment and editing your code with Sublime Text.

As a refresher, here's a set of steps you can follow to quickly set up your development environment (all the packages you'll need) safely in a virtual environment:

cd reading-list-app
virtualenv venv
source venv/bin/activate
sudo pip install -r requirements.txt

or, if you're on a PC:

cd reading-list-app
virtualenv venv
venv\Scripts\activate
pip install -r requirements.txt

As we mentioned back in Level 0: Environment Setup, we can painlessly install packages into our virtual environment by asking pip to install from a list of packages. That list is requirements.txt, and conveniently prevents you from having to install each package individually. Isn't that great?

In order to write our first Flask app, we only need to edit one file: app.py. It's that easy!

1.2.1 Editing app.py

First, import the Flask class from the flask module.

from flask import Flask

Then, construct the Flask app variable. We'll pass around this variable whenever we want to access information about the server. We pass __name__ into the Flask() function so that your flask app is associated with the directory structure we created in Section 1.1.

app = Flask(__name__)

Next, apply the @app.route("/") decorator to a function called hello(). It returns just the string "Hello World!". By doing this, we are making a route. The route() decorator binds the URL http://yourwebapp.com/ to this function, effectively adding a new page to your app. Functions with the route() decorator can return text strings or HTML, and whatever is returned will be displayed by the client.

@app.route("/")
def hello():
    return "Hello World!"

Finally, call app.run(host="0.0.0.0") when the file is executed. Once app.run(host="0.0.0.0") is called, the server will start accepting requests from the client.

if __name__ == "__main__":
    app.run(host="0.0.0.0")

This leaves us with a completed Hello World program in Flask:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(host="0.0.0.0")

1.2.2 Running a Flask App

To run our Hello World app, just type the following command in the Project Directory folder:

$ python app.py

If it's working, it should print the lines:

* Running on http://0.0.0.0:5000/

Point your browser to that URL and bask in the awesomeness!

Wondering what that weird URL is? 0.0.0.0 lets all public IPs connect to the server. When we run a Flask server with python app.py, it is running only on your machine, not the internet. The :5000 bit is the port, or specific place where your app is running. Developing locally is much easier and safer than publishing your app to the internet every time you want to test something, and is considered good practice. Also, instead of typing out http://0.0.0.1:5000 every time you want to see your app, you can also point your browser to http://localhost:5000. Try it out!

1.2.3 Developing with Flask

Flask is great for development. It offers very helpful error messages and prints stack traces well in the browser, if instructed to. To enable these debugging features and (more importantly) automatic reload, edit app.py and add a statement configuring the app object.

from flask import Flask

app = Flask(__name__)
app.config["DEBUG"] = True  # Only include this while you are testing your app

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(host="0.0.0.0")

With this modification, edit the string returned by the hello() function and refresh your browser to watch it change! When you run the server now, it should also print:

* Restarting on reloader

Top

1.3 Working with Routes

Let's define a few more routes for our app. Again, routes are paths that can be visited by the user of the app. We use the decorator app.route("/somepath") to tie the decorated function to the path given in the parenthesis.

1.3.1 Static Routes

First, lets make a route that lets the client get the name of the creator of this app. Start by defining a function called name(), and have it return your name as a string.

def name():
  return "Your Name"

Now we'll apply the decorator app.route(). Inside the parenthesis for the decorator, include the path "/name". Paths in Flask always start with a /.

@app.route("/name")
def name():
  return "Your Name"

Save. With your server running or reloaded, point your browser to "http://localhost:5000/name" and your name will appear! Our /name route is static, because it returns the same string every time.

Now, as an exercise, make another static route accessible at http://localhost:5000/website that returns the URL of your personal website or favorite website (don't forget the http://!).

1.3.2 Dynamic Routes

Dynamic routes are what make using Flask so valuable. Start off by making a static route called search. It can return any string you want. We'll edit it to return the results of our web search.

@app.route("/search")
def search():
  return "Search"

To make our route dynamic, first we will modify the url to take a variable parameter named search_query.

@app.route("/search/<search_query>")

Then, modify the search function to take a string variable search_query, and return that.

@app.route("/search/<search_query>")
def search(search_query):
  return search_query

Save and reload your server as needed, and navigate to http://localhost:5000/search/test and see test appear as the returned page. If you change what comes after the /search/ in the URL, it will be displayed in the browser. We will soon modify this route to return actual search results.

Top

1.4 HTML Basics

Now that we've covered basic routes in Flask, we can take a look at HTML.

1.4.1 What is HTML

HTML, or HyperText Markup Language is the main lanugage we use for creating content that will be displayed by a browser. So what is HTML? Well, at the very core, it's text. Create a file in your working directory, and call it hello.html. Inside it, just type:

Hello World!

No funny business, just the two words. If you open hello.html in your browser, you'll see it display Hello World!. Congratulations on writing your first HTML document! Every text document can be interpreted as an HTML document (just add the .html extension!), but true HTML documents organize themselves in a structured way, using elements.

Elements

Elements are made up of the start tag, the content, and the end tag (or closing tag). Both the start and end tags are wrapped in angle brackets (< >), and contain the element name inside them. The end tag has slash (/) that immediately follows the open angle bracket. Content of elements can be plain text, or even more HTML. Copy this example element into hello.html, and view it in your browser:

Hello World!

<p>This is a really cool paragraph</p>

The start tag is <p>, the content is This is a really cool paragraph, and the end tag is </p>. The element name in this case is p, for paragraph. Because the content of an element can be HTML, we can nest tags inside each other. So for example, if we wanted to emphasize that the paragraph is cool, we could wrap the words "really cool" in <em> tags, like this:

Hello World!
<p>This is a <em>really cool</em> paragraph</p>

Reload hello.html. You'll probably see the words "really cool" italics. Now lets say we want to put even stronger emphasis on the word "really". We can use the <strong> tag for that. Edit hello.html:

Hello World!
<p>This is a <em><strong>really</strong> cool</em> paragraph</p>

If you reload hello.html, you should see that "really cool" is in italics, and "really" is also in bold. Does that mean the the <em> is used to make words italic and <strong> is used to make them bold? No. Most web browsers have agreed that emphasis should be expressed using italics, and strong emphasis should be expressed with bold font, but as writers of HTML, we use tags to describe the purpose of the content, not to achieve the format that we see once they're applied.

Headings

One rule of thumb for writing good HTML: All text should be wrapped in tags, and the tags should be meaningful. Right now, our Hello World! text has no tags around it, so let's figure out what tag makes the most sense. "Hello World!" is the title of our hello.html web page, so we'll use the heading tags to indicate this. The <h1>, <h2>, <h3>, <h4>, <h5>, and <h6> tags are used for headings and subheadings, <h1> being the highest level (and usually the largest), and <h6> being the lowest level (and usually the smallest). Let's wrap our page title in <h1> tags.

<h1>Hello World!</h1>
<p>This is a <em><strong>really</strong> cool</em> paragraph</p>

Reload hello.html and see the <h1> tag in action! You might have an instinct to mess around with the different headings 1-6, and you should go ahead! But remember this, we chose <h1> because "Hello World!" was the title of our webpage, not because we wanted it to be large. So as you switch the <h1> tags to <h2>, remember that it wouldn't really make any sense to include an <h2> element in a website without an <h1> element, because that would mean a subheading without a heading. From <h2> and down, headings should be interchanged depending on their importance.

Attributes

HTML elements can also have attributes. Attributes are key-value pairs that modify the contents of HTML elements, or provide additional information about the element itself. For example, when we use the <a> tag to create an anchor, or hyperlink, we have to provide the destination in the href attribute. Edit hello.html:

<h1>Hello World!</h1>
<p>This is a <em><strong>really</strong> cool</em> paragraph.  My favorite search engine is <a href="http://www.google.com">Google</a></p>

The href is the key, and then we set it = to the value "http://www.google.com". If you reload hello.html, you'll see a blue (or maybe purple), underlined link to google.com on your page!

Wondering what "href" stands for? So do we. It's extremely unclear in the community, and while there is some consensus that it should mean "Hypertext REFerence", there is still confusion.

1.4.2 Anatomy of an HTML Document

The purpose of HTML documents is to provide a semantic structure that represents the web page that is being displayed. When talking about HTML, semantic means that all the tags are used for the appropriate purposes, and that all necessary information is included in the HTML source code.

In this section, we'll dissect a boilerplate HTML document that follows HTML5 (the most modern) standards that help ensure that your HTML is rendered as best as possible by all devices and browsers. Here is the document:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>My Website</title>
	</head>
	<body>
		<!--Page content-->
	</body>
</html>

The first line is the our file's doctype. according to the HTML specification, the doctype is a "required preamble", but for our purposes, it's just something you should always include.

Wondering how we got away with writing an HTML document without a DOCTYPE, or think you're too cool for one? Read this stack overflow response that summarizes it pretty well. Use it, or be prepared to feel the pain that may or may not eventually result from such hubris.

Older HTML standards include a much wordier DOCTYPE tag at the beginning of the document, but we only need:

<!DOCTYPE html>

Note that this is not an element, and is just a tag. There is an ! at the beginning of it and there is no closing tag. This is one of the only cases where this will be true.

Next, we see the line:

<html lang="en">

This the the opening tag of the <html> element. You can see it's closing tag at the last line. We use the <html> element as the root for all other elements, by convention. The lang attribute is used to indicate that the primary language for this document will be English.

Moving inwards to the children of the <html> element, or elements one level within it, we see the <head> and <body> elements.

The <head> element holds all the information about the web page that should not be displayed within the browser window. The <body> tag holds all the elements that should be displayed.

Within the <head> element, we first have a <meta> element:

<meta charset="utf-8">

The <meta> element does not have content or a closing tag, tells us extra information about this document. By default, we indicate that our character set will be utf-8, or unicode.

The next line is more straightforward.

<title>My Website</title>

The <title> element tells the browser the name of our website. If you load this example in your browser, you'll see My Website show up as the title of the page (even though it will have no content in the browser window).

Within the <body> element, all we have is the line:

<!--Page content-->

Everything wrapped between the <!-- and the --> is a comment, and won't show up in the page. Edit hello.html to reflect the standard HTML5 template. We can remove the comment.

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Hello World!</title>
	</head>
	<body>
		<h1>Hello World!</h1>
		<p>This is a <em><strong>really</strong> cool</em> paragraph.  My favorite search engine is <a href="http://www.google.com">Google</a></p>
	</body>
</html>

When you reload hello.html, you may not notice any change. However, constructing our document in this manner will make it more extensible and cross-device compatible.

1.4.3 An Overview of Common Tags

There are many HTML elements that you will use for your website. There are some basics you should know, before you dive into it, however. Most of this section is summarized from the Mozilla Developer Network (MDN), more specifically their HTML element reference.

DO NOT SKIM THIS NEXT PARAGRAPH

Please, please, please use MDN. There is another site, called W3 schools (hyperlink intentionally excluded) that consistently turns up higher in Google search rankings, and consistently has incorrect, outdated, and more confusing information on the same topics. If you have a question about web development (generally HTML, CSS, or JavaScript), just append "mdn" to the end of your search, to make sure that you get MDN as the top result. The team at Mozilla has done an excellent job of making an excellent website that has the most up-to-date information about good web development practices.

Structure

  • <html> (MDN) - All HTML should be wrapped in the <html> element. It should be the only element at the top level of an HTML document (except <DOCTYPE>).
  • <body> (MDN) - Represents the content of the HTML document. All visible elements are descendant from the <body> tag.
  • <head> (MDN) - Holds all the metadata about the document. This could include the title, charset, styling, and scripts
  • <div> (MDN) - A general purpose container, to be used when no other element has semantic meaning. Using CSS, <div> elements can be made to server all sorts of stylistic purposes. There are a lot of elements to choose from, so be sure that nothing else fits before using a <div>. That said, you will find that you end up using <div> elements with some frequency.
  • <span> (MDN) - Another general purpose container, but specifically for inline content. For example, if you want to highlight misspelled words in a page, wrapping them in a span that has been configured to highlight its content would be the best solution. Don't use a <div> when a <span> would be more appropriate.

Text

  • <p> (MDN) - A paragraph of text.
  • <a> (MDN) - A link, or anchor. Be sure to always include the href attribute when using <a> tags. If a link doesn't go anywhere, set the href attribute equal to "#".
  • <h1> - <h6> (MDN) - Headers of various levels, <h1> being the highest level (like the title of an article or the title of the page), and <h6> being the most low level heading. For Search Engine Optimization (SEO) reasons, you should only include on <h1> tag on every page.
  • <em> (MDN) - Emphasis. While on many browsers this will make text italic, do not use <em> elements for only this purpose. Only use emphasis tags when words need emphasis.
  • <strong> (MDN) - Strong emphasis. Usage rules are similar for the <em> element.
  • <br> (MDN) - For line breaks. Using <br> tags is only really appropriate when writing a poem, address, or something else where line breakage is important. Don't use this for the space between two paragraphs (just use two <p> tags!).

Lists

  • <ol> (MDN) - An ordered list. Ordered lists should only contain list items (<li> elements). Only use an <ol> when the order of the items in the list matters, like a recipe or instructions.
  • <ul> (MDN) - An unordered list. Unordered lists should also only contain list items (<li> elements). Use this when you have a list of similar items, but the order does not matter.
  • <li> (MDN) - An item in a <ul> or an <ol>.

Forms

  • <form> (MDN) - A wrapper for any form that the user will fill out on the page. If you are making a web form, check out this instructional guide from MDN on how to build a basic form.
  • <input> (MDN) - An element that is used to input data into a form. This could be a text-box, radio button, or an email address box depending on the attributes used. Input tags are self closing, meaning that they don't have content or an end tag, and just take the form <input />.
  • <label> (MDN) - A piece of text that labels an input.
  • <textarea> (MDN) - A large, multiline text box that is part of a form.
  • <button> (MDN) - Used for a button that submits or resets the form.

Meta / Informational

Except the <DOCTYPE> tag, all of these elements should be children of the <head>.

  • <DOCTYPE> (MDN) - The declaration of the document type.
  • <meta> (MDN) - Provides extra information about the document. The <meta> tag may serve a bunch of different purposes depending on its attributes.
  • <link> (MDN) - Linking this document to an external resource. For the most part, the <link> tag is only used for linking to an external CSS file. We'll learn more about this syntax in section 2.1. Be sure to always include the href, rel, type, and media attributes, as follows:
<link href="style.css" rel="stylesheet" type="text/css" media="all">
  • <style> (MDN) - Embedded CSS style. While it is better practice to use <link> to an external CSS file, it is also possible to include CSS content in a <style> element.
  • <title> (MDN) - The title of your page.

Other

  • <img> (MDN) - Used to include an image on the page. Always include the src attribute, the URL of the image resource, and the alt attribute to describe the image if for some reason the image cannot or should not be loaded. The <img> element is self closing, like the <input> element. We write it in the form <img />. For example:
<img src="https://developer.cdn.mozilla.net/media/img/mdn-logo-sm.png" alt="MD Logo" />
  • <script> (MDN) - Use to either embed JavaScript (in rare occasions) or link to external JavaScript file (more common). Always include the type attribute, and include the src attribute if the file is external. Here is an example of the two different styles of using the <script> element:
<script type="text/javascript" src="my_script.js"></script>
<script type="text/javascript">
	console.log('Hello Console!');
	alert('Hello World!');
</script>

1.4.4 The Landing Page

Let's turn hello.html into the landing page for our app! Replace its contents with this:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Reading List App</title>
	</head>
	<body>
		<h1>Reading List App</h1>
		<p>This web app allows users to search for books and add them to their reading lists!</p>
	</body>
</html>

Flask uses something called templates to make writing HTML more modular. We will go in-depth on templating in a later section, but let's set up a simple HTML page here. Let's make our Flask app return hello.html as a template. First, move hello.html into the templates folder. Then, in app.py, import render_template from the flask package:

from flask import Flask, render_template
import requests
...

The function render_template() turns templates in your templates folder into HTML that can be sent to the client.

Now for our / route, we'll return the string that is created by calling render_template() on the name of the template file.

...
@app.route("/")
def hello():
    return render_template("hello.html")
...

Reload the server and visit localhost:5000. You'll see hello.html, but this time rendered by Flask!

hello.html

1.4.5 The Search Page

Now let's write the web page where users can search for books!

First start by creating a file called search.html in the /templates folder like such:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Search</title>
	</head>
	<body>
		<!--Page content-->
	</body>
</html>

The first thing we should do is add a title to the search page. We'll use <h1> because this will be the top level heading.

...
<body>
	<h1>Search</h1>
</body>
...

Now, let's create a web form that allows users to enter a search query and click a button to execute the search. We'll make a very simple search form (following guidelines from MDN). Start with a <form> element. We fill out the action and method attributes in accordance with HTML5 standards.

...
<h1>Search</h1>
<form action="/search" method="post">
</form>
...

Next we'll add an <input> method with type attribute equal to text, in order to make a text field. We'll add placeholder text using the placeholder attribute, to cue in users as to how to use the form. We also include the name attribute, which lets us standardize how the data from the form will be sent to our Flask server. Finally, add the attribute required (it does not take a value). This ensures that we don't submit the form when the text box is empty.

...
<form action="/search" method="post">
	<input type="text" placeholder="Search for a book" name="user_search" required/>
</form>
...

Then we'll add a search <button>:

...
<form action="/search" method="post">
	<input type="text" placeholder="Search for a book" name="user_search" required/>
	<button type="submit">Search</button>
</form>
...

Now we have our completed search.html:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Search</title>
	</head>
	<body>
		<h1>Search</h1>
		<form action="/search" method="post">
			<input type="text" placeholder="Search for a book" name="user_search" required/>
			<button type="submit">Search</button>
		</form>
	</body>
</html>

search.html

Right now the search button doesn't do anything, but we will add functionality to it in Level 3.

In the meantime, let's connect our landing page (hello.html) to our search page (search.html). To do so, we just need to add a button to hello.html that redirects to a route that will serve search.html.

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Reading List App</title>
	</head>
	<body>
		<h1>Reading List App</h1>
		<p>This web app allows users to search for books and add them to their reading lists!</p>
		<a href="/search"><button>Launch App</button></a>
	</body>
</html>

Note that the button is wrapped in an href to /search. This means that when you click on the button, it will take you from localhost:5000 to localhost:5000/search (when running locally). Now we just need to write a search route in app.py. Replace the old /search/<search_query> route with this:

@app.route("/search")
def search():
	return render_template("search.html")

Try it out by going to localhost:5000 and clicking the "Launch App" button!

Top

Level 2: Styling our App: CSS

If you have not completed the previous levels, download the starter code for level 2 here.

Now that we have a basic landing page, let's add some styling to it so it doesn't look so bland!

CSS, or Cascading Style Sheets, is a styling language that is used to arrange and stylize HTML elements. CSS is extremely powerful, but also fairly hard to learn. Every different browser interprets CSS slightly differently, and there are a lot of tricks and best practices that are hard to learn. As such, CSS is best learned by lots and lots of practice. The ADI Resources page has links to a lot of different tutorials and walkthroughs, if you want more practice after styling your Flask app.

Top

2.1 CSS Basics

CSS is a very simple language. At it's core, CSS is made up of three parts: selectors, properties, and values. Selectors are used to select which elements are being styled. For example:

p { }

selects all <p> elements.

Inside the braces { } are the properties and values separated by colons :, with semicolons ; at the end of each property / value pair. For example:

p { color: blue; }

would make all the <p> tags blue. Here, color is a property and blue is a value. In terms of syntax, that's really it. If we wanted to make all <p> tags blue and all <strong> tags red, here would be our CSS:

p {
	color: blue;
}
strong {
	color: red;
}

Notice that whitespace is not relevant to syntax. Finally, comments should be surrounded in /* */.

/* this is a comment */

There is an extremely large collection of CSS properties and values, all of which are documented excellently at (surprise) the Mozilla Developer Network. Again, avoid incorrect or out of date information by appending mdn to any Google searches related to CSS you might have!

2.1.1 Applying CSS Styles

There are three ways to apply CSS to HTML elements. The first is called inline styling. Every element can be given a style attribute that can take CSS styles that apply to this element. Doing this, we can avoid selectors entirely because all of our styling affects only the element that we are adding the style attribute to. For example, this <p> tag will have blue text:

<p style="color: blue">This text is blue.</p>

Inline styles are problematic, however. Not only do we clutter the text that is being displayed, but also we would have to duplicate our styling for every paragraph we have!

<p style="color: blue">This text is blue.</p>
<p style="color: blue">An so is this text.</p>
<p style="color: blue">And this text.</p>

Because of this, inline styles should be absolutely avoided unless you are writing for mail clients that ignore CSS that isn't inline.

We can also apply CSS in a <style> element, in the <head>, like so:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>CSS Demo</title>
		<style>
p {
	color: blue;
}
		</style>
	</head>
	<body>
		<p>This text is blue.</p>
		<p>An so is this text.</p>
		<p>And this text.</p>
	</body>
</html>

This is great, because the body of the document is a lot less cluttered. As you can imagine, this is only relocates the problem. now the head of the document is messy and full of CSS styles. As your HTML gets longer so does your CSS, and now documents can reach unmanageable length.

For the same reason we might break up a large programming project into classes and functions, we want to get our CSS out of our .html document entirely. Enter the <link> tag, which lets us reference external .css stylesheets! Link tags only need an open tag, and have no content or end tag. Also, be sure to always include the rel attribute as 'stylesheet' and the type attribute as text/css alongside the href attribute pointing to the .css file.

```html <title>CSS Demo</title>

This text is blue.

An so is this text.

And this text.

``` ```css /* demo.css */ p { color: blue; } ```

Using an external CSS file is the best practice, barring special circumstances. You can even include multiple CSS files to keep things organized.

<!DOCTYPE html>
<!-- demo.html -->
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>CSS Demo</title>
		<link href="blue.css" rel="stylesheet" type="text/css">
		<link href="red.css" rel="stylesheet" type="text/css">
	</head>
	<body>
		<p>This text is blue.</p>
		<strong>And this text is red.</strong>
	</body>
</html>

```css /* blue.css */ p { color: blue; } ``` ```css /* red.css */ strong { color: red; } ```

2.1.2 Selectors

In the above examples p and strong in the .css files are selectors. Selection can be done in a variety of different ways. The most basic selection is by element name.

```html

This text is blue

This text is not ``` ```css /* demo-name.css */ p { color: blue; } ```

If you provide a class attribute, the .classname syntax will select all elements with class="classname". Elements can have multiple classes using the syntax class="classname1 classname2".

```html

This text is blue

This text is not blue

This text is blue and underlined.

``` ```css /* demo-class.css */ .blue { color: blue; } .underline { /* Use the "text-decoration" property to underline text. */ text-decoration: underline; } ```

You can also give an element a unique id and it can be selected with the #idname syntax. Do not give two elements the same id.

```html

This text is blue

This text is not blue

``` ```css /* demo-id.css */ #blue { color: blue; } ```

There are also relational selectors for finding elements in the HTML document.

Using the descendant selector syntax, or selector1 selector2, you can restrict selector2 to only elements that are descendants of an element selected by selector1. For example:

```html

This text is not blue

This text is blue

This text is also blue

This text is not blue

``` ```css /* demo-descendant.css */ .blue p { color: blue; } ```

Similarly, the child selector syntax (selector1 > selector2) will restrict selector2 to only children (not grandchildren or other descendants) of elements selected by selector1. For example:

```html

This text is not blue

This text is blue

This text is NOT blue, because its parent is the span tag, which does not have the "blue" class.

This text is not blue

``` ```css /* demo-child.css */ .blue > p { color: blue; } ```

Finally, you can combine two selectors with a comma (,). These two CSS files are the same:

```css /* demo.css */ p { color: blue; } .blue { color: blue; } ``` ```css /* demo.css */ .blue, p { color: blue; } ```

2.1.3 Basic Properties and Values

Learning CSS, for the most part, is about learning the knitty gritty details. For an in-depth, comprehensive walkthrough of CSS properties and and how to apply them, check out HTML Dog's excellent CSS tutorial series. In section 2.2, we will be applying CSS en-masse, using external libraries that provide shortcuts to a stylized webpage. While using these libraries is good practice, these libraries always need to be accompanied by some extra CSS code for your own website. For this reason, it's important to understand some basic CSS properties.

Color and Background-Color

You can change the background color of an element with the background-color attribute, just as you can change the text color with the color attribute. There are many CSS properties that have colors as values, and for all of them colors can be represented in multiple ways (detailed in-depth at MDN), the most simple (and limited) being keywords (like blue, red, etc.).

```html

This text is blue.

This text is yellow on black.

``` ```css /* demo-color.css */ .blue { color: blue; } .b-and-y { color: yellow; background-color: black; } ```

Height and Width

The most basic CSS property is height and width. They change the size of the content area of the selected element. Height and weight take measurements of length (detailed in depth at MDN), the simplest being pixels, or px.

```html
``` ```css /* demo-height-width.css */ .blue { background-color: blue; } .red { background-color: red; } .first { height: 100px; width: 200px; } .second { height: 150px; width: 150px; } .third { height: 50px; width: 300px; } ```

demo-height-width

Borders

You can also give your elements borders. To create a visible border, you need to set three properties for the selected element: border-width, border-style, and border-color. These three properties can be combined into one border attribute. The values width, style, and color can be provided in any order. The border will wrap around the box, not the text (note that the third resized <p> has a square border).

```html

Black border, 3px wide.

Same as above

A thick, dashed blue border.

``` ```css /* demo-border.css */ .first { border-width: 3px; border-style: solid; border-color: black; } .second { border: 3px solid black; } .third { height: 100px; width: 100px; border: 10px dashed blue; } ```

demo-border

Margin and Padding

Setting the margin and padding attributes for an element creates space around it. The content area (changed by the height and width properties) border, margin, and padding make up the box model.

boxmodel

Photo credit to the MDN page on the box model.

For the most part, margin is used to create space outside of the element, and padding is used to make space inside the element. The border sits just between the two.

You can set margins with the margin-top, margin-right, margin-bottom, and margin-left attributes, and you can set padding with the padding-top, padding-right, padding-bottom, and padding-left attributes. Values for these attributes are in units of length, and can even be negative.

The four margin and padding attributes can each be combined into margin and padding, and depending on how many arguments are provided, different of these properties will be set. See the table for the details of how this works (margin is used, but the same goes for padding).

CSS Shorthand -top -right -bottom -left
margin: 1px 2px 3px 4px T R B L 1px 2px 3px 4px
margin: 1px 2px 3px T R&L B 1px 2px 3px 2px
margin: 1px 2px T&B R&L 1px 2px 1px 2px
margin: 1px T&R&B&L 1px 1px 1px 1px

Here are a couple different examples of padding and margins in action:

```html
One
Two
Three
``` ```css /* demo-margin-padding.css */ body { /* so that the effects of margins on the divs are easier to see. */ margin: 0; padding: 0; border: 50px solid green; } div { /* Every div starts off as a yellow square with a black border. */ height: 50px; width: 50px; background-color: yellow; border: 2px solid black; } .first { /* Offset from the wall on the left. */ margin-left: 20px; } .second { /* Offset from block one by 50px. */ margin-top: 50px; /* And the wall by 5px. */ margin-left: 5px; /* 20px larger in each dimension because padding is inside the border, but text is offset by 20px (that's why the text doesn't hug the border). */ padding:20px; } .third { /* Offset from the bottom by 10px, and 25px higher and to the left than expected because of the negative values. */ margin: -25px 0 10px -25px; /* Taller with the text separated from the top of the box. */ padding-top:20px; } ```

demo-margin-padding

2.1.4 Using the Inspector

One of the most important skills to learn as a web developer writing CSS is to learn how to use the inspector in your favorite browser (Internet Explorer not allowed). The inspector lets you see the HTML, CSS, and JavaScript that your web browser is rendering, live! You can inspect your own web page to find bugs or make tweaks to your code, or inspect other pages to learn how to imitate a desired HTML/CSS/JS effect.

With a web page open in FireFox, there are three ways to inspect the page:

  • Choose the "Inspector" option from the "Web Developer" menu (which is a submenu in the "Tools" menu on the Mac).
  • Press Ctrl+Shift+C (Cmd+Option+C on the Mac OS X and Linux).
  • Right-click an element on a web page and select "Inspect Element".

With a web page open in Chrome, there are three ways to inspect the page:

  • Select the Chrome menu at the top-right of your browser window, then select Tools > Developer tools.
  • Use Ctrl+Shift+I (or Cmd+Opt+I on Mac) to open the DevTools.
  • Right-click on any page element and select Inspect element.

To inspect a page in Safari, you first have to enable the Develop menu. Go into Safari's preferences, and check the “Show Develop menu in menu bar” checkbox in the "Advanced" pane. Then, you open the inspector in three ways:

  • Choose the "Show Web Inspector" option in the "Develop" menu.
  • Press Cmd+Option+I.
  • Right-click an element on a web page and select "Inspect Element".

Top

2.2 External Libraries

Building an entire web app from scratch can be an undertaking. In order to speed up the process, many web developers use front-end frameworks, which are packages of HTML, CSS, and JavaScript that can be included in your web app.

The two most famous front-end frameworks are Twitter Bootstrap and Foundation by Zurb. Which one is better to use is up for debate, but if you believe this Medium post, then the difference can be articulated fairly plainly:

ZURB and Twitter made their objectives and intentions very clear when naming each CSS Frameworks: Bootstrap will have everything you’ll ever need to bootstrap your project. Foundation will have just the things you will ever need as the foundation for your project.

We'll be using Foundation, because it's slimmer and simpler, and will provide a strong backbone for our app. You should keep Foundation's newly revamped documentation open at all times for this section — it's a great reference tool.

One other thing before we get started: on the Foundation website, you will continue to see references to "Sass" or "SCSS". These are languages that extend the CSS syntax. They are more powerful, but harder to learn and develop in for the first time. We are using "Foundation CSS", which is Foundation that uses raw CSS instead of Sass. When we go onto the documentation page for different Foundation components you will see a section called "Customize with Sass" at the bottom. For the purposes of our app, we can ignore this.

2.2.1 Using Foundation

Using Foundation can be as simple as applying some extra classes to HTML elements, applying the imported CSS. For general purpose "style-based" changes this is fine. (The class button will make an <a> tag look like a button, and so on. Just look up the component your are styling in the Foundation docs and you'll be fine.) While Foundation offers many shortcuts to attractively styled pages, it also provide "layout-based" classes that are used for placing elements appropriately on the page. Chief among these is Foundation's grid system.

The grid system makes layout easy. A <div> with class row hold 12 columns, and then you can create <div>s that take up any number of those columns. For example, the following would create three side-by-side columns:

<div class="row">
  <div class="large-4 columns">Left</div>
  <div class="large-4 columns">Middle</div>
  <div class="large-4 columns">Right</div>
</div>

Foundation's grid system is responsive, meaning that it adapts to different screen sizes. small is every screen from 0-640px, medium is 641-1024px, and large is 1025px and up. The numbers after the large-, medium-, small- classes to be how many columns that <div> takes up at that range and up. These numbers jump to 12 when you go below the size before the -. So for our example, as soon as the screen is resized to less than 1025px all three of the columns become 12 columns wide (taking up the entire row, and thusly being stacked on top of each other.) If we refactored like this:

<div class="row">
  <div class="small-4 columns">Left</div>
  <div class="small-4 columns">Middle</div>
  <div class="small-4 columns">Right</div>
</div>

they would remain 4 columns no matter how small the screen was.

What about this code?:

<div class="row">
  <div class="small-6 medium-4 large-3 columns">Left</div>
  <div class="small-6 medium-4 large-6 columns">Middle</div>
  <div class="medium-4 large-3 columns">Right</div>
</div>

Well, at screens wider than 1024px, we have 3,6,3, so the middle <div> will be twice as large as the ones on the sides. Then, between 641-1024px, all three are the same size. Finally, on screens 640px and narrower, the left and the right <div>s are half the screen, and the right columns overflows to be on it's next line. (The small-# class is not defined on the third <div>, so it becomes 12 columns wide.)

The grid system can be a little daunting, but with practice and the documentation at hand, it is manageable.

2.2.2 Installing Foundation

Installing Foundation is as easy as 1 2 3.

1. Download

Go to the Foundation download page and click the blue "Download Foundation CSS" button. It will download the latest version of Foundation in a zip file.

2. Integrate

Unzip the file. Inside, you'll see a couple of files and folders, but the ones we care about are css and js. Remember how we have a css and js folder under the static folder of our Flask app? Copy foundation.css from <Download_Directory>/foundation-x.x.x/css into <Project_Directory>/static/css and foundation.js and vendor from <Download_Directory>/foundation-x.x.x/js into <Project_Directory>/static/js.

Curious what Foundation looks like? You could poke around the Foundation documentation, or open index.html with all the CSS and JS still in the foundation-x.x.x folder. Pretty slick looking!

3. Template

On the Zurb website, they have templates for basic Foundation apps. Here is a basic one that we will use for our project:

<!DOCTYPE html> 
<html lang="en" > 
<head> 
  <meta charset="utf-8"> 
  <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
  <title>Foundation</title> 
  <link rel="stylesheet" href="css/foundation.css"> 
</head> 
<body> 
  
  <!-- body content here --> 

  <script src="js/vendor/jquery.min.js"></script> 
  <script src="js/foundation.js"></script> 
  <script> 
  	$(document).foundation(); 
  </script> 
</body> 
</html>

Apply the boilerplate code above to hello.html while preserving the content:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Reading List App</title>
		<link rel="stylesheet" href="static/css/foundation.css">
  		<link rel="stylesheet" href="static/css/app.css">
	</head>
	<body>
		<h1>Reading List App</h1>
		<p>This web app allows users to search for books and add them to their reading lists!</p>
		<a href="/search"><button class="launch-button">Launch App</button></a>	
	</body>
	<script src="static/js/jquery.min.js"></script>
	<script src="static/js/foundation.js"></script>
	<script> 
    	$(document).foundation(); 
  	</script> 
</html>

Note that we also link to css/app.css, which is for CSS code specific to our app (Never edit any of the Foundation files!). Create an empty file called app.css in the css folder, that we will add code to later:

/* app.css */

2.3 Adding CSS to Our Project

Now that you've learned the basics of CSS and Foundation, let's add some styling to our landing page!

Let's first use Foundation to add some structure to the content. Modify hello.html to wrap the body's content in the row, large-12 columns, and panel classes.

...
<body>
	<div class="row">
		<div class="large-12 columns">
			<div class="panel">
				<h1>Reading List App</h1>
				<p>This web app allows users to search for books and add them to their reading lists!</p>
				<a href="/search"><button>Launch App</button></a>
			</div>
		</div>
	</div>
</body>
...

Now let's write some CSS in app.css to further customize our landing page. We will start by adding a themed background image to our app! Add the following lines to app.css.

body, html {
	height: 100%;
	width: 100%;
}

body {
	background: black url(https://images.unsplash.com/reserve/fvD9myEgRued3zLne2GS__DSC0033-1.jpg?crop=entropy&fit=crop&fm=jpg&h=725&ixjsv=2.1.0&ixlib=rb-0.3.5&q=80&w=1450) no-repeat fixed center center;
}

See what the landing page looks like by visiting localhost:5000. Make sure the background image appears.

Let's fix the styling of our text so that it stands out from the background image. Add the following lines to app.css.

.panel {
	margin-top: 80px;
	text-align: center;
	background-color: rgba(242, 242, 242, 0.9);
	padding: 15px 0px 70px 0px;
}

.panel h1 {
	font-size: 3rem;
}

.panel p {
	font-size: 1.3rem;
	padding: 20px 0px;
}

Lastly, let's make our buttons more interesting. Add the following lines to app.css.

button {
	background-color: #FF9900;
	color: #EEEEEE;
	font-size: 1.5rem;
	font-weight: 500;
}

button:hover {
	background-color: #D77E22;
}

.launch-button {
	margin-top: 25px;
	padding: 30px;
}

.search-button {
	padding: 5px 20px;
}

Now that we've defined the launch-button and search-button classes, use them the HTML! In hello.html:

...
<a href="/search"><button class="launch-button">Launch App</button></a>
...

And in search.html:

...
<button class="postfix search-button" type="submit">Search</button>
...

Great! Now we have a basic landing page styled using CSS and foundation. We will style the rest of our app after we finish implementing some functionality.

style

Top

Level 3: Adding Search Functionality: APIs

Top

3.1 API Basics

If you have not completed the previous levels, download the starter code for level 3 here.

This section will take a step aside from our Flask project to build a foundation of knowledge around APIs and how they are used. We will return to our app in section 3.2.

3.1.1 REST APIs

APIs let us access external data in an easy, standardized way. In the webapp world, when we say API we usually mean REST (or RESTful) API, which can be effectively thought of as an API that is accessible at a series of URL addresses. An extremely simple example of a REST API is placekitten.com, an API that serves images of kitten. Here's how it works. If you point your browser to http://placekitten.com/g/<width>/<height>, it returns a picture of a kitten with that width and height in grayscale. Go to these urls to see a very basic REST API in action.

URL Image
http://placekitten.com/g/200/150 http://placekitten.com/g/200/150
http://placekitten.com/g/300/250 http://placekitten.com/g/200/300

The advantage in using a REST API here is that we don't need to remember the URL of the image we want, just the quality (500x500). Then, using the documentation provided at placekitten.com, we can derive the URL necessary to find the image we want.

Many web designers use placeholder image APIs like placekitten.com or placehold.it in their designs, as they speed up prototyping.

3.1.2 The Anatomy of a URL

From here out we will be using some increasingly complex URLs, and it is important to develop a vocabulary for the parts of the url and their purpose. To do this, we will dissect this url (which we will use in section 3.2 when we work with the Google Books API):

https://www.googleapis.com/books/v1/volumes?q=Treasure

This URL breaks up into six parts:

  1. The protocol (https): We are using the HTTPS protocol, which is a secure version of HTTP, detailed in section 3.1.5.
  2. The separator (://): A colon and two slashes always follow the protocol and are used to separate the protocol and the host.
  3. The subdomain (www.): Not always required.
  4. The host (googleapis.com): A host is usually a domain name (this is the case for our url), but it could also be an IP Address.
  5. The path (/books/v1/volumes): Everything from the first / up to the ? that starts the query string is the path. When accessing a web page, often these paths will be hierarchical and include a filename at the end, like /blog/2014/02/post.html. When making API calls, these paths are the API method that is being called. Here, we are searching books.
  6. The query string (q=Treasure): is a key-value pair of the form <key>=<value>. The query string starts with a ? and can have multiple key-value pairs that are separated by &. In our case we only have one pair.

This URL schema is by no means complete; it encompasses the parts of a URL that are most relevant to API programming. For a more complete view, check out this blog post by Google's Matt Cutts, or this exhaustive Wikipedia entry.

3.1.3 Data in JSON

The data that we usually want to get from a RESTful API is text, not images, and to organize this text we use the JSON, or JavaScript Object Notation text format. JSON is a text format that makes data easy to read and simple to manipulate. Here's a quick rundown:

Most JSON documents start and end with braces ({ }). We'll learn what these are later.

{ }

The core element of JSON documents are key-value pairs. A key is a string, and a value can be (amongst other things) a string. Keys and values are separated by a colon (:).

{ "name": "Jane Doe" }

Whitespace non-significant, and should be added for readability.

{
	"name": "Jane Doe"
}

Multiple key-value pairs can be separated by commas (,).

{
	"name": "Jane Doe",
	"occupation": "student",
	"school": "Columbia University"
}

Values can also be decimal numbers, boolean values (true or false), or null.

{
	"name": "Jane Doe",
	"age": 20,
	"female": true,
	"male": false,
	"occupation": "student",
	"school": "Columbia SEAS",
	"children": null
}

Values can also be arrays. Arrays are comma-separated values surrounded by brackets ([ ]). Values in arrays should be all of the same type, but don't have to be.

{
	"name": "Jane Doe",
	"age": 20,
	"female": true,
	"male": false,
	"occupation": "student",
	"school": "Columbia SEAS",
	"children": null,
	"hobbies": [
		"programming",
		"cello",
		"painting",
		"basketball",
		"REST APIs"
	],
	"luckynumbers" : [
		10, 25.3, 404
	]
}

The final value type is the object. An object is a set of comma-separated key-value pairs surrounded by braces ({ }). In fact, our entire JSON document is one big object.

{
	"name": "Jane Doe",
	"age": 20,
	"female": true,
	"male": false,
	"occupation": "student",
	"school": {
		"fullname": "The Fu Foundation School of Engineering & Applied Science",
		"university": "Columbia University",
		"undergrad": true
	},
	"children": null,
	"hobbies": [
		"programming",
		"cello",
		"painting",
		"basketball",
		"REST APIs"
	],
	"luckynumbers" : [
		10, 25.3, 404
	]
}

Most JSON documents are one big object, but they can also be one big array:

[
    "This",
    "Is",
    {
        "valid": JSON
    }
]

JSON can represent a wide variety of data, just using the simple types:

  • Objects
  • Arrays
  • Strings
  • Numbers
  • Booleans
  • null

3.1.4 Viewing JSON in the Browser

JSON is the most common data format returned by RESTful APIs. For example, colr.org's API returns it's responses in JSON format. Try it: point your browser to http://www.colr.org/json/color/e0d1dd. You'll probably see some unreadable mess like this:

{"colors": [{"timestamp": 1187574833, "hex": "e0d1dd",
"id": 19425, "tags": 	[{"timestamp": 1111913422, "id":
11257, "name": "joyful"}, {"timestamp": 1108110854,
"id": 2798, "name": "lilac"}]}], "schemes": [],
"schemes_history": {}, "success": true, "colors_history":
{"e0d1dd": [{"d_count": 0, "id": "11257", "a_count": 1,
 "name": "joyful"}, {"d_count": 0, "id": "2798",
 "a_count": 1, "name": "lilac"}]}, "messages": [],
 "new_color": "e0d1dd"}

To view JSON in the browser, use a browser extension like JSONView (Chrome, Firefox) or JSON Formatter (Safari). Install the appropriate extension and reload that url. You should now see the JSON with the proper indentation that we like. If that didn't work for you or you're using another browser, copy the mess into jsonprettyprint.com to see it nicely formatted.

It should look like this:

{
  "colors": [
    {
      "timestamp": 1187574833,
      "hex": "e0d1dd",
      "id": 19425,
      "tags": [
        {
          "timestamp": 1111913422,
          "id": 11257,
          "name": "joyful"
        },
        {
          "timestamp": 1108110854,
          "id": 2798,
          "name": "lilac"
        }
      ]
    }
  ],
  "schemes": [],
  "schemes_history": {},
  "success": true,
  "colors_history": {
    "e0d1dd": [
      {
        "d_count": 0,
        "id": "11257",
        "a_count": 1,
        "name": "joyful"
      },
      {
        "d_count": 0,
        "id": "2798",
        "a_count": 1,
        "name": "lilac"
      }
    ]
  },
  "messages": [],
  "new_color": "e0d1dd"
}

So now that we see it nicely formatted, what are we seeing here?

The document is one big object, with keys colors, schemes, schemes_history, success, colors_history, messages, and new_color. The value associated with the key colors is an array containing one color object, which has the keys timestamp, hex, id, and tags. The value associated with the key tags is also an array of objects, where each of these objects is a tag.

So how did we know that the array for colors held color objects and the array for tags holds tag objects? We don't. With JSON, we don't have strictly defined object types like many programming languages do. We have to trust the creator of the JSON document to store their data in a consistent way. Because the JSON we are reading is written well, the tags key has an array value that holds objects that look very similar and have the attributes of a tag. The API call we are making is for one specific color, so it makes sense that array associated with the colors key has only color object in it.

Try out these other API calls (http://colr.org<path>) to see how colr.org used this JSON structure to return different types of results:

Path Description
/json/color/e0d1dd data for the color with hex value e0d1dd
/json/colors/e0d1dd,95604a data for the colors e0d1dd and 95604a
/json/color/random data for random color
/json/colors/random/3 data for three random colors

3.1.5 Extension: HTTP

If you don't remember the client-server model, you should review it before reading this section.

By now, you've seen "HTTP" in every URL in this document. Let's look a little at what "HTTP" actually means.

"HyperText Transfer Protocol" is the protocol by which your browser asks for and receives web sites (and lots of other data). A web server sits around and listens for an HTTP request, which is when a web browser or other client asks for a particular item (ex. web page or image) that the server has. An HTTP request looks something like this:

GET /index.html HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0

There's a lot going on here, but you only need to pay attention to a few parts:

  • GET: this is what we call an HTTP verb. This tells the web server what the client wants it to do. GET means "give me a resource"; POST means "here is some data"; DELETE means "get rid of this resource".

  • /index.html: this indicates the resource that should be acted upon (using the given verb).

  • HTTP/1.1: this is the HTTP version; it's almost always 1.1 (which has been around since 1999).

  • The lines that begin with a word or phrase followed by a colon are called headers. They convey additional information. Host is the header that indicates what site the client wanted to access. User-Agent is the program that you're using as a client; here, it's Firefox.

Then, the server processes the request and sends back an HTTP response:

HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
ETag: "3f80f-1b6-3e1cb03b"
Content-Type: text/html; charset=UTF-8
Content-Length: 131
Connection: close

<html>
<head>
  <title>An Example Page</title>
</head>
<body>
  Hello World, this is a very simple HTML document.
</body>
</html>

The first line includes the HTTP version that the server is using, followed by an HTTP status code and reason. Here, the status is 200 and the reason is OK. This means that all went well.

  • Status codes in the 100s are informational messages; you don't need to worry about them most of the time.
  • Status codes in the 200s mean that the request was processed successfully.
  • Status codes in the 300s mean that the client must do something else in order to complete the request successfully; an example is a redirect message (301).
  • Status codes in the 400s mean that the client did something wrong; for example, code 404 means that the resource the client asked for could not be found by the server.
  • Status codes in the 500s mean that something is wrong with the server; code 500 is a generic error message.

Then, there are a bunch of headers. After that is a blank line, then the body of the response (that is, the actual data requested).

After the body of the response, the server closes the connection. To request another resource, the client must begin the whole process again.

Check out the HTTP requests your browser makes! In Firefox, hit Ctrl-Shift-Q to open the network tool, or in Chrome, hit Ctrl-Shift-I to open Developer tools and navigate to the "Network" tab (Mac users should use Cmd instead). Then, browse the internet a little. Check out here for an example of a successful request, and here for an example of an unsuccessful request.

Top

3.2 Google Books API

In order to search for books using our search route, we'll use the Google Books API. The first step for using any API is to familiarize yourself with its documentation, and so our first stop is https://developers.google.com/books/docs/v1/reference/. We know that we want to search for books, so we'll focus on the "Volume" section.

3.2.1 Determining the Request URL

Before we can try to parse Google Books search data, we need to determine the correct request URL, including the correct query string. We know the protocol, domain, and path. Don't forget the https!

https://www.googleapis.com/books/v1/volumes

Now let's examine the query string piece by piece.

For the q key, we want the search query. For our testing we'll use Treasure Island. We have to encode that string to be URL safe, so we'll use Treasure%20Island (The space character needs to be encoded to %20).

https://www.googleapis.com/books/v1/volumes?q=Treasure%20Island

If you put that URL into your browser, you should see the JSON response!

3.2.2 Using cURL

If you are using Windows, you may have to skip this section. We will be using the cURL program, which is pre-installed on Mac and Linux machines. It is possible to install it on Windows, but we won't walk through that process here.

The most basic way of interaction with an API call programmatically is through the command line. The cURL program runs in Terminal, and is used (most simply) to copy the content at a URL and print it to the screen. First, open Terminal. If you are on a Mac, open Finder, click Applications on the sidebar, open the Utilities folder, and then double click Terminal.

Change directory into your working directory, or the directory where app.py is.

Type the following command:

$ curl https://www.googleapis.com/books/v1/volumes?q=Treasure%20Island

You should see the entire JSON response (probably pretty long!) print to the console. Now lets write that response to a file:

$ curl https://www.googleapis.com/books/v1/volumes?q=Treasure%20Island > response.json

The > response.json section redirects all the output that would normally be sent to the console into the response.json file.

You should now have a new file in your current directory named response.json. If you open that file in your text editor, you'll see the response!

3.2.3 Using Python

Python has several built-in libraries for handling REST APIs, but we will be using the external library Requests. To install it, use Pip:

$ sudo pip install requests

Create a new python file:

$ touch books.py

Editing books.py, start by importing requests.

import requests

Now all we need to to is call requests.get() on the API url we developed in section 3.2.1. This method returns a Response object. Add a print statement to see what the object is.

import requests

url = "https://www.googleapis.com/books/v1/volumes?q=Treasure%20Island"
response = requests.get(url)

print response

If you run this script, you should just see the Response object, represented by it's status code (hopefully 200, or "OK").

$ python books.py
<Response [200]>

The only other thing we need is to get usable data is to convert the response object into a Python dictionary, using the Response object's .json() method. To show that it worked, print it to stdout.

import requests

url = "https://www.googleapis.com/books/v1/volumes?q=Treasure%20Island"
response = requests.get(url)
response_dict = response.json()

print response_dict

This gives us, again a very large dictionary, printed to the screen.

Try exploring the dictionary! JSON has analogous structures in Python: objects become dictionaries, arrays become lists, and everything else converts as you expected. For example, if you modify the print statement like so:

 print response_dict["items"][0]["volumeInfo"]["title"]

You should see it print the title of the first book in the search results.

3.2.4 Using Flask: Extending the Search Route

Adapting our Python code to work in our search route will be pretty simple, but there are a few constraints:

  • We want to search Google Books for the search query that the client sends, not our test string "Treasure Island"
  • We need to return valid HTML in our route, not a Python dictionary or a Response object.

First, ensure that these imports are in your app.py.

from flask import Flask, render_template, request
import requests
...

Then, modify your search route to handle POST and GET requests:

...
@app.route("/search", methods=["POST", "GET"])
...

Now, we can take different actions depending on whether we have a POST request or a GET request.

...
@app.route("/search", methods=["POST", "GET"])
def search():
	if request.method == "POST":
		# User has used the search box
	else: # request.method == "GET"
		# User is loading the page
...

We know that when the method is a GET request, we can simply return the search page that we created earlier.

...
@app.route("/search", methods=["POST", "GET"])
def search():
	if request.method == "POST":
		# User has used the search box
	else: # request.method == "GET"
		return render_template("search.html")
...

For a POST request, we have to make an API call to the Google Books API and return a page containing the results. First, we use request.form["user_search"] to obtain the user query. Then, we use requests.get(url).json() to actually send the GET request and receive a json object back. Then, we can pass this data into a results template with Flask. Coming together, this is what it looks like:

...
@app.route("/search", methods=["POST", "GET"])
def search():
	if request.method == "POST":
		url = "https://www.googleapis.com/books/v1/volumes?q=" + request.form["user_search"]
		response_dict = requests.get(url).json()
		return render_template("results.html", api_data=response_dict)
	else: # request.method == "GET"
		return render_template("search.html")
...

Here the variable api_data has an arbitrary name chosen to be used from within the template. Inside the template, we will refer the the data as api_data, not response_dict. Often these two names will be the same, i.e. response_dict=response_dict, but we'll leave them different for clarity.

We haven't written results.html yet, so the search functionality still isn't complete. Let's learn how to dynamically populate an HTML page with the data we get back from an API.

3.2.5 Extension: Using JavaScript

If you're familiar with JavaScript and would prefer to interact with the Google Books API using JavaScript, we'll go over how to do that in this section. If you don't know JavaScript, feel free to skip it.

Vanilla JavaScript has a way to interact with external APIs, but jQuery, a library of common functions that simplifies your JavaScript, makes everything much easier. To include it, put the following lines into a new file called js_test.html in your working directory:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="script.js"></script>
<p>Open the JavaScript Console!</p>

After this is run, jQuery will be bound to the $ object. All calls beginning with $ are jQuery-related.

jQuery makes selecting DOM elements (that is, the HTML elements on your web page) incredibly simple using selectors: to "select" all of the img elements on your page, do $("img"); to select an element with id example-id, do $("#example-id"). Once an element is selected, you can perform a number of actions with it; a fun one is .fadeOut(). See more at the jQuery API Documentation.

jQuery also lets you perform AJAX (Asynchronous JavaScript and XML) requests easily. This is a mechanism that your JavaScript can use to fetch new data without loading a new page (it's how your Gmail inbox knows you have a new message without you refreshing the page). Create another file called script.js with the following lines:

$.getJSON("https://www.googleapis.com/books/v1/volumes?q=Treasure%20Island", function(data) {
	console.log(data);
});

Here (as before), we fetch all books that match the string "Treasure Island" and are written in JavaScript; we then print it out to the JavaScript console (which can be accessed in the "Developer Tools" for Firefox (Ctrl-Shift-K) or Chrome (Ctrl-Shift-J) (or Cmd for OS X).

Open up test_js.html in your browser and see the response from your JSON request in the JavaScript Console.

A couple of gotchas here:

  • We add the parameter callback=?; this is a JSONP callback to get around the same-origin policy. It's necessary for security reasons; for more information, see those links.

  • We process the data in a callback (that's the function(data) { ... } bit). This is because loading data over the internet takes time; rather than stop everything up while we're waiting for that to finish, JavaScript moves on and executes the next statement. We tell JavaScript what we want it to do once it's finished loading the data; that's the body of the function we pass to getJSON.

That's it! You've successfully queried the Google Books API using JavaScript and jQuery.

3.3 Displaying Search Results

Now that we have JSON data, let's display the relevant books on a results page.

3.3.1 Template Variables Using Jinja2

Now displaying data as JSON is all well and good, but it would be great to display the content as HTML. The problem is, we can't know at the point of writing our HTML what the response will be, so we'll use the more advanced templating features of Flask to dynamically create HTML.

We'll be using the Jinja2 templating engine built into Flask. Using it, we can pass variables from our Python code into templates, essentially allowing us to incorporate variables in our HTML.

First, create a boilerplate HTML5 document called results.html:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Results</title>
	</head>
	<body>
		
	</body>
</html>

Before we can start designing our template, we have to actually pass the variable into the template. We do this in the render_template() function, where (as the documentation indicates) we can pass variables as keyword arguments.

We've passed our data into the HTML document, so now we'll turn results.html into a dynamic template. As a concept, lets have an unordered list of all the results, where each item represents a book with its title, authors, and link.

First, create an unordered list for the results.

...
<body>
	<ul>
	</ul>
</body>
...

Next, we'll iterate over all of the repositories in the items list using Jinja2's for loop syntax, making a list item for each one. We know to do this because of the JSON response structure we saw in section 3.1.4. When render_template() is called on this template, Flask will replace the for loop with multiple instances of whatever is inside the {%%} tags.

In general, Jinja2 code that contains {%%} is for control flow, and will not be displayed literally on the web page.

...
<body>
	<ul>
	{% for book in api_data["items"] %}
		<li></li>
	{% endfor %}
	</ul>
</body>
...

Now lets populate the dynamic list item. We can insert variables into Flask using the {{ variable }} syntax.

...
<ul>
{% for book in api_data["items"] %}
	<li>
		<a href={{ book.accessInfo.webReaderLink }}><h3>{{ book.volumeInfo.title }}</h3></a>
		<h5>
			{% for author in book.volumeInfo.authors %}
				<ul>{{ author }}</ul>
			{% endfor %}
		</h5>
	</li>
{% endfor %}
</ul>
...

Now, visit localhost:5000/search again and submit a search. You should see a bulleted list of results for your search!

<a href={{ book.accessInfo.webReaderLink }}><h3>{{ book.volumeInfo.title }}</h3></a> makes the title of each book a link to its Google Books page. Notice that we also have an inner for loop because there can be multiple authors for any given book.

3.3.2 Extending Templates

Our results page looks great, but what if we want to do another search? Let's put the search form and header at the top of results.html as well.

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Results</title>
	</head>
	<body>
		<h1>Search</h1>
		<form action="/search" method="POST">
			<input type="text" placeholder="Search for a book" id="user_search" name="user_search" required/>
			<button type="submit">Search</button>
		</form>
		<ul>
			{% for book in api_data["items"] %}
				<li>
					<a href={{ book.accessInfo.webReaderLink }}><h3>{{ book.volumeInfo.title }}</h3></a>
					<h5>
						{% for author in book.volumeInfo.authors %}
							<ul>{{ author }}</ul>
						{% endfor %}
					</h5>
				</li>
			{% endfor %}
		</ul>
	</body>
</html>

Noticing a lot of repeated code? You should. search.html and results.html look very similar, and it would be great if we didn't have to reuse code.

We can use template inheritance to reuse one template with another. It's easy! The parent template will be search.html, because it is more simple, and all of the code from it shows up in results.html. In search.html, add a Jinja2 block that the child (results.html) will fill in:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Search</title>
	</head>
	<body>
		<h1>Search</h1>
		<form action="/search" method="POST">
			<input type="text" placeholder="Search for a book" id="user_search" name="user_search" required/>
			<button type="submit">Search</button>
		</form>
		{% block results %}{% endblock %}
	</body>
</html>

Next, we'll make results.html extend, or be a child of search.html. To do this, add the following line at the beginning of results.html:

{% extends search.html %}
<!DOCTYPE html>

Now, we can delete everything that is in search.html from results.html, and just include the contents of our {% block %}. Edit results.html:

{% extends "search.html" %}
{% block results %}
<ul>
{% for book in api_data["items"] %}
	<li>
		<a href={{ book.accessInfo.webReaderLink }}><h3>{{ book.volumeInfo.title }}</h3></a>
		<h5>
			{% for author in book.volumeInfo.authors %}
				<ul>{{ author }}</ul>
			{% endfor %}
		</h5>
	</li>
{% endfor %}
</ul>
{% endblock %}

And that's it! When render_template() is called on results.html, Flask sees that results.html extends search.html, so it renders search.html filling in any {% block %}s that were defined in results.html. When search.html is rendered, the empty {% block %} is ignored.

View it live! You'll see the form persist into the results page, even though results.html doesn't have the form HTML in it.

3.3.3 Base Templates and Style

Now let's take a minute to add Foundation and CSS to the search and results pages. Instead of copy and pasting the Foundation set up over and over again, let's use templating to modularize it!

Create a file called base.html in the /templates directory. Here, we will place all of the code that is applicable to all of the other HTML files and let other files simply extend this one.

<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="utf-8">
  <title>{% block title %}{% endblock %}</title>
  <link rel="stylesheet" href="{{ url_for('static', filename='css/foundation.css') }}">
  <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">

</head>
<body>
  <div class="row">
      <div class="large-12 columns">
          <div class="panel">
              {% block body %}{% endblock %}
          </div>
      </div>
  </div>

  <script src="{{ url_for('static', filename='js/vendor/jquery.min.js') }}"></script>
  <script src="{{ url_for('static', filename='js/foundation.js') }}"></script>
  <script>
  	$(document).foundation();
  </script>
</body>
</html>

Note that we use url_for here to let Flask find the correct files for us.

Now, we can strip the other HTML files to their unique components and let inheritance take care of the common components:

hello.html:

{% extends "base.html" %}
{% block title %} Reading List App {% endblock %}
{% block body %}
    <h1>Reading List App</h1>
    <p>This web app allows users to search for books and add them to their reading lists!</p>
    <a href="/search"><button class="launch-button">Launch App</button></a>
{% endblock %}

search.html:

{% extends "base.html" %}
{% block title %} Search {% endblock %}
{% block body %}
    <h1>Search</h1>
    <form action="/search" method="POST">
        <div class="small-10 columns">
            <input type="text" placeholder="Search for a book" id="user_search" name="user_search" required/>
        </div>
        <div class="small-2 columns">
            <button class="postfix search-button" type="submit">Search</button>
        </div>
    </form>
    {% block results %}{% endblock %}
{% endblock %}

We added some Foundation here in search.html to properly position the search bar and button.

results.html:

{% extends "search.html" %}
{% block results %}
<div class="results">
	<ul>
	{% for book in api_data["items"] %}
		<li>
			<a href={{ book.accessInfo.webReaderLink }}><h3>{{ book.volumeInfo.title }}</h3></a>
			<h5>
				{% for author in book.volumeInfo.authors %}
					<ul>{{ author }}</ul>
				{% endfor %}
			</h5>
		</li>
	{% endfor %}
	</ul>
</div>
{% endblock %}

Finally, let's style the results page. Add the following lines to app.css.

.results {
    text-align: left;
    margin-top: 100px;
}

li {
    list-style-type: none;
    padding-bottom: 25px;
}

Check out the app now at localhost:5000!

results

3.2.5 Extension: Templating Best-Practices

Our results.html template works, but there are some corner cases that we'd be good to cover. We'll be addressing some issues with the user experience of our app. Figuring out that you have user experience problems can be hard to do, but as a rule of thumb, give your app to a few friends and see what they think after using it.

Empty search results

What if there are no results from the search? The items list will be empty, and no <li> elements will be rendered. Lets have a fall back. Using the {% if %} control statement, we can check if the items list exists and isn't empty, and display a message indicating a lack of results otherwise.

{% extends "search.html" %}
{% block results %}
<div class="results">
	{% if api_data["totalItems"] > 0 %}
		<ul>
		{% for book in api_data["items"] %}
			<li>
				<a href={{ book.accessInfo.webReaderLink }}><h3>{{ book.volumeInfo.title }}</h3></a>
				<h5>
					{% for author in book.volumeInfo.authors %}
						<ul>{{ author }}</ul>
					{% endfor %}
				</h5>
			</li>
		{% endfor %}
		</ul>
	{% else %}
		<p>There were no books found.</p>
	{% endif %}
</div>
{% endblock %}

Missing Data Members

What if a book doesn't have an author listed? Or the link is missing? Whenever we use template variables, we should be sure that they exist first, lest the user see some ugly template error instead of your app.

As an exercise, wrap each {{ }} statement in a Jinja2 {% if %}, checking if the variable exists before using it.

Top

Level 4: Storing Favorites: Databases

If you have not completed the previous levels, download the starter code for level 4 here.

Right now, our website allows us to search for books. However, it would be nice if we had a way to save books that looked interesting. Let's work on adding a "Favorites" feature, which will allow us to mark our favorite books.

Top

4.1 Using MongoDB

To store favorites for our application, we're going to be using a database called MongoDB.

4.1.1 What is a database?

In order to store favorites for our app, we'll need to use a database. A database is essentially just an organized collection of data. The data can be anything: bank transactions, songs, restaurant reservations, anything. As long as you have an organized way of representing it, you can store it.

Databases are incredibly common; almost every application that you have ever used probably has a database to store some kind of data. There's a lot of things you can do with a database, but for this tutorial we'll only focus on the basics, which can be remembered by the CRUD acronym: create, read, update, and delete.

4.1.2 What is MongoDB?

There are several different kinds of databases you may have heard of. Some common ones are MySQL, Oracle, and PostgreSQL. However, the one that we're going to be using is MongoDB. Mongo, as it is more commonly known, will allow us to quickly and easily start storing data, which will great for our purposes.

MongoDB is what's called a NoSQL database, which means data isn't stored in the common SQL formatting. Think of SQL data like rows in a spreadsheet: there's a header value for each column of the spreadsheet, and the rows of each spreadsheet provide a value for each of the columns. NoSQL data doesn't use this kind of storage; instead, each object is stored as its own chunk of data and doesn't relate to the other rows in the same way.

4.1.3 Using Flask-Mongoengine

To use MongoDB, Flask provides a really nice add-on called Flask-Mongoengine, which allows us to perform our CRUD operations directly from our Flask app.

To get started using MongoDB, first install it! Follow the instruction guide for your operating system here).

Make sure you also create the necessary /data/db folder and run mongod, as detailed in the instruction guide.

Once you've done that, we'll need to install Flask-Mongoengine. Ensure your virtualenv virtual environment is activated (you should see the name of your directory prepended to your terminal prompt). Then, run the following command:

$ sudo pip install flask-mongoengine

Now that we have our database and Flask library ready to go, let's create a database object for our app to use. Add the following code at the top of your app.py file:

from flask.ext.mongoengine import MongoEngine
...
app.config['MONGODB_SETTINGS'] = { 'db' : 'books' }
...
db = MongoEngine(app)

The import statement brings in the MongoEngine object, which is the basic object we'll be using for our app. Next, below our other app.config changes, we'll add the settings for MongoDB: basically, we just say the database that we'll be using will be called "books". Next, we create an instance of our MongoEngine class, which we'll use later.

Top

4.2 Adding a Model

Now that we have our database all set up, we need to specify what kind of data we're going to be storing. Generally, to specify this, we create a database model, which determines what our data is and how it will be stored. Let's work on creating our model.

4.2.1 Creating a FavoriteBook Model

Since we're storing books that we're marking as favorites, let's create a model called FavoriteBook. To store this, we're going to need three things: the author's name, the name of the book, and the link to the book. We can include all of those things in our model.

In your app.py file, just below the line in which we instantiate db, add the following code to create our model:

class FavoriteBook(db.Document):
    author = db.StringField(required=True)
    title = db.StringField(required=True)
    link = db.StringField(required=True)

(NOTE: generally it's best to not keep models in the same file as all your other logic. We could've also created a new file called models.py and put our code there. However, for the sake of this tutorial, we only have one model, so it makes sense to keep it all in the same file.)

4.2.2 Creating and Saving Objects

Now that we have our model created, we're going to want to actually create a model to save a model as a favorite. To do that, let's first add a button to each of our search results. Below the h5 tag that shows our authors, add the following code to provide a link to do so:

<a href="/favorite/{{ book.id }}">Add to favorites</a>

Our search page should now look like this:

Add to Favs

You'll notice that our link refers to a new route called favorite - to it, we pass the id of the book as a parameter so we know what book we're adding to favorites. Try clicking on the link; you should get a 404 error. That's because we haven't created the /favorite endpoint yet. Let's do that now!

In our app.py file, create a new route and function that looks as follows:

@app.route("/favorite/<id>")
def favorite(id):

You'll notice <id> in the URL route we're intercepting. This is a really cool feature Flask includes; it allows us to take variable parts of a URL and pass them to our route function. So when we pass the ID of the book as to the URL, like we did above, we'll get it as a parameter passes to our function. So, for example, if Flask intercept the route /favorite/12345, when our function is called, id will equal 12345; this allows us to customize what our function does depending on whatever is passed into us.

Now that we have access to our book ID, let's create our model and save it to our database! The following code, added to our favorite function, should do it:

book_url = "https://www.googleapis.com/books/v1/volumes/" + id
book_dict = requests.get(book_url).json()
new_fav = FavoriteBook(author=book_dict["volumeInfo"]["authors"][0], title=book_dict["volumeInfo"]["title"], link=book_url)
new_fav.save()
return render_template("confirm.html", api_data=book_dict)

First, we create the URL for our book, which just appends the ID to our base URL. Then, we query the API with the link, so we have all the information we need. Next, we create our new model; we pass the info from our API results, taking the first author's name, the title of the book, and the Google Books URL. Once we've done this, all we need to is called .save() on our newly created object, and it is saved to our database. It's that easy! Simply create a new object using our default constructor and call the save() method, and our data will be persisted to MongoDB.

Finally, we pass our book data to a new template called confirm.html; create that now to provide a page that confirms that our book was added to favorites. Create a new file called confirm.html and add this to it:

{% extends "base.html" %}
{% block title %} Favorite Added {% endblock %}
{% block body %}
    <h1>Reading List App</h1>
    <p>Thanks for adding {{api_data.volumeInfo.title}} to your favorites.</p>
    <a href="/favorites"><button class="launch-button">View Favorites</button></a>
{% endblock %}

Once we do this, you should see a screen that looks something like this:

Fav Confirm

Now that we're done, we're all done with the functionality to add books to our favorites!

Top

4.3 Viewing Favorites

Now that we've started adding books to our favorites, we'll need a screen to view all the books we've already added to favorites. Let's build that now.

4.3.1 Querying for Objects

We've already seen how to create objects; now, we'll need a way to pull objects out of our database. The code for that will look as follows:

books = FavoriteBook.objects()

It's as simple as that; to get all the FavoriteBook objects we have stored, all we have to do is call the objects() function. We can optionally pass checks we want to do as parameters to this function call. For example, say we want to see whether the user has favorited Great Expectations, we could say book = FavoriteBook.objects(title='Great Expectations'), which would only return the book (or books) that have that as the title. In our case, however, we'll want all of the books.

4.3.2 Presenting a List of Favorites

You may have noticed that, in our confirm.html template, we added a button to allow us to see the list of favorites we've created thus far. For that, we added an endpoint /favorites, which will be our list of books we've identified as favorites. We're going to handle that endpoint now.

To your app.py file, add a route at the bottom, as follows:

@app.route("/favorites")
def favorites():
  favorites = FavoriteBook.objects()
  return render_template("favorites.html", favorites=favorites)

As you'll see, the code is quite simple. We just query for all of our FavoriteBook objects, as we did above, and then pass them to a template called favorites.html. We'll need to create that file, so create a new file with that name in the templates/ directory. It should look as follows:

{% extends "base.html" %}
{% block title %}Favorites{% endblock %}
{% block body %}
<h1>Favorites</h1>
{% if favorites|length > 0 %}
    <ul>
    {% for fav in favorites  %}
        <li>
            <a href={{ fav.link }}><h3>{{ fav.title }}</h3></a>
            <h5><ul>{{ fav.author }}</ul></h5>
        </li>
    {% endfor %}
    </ul>
{% else %}
    <p>You haven't added any favorites yet.</p>
{% endif %}
{% endblock %}

The template looks pretty similar to results. We show a list of each of the favorites, or if there are zero favorites then we show that message. Then, just as we did for the JSON objects, we can refer to the attributes of our model with the Python dot syntax. Thus, to show the title of a book, we simply say fav.title inside the template.

Try adding some books to your favorites. Your page should look something like this:

Favorites List

Top

Level 5: Adding User-Specific Favorites: Sessions & Accounts

If you have not completed the previous levels, download the starter code for level 5 here.

If you add lots of favorites to your list, you'll notice it might get crowded. If you're going to have more than one user, it probably makes sense to have a favorites list for each user of your website; that way, different users can have different favorites. We're going to work on that in this section.

Top

5.1 Using Flask-Login

Generally, to have users log in and out of your application, you need to use what's called a user session. The session, as this Stack Overflow answer describes, essentially passes data back and forth with every request, providing an id that only the client has access to. Generally, the passing of these id's can get pretty messy. Luckily, there's a great extension for Flask called Flask-Login that makes handling these sessions much more manageable.

5.1.1 Installing Flask-Login

Before we begin, we'll need to install our new extension. To do so, run the following command, as we've been doing for each of our new installations:

$ sudo pip install flask-login

Next, we'll need to do some configuration; most importantly, we need to create a login manager object that will work on our behalf.

Open up app.py. At the top, add the following import statement:

from flask.ext.login import LoginManager

Next, just below where we declare app, add the following two lines to create our login manager object:

login_manager = LoginManager()
login_manager.init_app(app)

5.1.2 Creating a User Object

As you can read about in its documentation, Flask-Login requires that we provide a class called User to it; this will represent each user of the service, which the extension will handle the logging in and out of.

The extension also requires us to implement four methods in our User class: is_authenticated, is_active, is_anonymous, and get_id. Each of these methods represents something that the Flask-Login library will need to know to take care of user login.

In app.py, let's add the following User class, just above where we defined FavoriteBook before:

class User(db.Document):
  name = db.StringField(required=True,unique=True)
  password = db.StringField(required=True)
  def is_authenticated(self):
    users = User.objects(name=self.name, password=self.password)
    return len(users) != 0
  def is_active(self):
    return True
  def is_anonymous(self):
    return False
  def get_id(self):
    return self.name

Let's look at the code a little bit more in-depth. First, we add two fields: name and password. This user is going to be pretty simple. Also worth noting, we make sure the name is unique, as this will be our identifying attribute.

Next, we see the implementation of each of the four methods we discussed before. is_active and is_anonymous are boring, and we just return the same thing each time. For is_authenticated, we check our database to see if a user exists that matches the one we currently have; if so, it returns true. For get_id, we simply pass back the name; we need these id's to be unique, which is why we defined the name above to be unique.

5.1.3 Adding the user_loader

Flask-Login requires us to do one more thing: provide a way to load a user. Essentially, we'll need a way to, given an id, load a user. To identify this function, we can use the @login_manager.user_loader decorator so that the extension knows which method to call. Add the code for this ust below our User class definition:

@login_manager.user_loader
def load_user(name):
  users = User.objects(name=name)
  if len(users) != 0:
    return users[0]
  else:
    return None

Once we're done with this, we're all done creating our user objects, and Flask-Login should be all set to use.

Top

5.2 Handling Registration and Login

There are two problems with our above Flask-Login situation: first, there's no page to sign-in, and second, there are no user accounts that we can sign into. To fix these problems, we'll need ways to register and login.

5.2.1 Create a UserForm with WTForms

WTForms is an incredibly useful library that helps us to generate forms that are really easy to validate. Form validation can be an incredibly tedious task. Normally you have to write custom JavaScript to check all sorts of things. Do the passwords match? Is your email address the correct format? Did you fill out all the required inputs? WTForms makes this really easy to do.

Particularly useful is a method called model_form, which automatically creates a form given a MongoEngine model. So, given the User object we just created, creating a form for it can be done in two lines. Add these just under your User class definition:

UserForm = model_form(User)
UserForm.password = PasswordField('password')

(Note: add the following import statements at the top: from flask.ext.mongoengine.wtf import model_form and from wtforms import PasswordField).

Now, we have a new object called UserForm, which represents a form that will gather all of the fields that we have in our User object; how cool is that?! Imagine we had 40 different fields for our User object - UserForm would be a form that had 40 inputs and custom validators to make sure they matched all the restrictions we put on them. The second line simply changes our password field (corresponding to the password object in User) to a PasswordField, so that the text is obfuscated when users type in their passwords.

5.2.2 Creating a Registration Page

Now that we have the form, let's add a new route, /register. Add this anywhere in your app.py file:

@app.route("/register", methods=["POST", "GET"])
def register():
  form = UserForm(request.form)
  if request.method == 'POST' and form.validate():
    form.save()
    return redirect("/login")

  return render_template("register.html", form=form)

First, we create an instance of our UserForm object given the form data passed in our request. If we're posting data, we try to validate it (meaning all of our fields match the conditions we applied to them - i.e. less than the minimum length, unique, etc.). Then, to save the model that our data represents, all we have to do is call form.save(). Think about what that means; we can type 'matt' and 'password', call .save() on the form, and our database automatically has a new User object added to it. This simplicity makes the MongoEngine/WTForms combination very useful. Assuming a successful save, we'll just redirect to the login page (which we'll make next).

In the case that our form isn't ready yet (or we haven't filled it out yet), we need to pass it to the user to be seen! To do that, we created the register.html; create your new file and add this content to it:

{% extends "base.html" %}
{% block title %}Register{% endblock %}
{% block body %}
<h2>Register</h2>

<form action="" method="POST">
  {{ form.csrf_token }}
  <div>
    {{ form.name.label}}
    {{ form.name }}
  </div>
  <div>
    {{ form.password.label }}
    {{ form.password }}
  </div>
  <input type="submit" value="Register">
</form>
{% endblock %}

The template is really easy. We create a new form, and for each of the fields in our form, simply display their label and their actual input field. Then, we add a submit button that says "Register".

Something interesting you may note is the use of the csrf_token. CSRF stands for cross-site registration forgery; it can do damage to your website by allowing people from other sites to post things to your website maliciously. To do that, we use what's called a token that can only be uniquely identified for our application. To do that, add the following config lines at the top of our app.py file:

app.config['SECRET_KEY'] = '<insert random string>'
app.config['WTF_CSRF_ENABLED'] = True

Pick your own secret key, but make sure it's long and unique.

(NOTE: You will have to add redirect to the top from flask import statement.)

Try going to localhost:5000/register. It should look something like this:

Register

Fill out the form and press register; if all goes according to plan, you should be redirected to the /login page, which should give you a 404 error. Let's make that page now.

5.2.3 Creating a Login Page

The login page should end up looking quite similar to the registration page. However, on the backend, we'll need to make a couple changes; we'll need to login a user, rather than saving a new user to our database. For this, we'll make use of the FlaskLogin material we configured in the last step.

Create a new route for /login. Your function should look like this:

@app.route('/login', methods=['GET', 'POST'])
def login():
  form = UserForm(request.form)
  if request.method == 'POST' and form.validate():
    user = User(name=form.name.data,password=form.password.data)
    login_user(user)
    return redirect('/search')

  return render_template('login.html', form=form)

Just like last time, we create a new UserForm (we can reuse the form because, just like registration, it takes a username and password). If our form validates, we create a new user object given our username and password. Then, in a very important line, we call the FlaskLogin built-in function login_user (you'll need to add login_user in our from flask.ext.login line). This function will make use of the other methods we already implemented (is_authenticated, get_id, etc.); if it's successful, we'll continue in our execution, and if not, FlaskLogin will throw an error.

In the case that we have a successful login, we'll be redirected to our /search page, which is the app's homepage. In the case where we haven't posted yet, we'll render login.html, a new template you should create. Its code will look like this:

{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block body %}
<h2>Login</h2>

<form action="" method="POST">
  {{ form.csrf_token }}
  <div>
    {{ form.name.label}}
    {{ form.name }}
  </div>
  <div>
    {{ form.password.label }}
    {{ form.password }}
  </div>
  <input type="submit" value="Login">
</form>
{% endblock %}

The page will look like very similar to our registration page. Try logging in, and you should see the search page.

5.2.4 Adding Logout

Logout is easier, because we don't need a dedicated page. Let's make a /logout route that, when visited, will simply log a user out:

@app.route("/logout")
def logout():
    logout_user()
    return redirect("/")

After you add the logout_user function to your flask.ext.login import statement, you should be able to logout and return to the homepage.

5.2.5 Adding Home Page Buttons

Now that we have registration and login options, let's show our users how to get to them. We can do this by changing the buttons on our home page.

Open up hello.html. Currently, we have a button that says "Launch App":

<a href="/search"><button class="launch-button">Launch App</button></a>

Let's remove this code, adding in its place two buttons, one for register and one for login:

<a href="/login"><button class="launch-button">Login</button></a>
<a href="/register"><button class="launch-button">Register</button></a>

Register Button

Top

5.3 Personalizing Favorites

Now that we have user accounts that we can register and log in and out of, it's time to finish off the job. Let's use the accounts we've created to make sure every user gets her own favorite reading list.

5.3.1 Using @login_required

A pretty useful decorator can help us to make favorites personalized. Any page that you want to be blocked to users that are not logged in can be blocked by simply adding this decorator. For example, let's make it such that only users who are logged in can access the /search page. To do that, I just change the function definition to look as follows:

@app.route("/search", methods=["POST", "GET"])
@login_required
def search():

First, go to localhost:5000/logout to make sure you're logged out. Then, try going to localhost:5000/search; you should see a screen like this:

Unauthorized

This is great! We won't need to start every route we write with an if statement to see if a user is logged in. This is also a big step to making our data sensitive between users. For fullness' sake, add the @login_required decorator to /favorite/<id> and /favorites as well; those pages only make sense if we have a user.

(NOTE: per usual, remember you'll have to import the login_required decorator.)

5.3.2 Adding User to FavoriteBook

If we think back to our FavoriteBook model, we had three attributes: title, author, and link. However, this model pays no attention to who marked it as a favorite. Now that we have the user accounts to support this, we should remember whose favorite book it is. Let's do that by editing the model:

class User(db.Document):
  name = db.StringField(required=True,unique=True)
  password = db.StringField(required=True)
  def is_authenticated(self):
    users = User.objects(name=self.name, password=self.password)
    return len(users) != 0
  def is_active(self):
    return True
  def is_anonymous(self):
    return False
  def get_id(self):
    return self.name

class FavoriteBook(db.Document):
  author = db.StringField(required=True)
  title = db.StringField(required=True)
  link = db.StringField(required=True)
  poster = db.ReferenceField(User)

Our two models will now look like this. You'll notice, at the bottom we added poster = db.ReferenceField(User). A ReferenceField object, as you might guess, references another type of object. In this case, we pass in a User object; there will be a user associated with each FavoriteBook, and we'll call that user the poster.

Just for the sake of emptying out old data, let's remove all the favorite books we have thus far; they don't have posters attached, so it'll be bad when we don't have any user account to show them in.

To do this, let's open the Mongo shell. The shell is essentially a way to perform all the different commands we've been running, like FavoriteBook.objects(), but in a command-line interface. To start, simply type mongo to open up this shell. After it was open, I typed the following commands to remove all my previously recorded FavoriteBook items:

$ use books
switched to db books
$ db.favorite_book.remove({})
WriteResult({ "nRemoved" : 2 })

First, we switch to our books database, which is the name we previously provided in the Flask config. Then, we remove the favorite_book items we already have stored; you'll see I removed 2. To read more about the Mongo shell (you should! it's awesome!), visit this website.

Now that we've removed all our old data, let's update /favorite/<id> to provide our poster object, so we know each favorite book has a user associated with it:

@app.route("/favorite/<id>")
@login_required
def favorite(id):
  book_url = "https://www.googleapis.com/books/v1/volumes/" + id
  book_dict = requests.get(book_url).json()
  poster = User.objects(name=current_user.name).first()
  new_fav = FavoriteBook(author=book_dict["volumeInfo"]["authors"][0], title=book_dict["volumeInfo"]["title"], link=book_url, poster=poster)
  new_fav.save()
  return render_template("confirm.html", api_data=book_dict)

Basically everything stays the same, except we query for the current user object with the call User.objects(name=current_user.name).first(). Be sure to import current_user, as it's a built-in variable for FlaskLogin. We can be sure to return first() because login is required on this page, which means that we will certainly have a user of the name of the currently logged-in user.

Try adding a few favorite books to your account; if everything works normally, then that means we're very close to finishing our app!

5.3.3 Changing our Favorites Page

Now, let's make the final changes to our /favorites page so that we only see favorite books posted by our own account. For the /favorites route, let's change the function to look as follows:

@app.route("/favorites")
@login_required
def favorites():
  current_poster = User.objects(name=current_user.name).first()
  favorites = FavoriteBook.objects(poster=current_poster`)
  return render_template("favorites.html", current_user=current_user, favorites=favorites)

With one small tweak, we only load the FavoriteBook objects who have the poster equal to the current user. Then, to add a little bit more detail, we pass the current_user object to the template; that way, we can make our page a little bit more personalized.

In favorites.html, we changed the <h1> line so that it looks like this:

<h1>{{current_user.name}}'s Favorites</h1>

This way, we can access the name of the current user and show that to the user. To view our finalized product, check out my personalized page, after I added a few favorites:

Personalized Recommendations

Congratulations! You have completed the DevFest 2016 Web Development Track and built a fully-functional reading list app. Now, you can be creative and add more features to the app, change the look of the webpages, or anything you can imagine. You can also build an entirely different web app using the skills you have just learned!

Thanks for reading!

Top

Additional Resources

Along with this tutorial, there is a wealth of information available on Python, Flask, and web development all across the web. Below are some good places to start: