Get AI summaries of any video or article — Sign up free
Python Flask Tutorial: Full-Featured Web App Part 2 - Templates thumbnail

Python Flask Tutorial: Full-Featured Web App Part 2 - Templates

Corey Schafer·
5 min read

Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Create a templates/ directory and move full HTML documents into home.html and about.html instead of returning raw HTML strings from routes.

Briefing

Flask templates turn messy, repeated HTML strings into maintainable web pages—and the biggest upgrade comes from template inheritance, which lets one shared “layout” control the look of every route. Instead of returning raw HTML from route functions, the app creates a templates/ directory with home.html and about.html. Each route then uses Flask’s render_template to pull in a full HTML document structure from disk, so the browser receives complete pages (doctype, html, head, body) rather than a single heading tag.

Once templates are in place, the workflow shifts from hardcoded markup to dynamic rendering. Dummy blog-post data is defined as a list of dictionaries (author, title, content, date posted) and passed into templates via render_template. Inside the template, Jinja—Flask’s templating engine—iterates over that list using for loops and prints fields with double-curly-brace expressions like {{ post.title }} and {{ post.author }}. The result is server-side HTML generation: the page source shows the loop expanded into repeated sections for each post, not placeholders.

Templates also support conditional logic. A title variable can be provided to a template; if it exists, the page title becomes “Flask Blog - <title>,” and if it doesn’t, the template falls back to a default. This conditional behavior is used across both home and about pages: home relies on the default, while about passes title=about to produce a distinct browser tab title.

The major maintainability win arrives when home.html and about.html share lots of identical head and layout code. Rather than duplicating that repeated structure in every template, a new layout.html acts as the parent template. It contains the common HTML skeleton and a named block (block content) that child templates override. home.html and about.html then extend layout.html using Jinja’s extends mechanism and fill only the unique portion inside the content block. Functionally, the pages still render the same full HTML, but updates now happen in one place.

That single shared layout becomes the control center for site-wide styling. Bootstrap is added by copying its CDN-based CSS and JavaScript into layout.html. A container wrapper and a navigation bar are inserted into the shared structure, and a custom main.css stylesheet is introduced for additional styling. To link CSS correctly, the app uses Flask’s url_for to generate the proper path to static/main.css. After that, the homepage’s post rendering is refined by swapping the basic markup inside the posts loop for a dedicated article.html snippet, which formats each post more cleanly.

By the end, the app demonstrates a full template pipeline: render_template for page generation, Jinja loops and conditionals for dynamic content, and template inheritance for scalable design. With the layout in place, switching the site’s framework (like adding Bootstrap) or adjusting global UI elements requires editing one file, not every route template—setting up the project for the next step: building and validating forms for real user input instead of dummy data.

Cornell Notes

Flask templates replace hardcoded HTML strings with reusable HTML files stored in a templates/ directory. Routes use render_template to render home.html and about.html, and Jinja lets templates loop over passed-in data (e.g., a list of post dictionaries) and print fields like title, author, content, and date posted. Templates also support conditionals to set the browser tab title with a default when no title variable is provided. To eliminate duplicated markup across pages, a parent layout.html defines shared structure and a block content that child templates override using extends. This makes site-wide changes—like adding Bootstrap, navigation, and global CSS—manageable by editing only layout.html.

Why does returning raw HTML from Flask routes become a problem, and how do templates fix it?

Raw HTML returned directly from route functions quickly becomes unmanageable because every route needs a full HTML document structure (doctype, html/head/body) and repeated code multiplies maintenance work. Templates move that HTML into separate files under a templates/ directory. Routes then return render_template('home.html') or render_template('about.html'), letting Flask load the full page structure from disk so the route code stays small and consistent.

How does Flask pass dynamic data into a template, and how does Jinja render it?

Dynamic data is passed as keyword arguments to render_template. For example, a variable posts is set to a list of dictionaries (each dictionary representing a blog post). In the template, a Jinja for loop iterates over posts using syntax like {% for post in posts %}. Inside the loop, double-curly-brace expressions print values such as {{ post.title }}, {{ post.author }}, {{ post.date_posted }}, and {{ post.content }}. The rendered HTML shows the loop expanded into repeated sections in the page source.

What conditional behavior is implemented for page titles, and where does it show up?

Templates use Jinja if/else blocks to choose a title. If a title variable is provided, the page title becomes something like “Flask Blog - <title>”; otherwise it falls back to a default “Flask Blog.” This affects the browser tab title because the logic is placed in the HTML head section, and it’s demonstrated by not passing title for home but passing title='about' for the about page.

What is template inheritance in Jinja, and how does layout.html reduce duplication?

Template inheritance uses a parent template (layout.html) that contains shared HTML and defines a block content. Child templates declare {% extends 'layout.html' %} and override the content block with only what’s unique to that page. This removes repeated head/body structure from home.html and about.html. The pages still render full HTML, but global changes (like updating the default title, navigation, or CSS links) happen in layout.html only.

How are Bootstrap and custom styles integrated across all pages?

Bootstrap is added by inserting its CDN CSS and JavaScript references into layout.html, so every page inheriting from layout.html automatically gains the styling. A navigation bar and a main section are also placed in the shared layout. Custom styling goes into static/main.css, and layout.html links to it using url_for('static', filename='main.css') so Flask generates the correct URL. After that, the homepage post markup is improved by replacing the basic loop output with an article.html snippet.

Review Questions

  1. How would you modify the template to display only posts with a specific author name?
  2. What changes would be required if you wanted to add a new shared block besides block content in layout.html?
  3. Where in the template should you place logic that affects the browser tab title, and why?

Key Points

  1. 1

    Create a templates/ directory and move full HTML documents into home.html and about.html instead of returning raw HTML strings from routes.

  2. 2

    Use render_template in Flask route functions to render templates and pass variables into templates as keyword arguments.

  3. 3

    Use Jinja for loops ({% for ... %}) and variable interpolation ({{ ... }}) to render repeated dynamic content like blog posts.

  4. 4

    Add Jinja if/else conditionals to implement defaults (such as a default page title when no title variable is provided).

  5. 5

    Eliminate duplicated markup by using template inheritance: put shared structure in layout.html and override only block content in child templates.

  6. 6

    Integrate site-wide styling by adding Bootstrap and global navigation/CSS to layout.html so every inherited page updates automatically.

  7. 7

    Link static assets like main.css using url_for('static', filename='main.css') to avoid hardcoding paths.

Highlights

Passing a list of post dictionaries into render_template lets Jinja expand a for loop into repeated HTML sections visible in the page source.
Template inheritance with layout.html and block content turns global UI changes (like Bootstrap) into single-file edits instead of per-page updates.
Jinja conditionals in the head section dynamically set the browser tab title, with a default when no title variable is passed.
Using url_for for static files keeps CSS/JS links correct without manual path management.
Swapping the homepage’s post markup to an article.html snippet improves formatting while keeping the loop logic intact.

Topics

Mentioned

  • Jinja