One place for hosting & domains

      Templates

      How To Use Templates in a Flask Application


      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Flask is a lightweight Python web framework that provides useful tools and features for creating web applications in the Python Language.

      When developing a web application, it is important to separate business logic from presentation logic. Business logic is what handles user requests and talks to the database to build an appropriate response. Presentation logic is how the data is presented to the user, typically using HTML files to build the basic structure of the response web page, and CSS styles to style HTML components. For example, in a social media application, you might have a username field and a password field that can be displayed only when the user is not logged in. If the user is logged in, you display a logout button instead. This is the presentation logic. If a user types in their username and password, you can use Flask to perform business logic: You extract the data (the username and password) from the request, log the user in if the credentials are correct or respond with an error message. How the error message is displayed will be handled by the presentation logic.

      In Flask, you can use the Jinja templating language to render HTML templates. A template is a file that can contain both fixed and dynamic content. When a user requests something from your application (such as an index page, or a login page), Jinja allows you to respond with an HTML template where you can use many features that are not available in standard HTML, such as variables, if statements, for loops, filters, and template inheritance. These features allow you to efficiently write easy-to-maintain HTML pages. Jinja also automatically escapes HTML to prevent Cross-Site Scripting (XSS) attacks.

      In this tutorial, you’ll build a small web application that renders several HTML files. You’ll use variables to pass data from the server to the templates. Template inheritance will help you avoid repetition. You’ll use logic in templates such as conditionals and loops, use filters to modify text, and use the Bootstrap toolkit to style your application.

      Prerequisites

      Step 1 — Rendering a Template and Using Variables

      Make sure you have activated your environment and have Flask installed, and then you can start building your application. The first step is to display a message that greets visitors on the index page. You’ll use Flask’s render_template() helper function to serve an HTML template as the response. You will also see how to pass variables from your application side to your templates.

      First, in your flask_app directory, open a file named app.py for editing. Use nano or your favorite text editor:

      Add the following code inside the app.py file:

      flask_app/app.py

      
      from flask import Flask, render_template
      
      app = Flask(__name__)
      
      
      @app.route('/')
      def hello():
          return render_template('index.html')
      

      Save and close the file.

      In this code block, you import the Flask class and the render_template() function from the flask package. You use the Flask class to create your Flask application instance named app. Then you define a view function (which is a Python function that returns an HTTP response) called hello() using the app.route() decorator, which converts a regular function into a view function. This view function uses the render_template() function to render a template file called index.html.

      Next, you’ll have to create the index.html template file in a directory called templates inside your flask_app directory. Flask looks for templates in the templates directory, which is called templates, so the name is important. Make sure you’re inside the flask_app directory and run the following command to create the templates directory:

      Next, open a file called index.html inside the templates directory for editing. The name index.html here is not a standard required name; you can call it home.html or homepage.html or anything else if you want:

      • nano templates/index.html

      Add the following HTML code inside the index.html file:

      flask_app/templates/index.html

      
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>FlaskApp</title>
      </head>
      <body>
          <h1>Hello World!</h1>
          <h2>Welcome to FlaskApp!</h2>
      </body>
      </html>
      

      Here, you set a title, added a Hello World! message as an H1 heading, and created a Welcome to FlaskApp! message as an H2 heading.

      Save and close the file.

      While in your flask_app directory with your virtual environment activated, tell Flask about the application (app.py in your case) using the FLASK_APP environment variable, and set the FLASK_ENV environment variable to development to run the application in development mode and get access to the debugger. Use the following commands to do this (on Windows, use set instead of export):

      • export FLASK_APP=app
      • export FLASK_ENV=development

      Then, run the application using the flask run command:

      With the development server running, visit the following URL using your browser:

      http://127.0.0.1:5000/
      

      You’ll see the title of the page is set to FlaskApp, and the two headings are rendered HTML.

      In web applications, you often need to pass data from your application’s Python files to your HTML templates. To demonstrate how to do this in this application, you will pass a variable containing the current UTC date and time to the index template, and you’ll display the value of the variable in the template.

      Leave the server running, and open your app.py file for editing in a new terminal:

      Import the datetime module from the Python standard library and edit the index() function so the file looks as follows:

      flask_app/app.py

      import datetime
      from flask import Flask, render_template
      
      app = Flask(__name__)
      
      
      @app.route('/')
      def hello():
          return render_template('index.html"https://www.digitalocean.com/community/tutorials/, utc_dt=datetime.datetime.utcnow())
      

      Save and close the file.

      Here you imported the datetime module and passed a variable called utc_dt to the index.html template with the value of datetime.datetime.utcnow(), which is the current UTC date and time.

      Next, to display the variable’s value on the index page, open the index.html file for editing:

      • nano templates/index.html

      Edit the file to look as follows:

      flask_app/templates/index.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>FlaskApp</title>
      </head>
      <body>
          <h1>Hello World!</h1>
          <h2>Welcome to FlaskApp!</h2>
          <h3>{{ utc_dt }}</h3>
      </body>
      </html>
      

      Save and close the file.

      You added an H3 heading with the special {{ ... }} delimiter to print the value of the utc_dt variable.

      Open your browser and visit the index page:

      http://127.0.0.1:5000/
      

      You’ll see a page similar to the following image:

      The Index Page

      You’ve now created an index page with an HTML template in your Flask application, rendered a template, and passed and displayed a variable value. Next you’ll avoid code repetition by using template inheritance.

      Step 2 — Using Template Inheritance

      In this step, you’ll make a base template with content that can be shared with your other templates. You’ll edit your index template to inherit from the base template. Then, you’ll make a new page that will serve as your application’s About page, where users can find more information about your application.

      A base template contains HTML components that are typically shared between all other templates, such as the application’s title, navigation bars, and footers.

      First, open a new file called base.html for editing inside your templates directory:

      Write the following code inside your base.html file:

      flask_app/templates/base.html

      
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{% block title %} {% endblock %} - FlaskApp</title>
          <style>
              nav a {
                  color: #d64161;
                  font-size: 3em;
                  margin-left: 50px;
                  text-decoration: none;
              }
          </style>
      </head>
      <body>
          <nav>
              <a href="#">FlaskApp</a>
              <a href="#">About</a>
          </nav>
          <hr>
          <div class="content">
              {% block content %} {% endblock %}
          </div>
      </body>
      </html>
      

      Save and close the file.

      Most of the code in this file is standard HTML, a title, some styling for the navigation links, a navigation bar with two links, one for the index page and one for the About page not yet created, and a <div> for the page’s content. (The links don’t work yet; the next step will demonstrate how to link between pages).

      However, the following highlighted parts are specific to the Jinja template engine:

      • {% block title %} {% endblock %}: A block that serves as a placeholder for a title. You’ll later use it in other templates to provide a custom title for each page in your application without rewriting the entire <head> section each time.

      • {% block content %} {% endblock %}: Another block that will be replaced by content depending on the child template (a template that inherits from base.html) that will override it.

      Now that you have a base template, you can take advantage of it using inheritance. Open the index.html file:

      • nano templates/index.html

      Then replace its contents with the following:

      flask_app/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Index {% endblock %}</h1>
          <h1>Hello World!</h1>
          <h2>Welcome to FlaskApp!</h2>
          <h3>{{ utc_dt }}</h3>
      {% endblock %}
      

      Here, you use the {% extends %} tag to inherit from the base.html template. You then extend it via replacing the content block in the base template with what is inside the content block in the preceding code block.

      This content block contains an <h1> tag with the text Index inside a title block, which in turn replaces the original title block in the base.html template with the text Index so that the complete title becomes Index - FlaskApp. This way, you can avoid repeating the same text twice, as it works both as a title for the page and a heading that appears below the navigation bar inherited from the base template.

      Then you have a few more headings: one <h1> heading with the text Hello World!, an <h2> heading, and an <h3> heading containing the value of the utc_dt variable.

      Template inheritance gives you the ability to reuse the HTML code you have in other templates (base.html in this case) without having to repeat it each time it is needed.

      Save and close the file and refresh the index page on your browser. The page will look as follows:

      The Index Page After Inheritance

      Next, you’ll create the About page. Open the app.py file to add a new route:

      Add the following route at the end of the file:

      flask_app/app.py

      
      # ...
      @app.route('/about/')
      def about():
          return render_template('about.html')
      

      Here you use the app.route() decorator to create a view function called about(). In it, you return the result of calling the render_template() function with the about.html template file name as an argument.

      Save and close the file.

      Open a template file called about.html for editing:

      • nano templates/about.html

      Add the following code to the file:

      flask_app/templates/about.html

      
      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} About {% endblock %}</h1>
          <h3>FlaskApp is a Flask web application written in Python.</h3>
      {% endblock %}
      

      Here, you inherit from the base template using the extends tag, replace the base template’s content block with an <h1> tag that also serves as the page’s title, and add an <h3> tag with some information about the application.

      Save and close the file.

      With the development server running, visit the following URL using your browser:

      http://127.0.0.1:5000/about
      

      You’ll see a page similar to the following:

      About Page

      Notice how the navigation bar and part of the title are inherited from the base template.

      You’ve now created a base template and used it in your index page and about page to avoid code repetition. The links in the navigation bar don’t do anything at this point. In the next step, you’ll learn how to link between routes in your templates by fixing the navigation bar links.

      Step 3 — Linking between Pages

      In this step, you’ll learn how to link between pages in your templates using the url_for() helper function. You will add two links to the navigation bar in your base template, one for the index page, and one for the About page.

      First open your base template for editing:

      Edit the file to look as follows:

      flask_app/templates/base.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{% block title %} {% endblock %} - FlaskApp</title>
          <style>
              nav a {
                  color: #d64161;
                  font-size: 3em;
                  margin-left: 50px;
                  text-decoration: none;
              }
          </style>
      </head>
      <body>
          <nav>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for('hello') }}">FlaskApp</a>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for('about') }}">About</a>
          </nav>
          <hr>
          <div class="content">
              {% block content %} {% endblock %}
          </div>
      </body>
      </html>
      

      Here, you use the special url_for() function that will return the URL for the view function you give it. The first link links to the route of the hello() view function (which is the index page). The second link links to the route of the about() view function. Notice that you pass the name of the view function, not the route (/ or /about).

      Using the url_for() function to build URLs helps you manage URLs better. If you hard-code URLs, your links will break if you edit the routes. With url_for() you can edit routes and guarantee that the links will still work as expected. The url_for() function also takes care of other things like escaping special characters.

      Save and close the file.

      Now go to the index page and try out the links in the navigation bar. You’ll see that they work as expected.

      You learned how to use the url_for() function to link to other routes in your templates. Next, you will add some conditional statements to control what is displayed in your templates depending on conditions you set, and use for loops in your templates to display list items.

      Step 4 — Using Conditionals and Loops

      In this step, you’ll use if statements in your templates to control what to display depending on certain conditions. You’ll also use for loops to go through Python lists and display each item in the list. You’ll add a new page that displays comments in a list. Comments with an even index number will have a blue background, and comments with an odd index number will be displayed with a gray background.

      First, you will create a route for the comments page. Open your app.py file for editing:

      Add the following route at the end of the file:

      flask_app/app.py

      
      # ...
      
      @app.route('/comments/')
      def comments():
          comments = ['This is the first comment.',
                      'This is the second comment.',
                      'This is the third comment.',
                      'This is the fourth comment.'
                      ]
      
          return render_template('comments.html', comments=comments)
      

      In the route above, you have a Python list called comments that contains four items. (These comments would usually come from a database in a real-world scenario rather than being hard-coded like you’ve done here.) You return a template file called comments.html in the last line, passing a variable called comments containing the list to the template file.

      Save and close the file.

      Next, open a new comments.html file inside the templates directory for editing:

      • nano templates/comments.html

      Add the following code to the file:

      flask_app/templates/comments.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Comments {% endblock %}</h1>
          <div style="width: 50%; margin: auto">
              {% for comment in comments %}
              <div style="padding: 10px; background-color: #EEE; margin: 20px">
                  <p style="font-size: 24px">{{ comment }}</p>
              </div>
              {% endfor %}
          </div>
      {% endblock %}
      

      Here, you extend the base.html template and replace the contents of the content block. First, you use an <h1> heading that also serves as the page’s title.

      You use a Jinja for loop in the line {% for comment in comments %} to go through each comment in the comments list (which gets stored in the comment variable). You display the comment in the <p style="font-size: 24px">{{ comment }}</p> tag the same way you would normally display a variable in Jinja. You signal the ending of the for loop using the {% endfor %} keyword. This is different from the way Python for loops are constructed because there is no special indentation in Jinja templates.

      Save and close the file.

      With the development server running, open your browser and visit the comments page:

      http://127.0.0.1:5000/comments
      

      You will see a page similar to the following:

      Comments Page

      Now you will use the if conditional statement in your templates by displaying comments with an odd index number with a gray background, and comments with an even index number with a blue background.

      Open your comments.html template file for editing:

      • nano templates/comments.html

      Edit it to look as follows:

      flask_app/templates/comments.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Comments {% endblock %}</h1>
          <div style="width: 50%; margin: auto">
              {% for comment in comments %}
                  {% if loop.index % 2 == 0 %}
                      {% set bg_color="#e6f9ff" %}
                  {% else %}
                      {% set bg_color="#eee" %}
                  {% endif %}
      
                  <div style="padding: 10px; background-color: {{ bg_color }}; margin: 20px">
                      <p>#{{ loop.index }}</p>
                      <p style="font-size: 24px">{{ comment }}</p>
                  </div>
              {% endfor %}
          </div>
      {% endblock %}
      

      With this new edit, you added an if statement in the line {% if loop.index % 2 == 0 %}. The loop variable is a special Jinja variable that gives you access to information about the current loop. Here you use loop.index to get the index of the current item, which starts from 1, not 0 as in Python lists.

      The if statement here checks whether the index is even using the % operator. It checks for the remainder of dividing the index number by 2; if the remainder is 0 it means the index number is even, otherwise, the index number is odd. You use the {% set %} tag to declare a variable called bg_color. If the index number is even, you set it to a blueish color, otherwise, if the index number is odd, you set the bg_color variable to gray. You then use the bg_color variable to set a background color for the <div> tag that contains the comment. Above the comment’s text, you use loop.index to display the current index number in a <p> tag.

      Save and close the file.

      Open your browser and visit the comments page:

      http://127.0.0.1:5000/comments
      

      You will see your new Comments page:

      Comments Page With Alternating Background Colors

      This was a demonstration of how to use the if statement. But you can also achieve the same effect by using the special loop.cycle() Jinja helper. To demonstrate this, open the comments.html file:

      • nano templates/comments.html

      Edit it to look as follows:

      flask_app/templates/comments.html

      
      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Comments {% endblock %}</h1>
          <div style="width: 50%; margin: auto">
              {% for comment in comments %}
                  <div style="padding: 10px;
                              background-color: {{ loop.cycle('#EEE', '#e6f9ff') }};
                              margin: 20px">
                      <p>#{{ loop.index }}</p>
                      <p style="font-size: 24px">{{ comment }}</p>
                  </div>
              {% endfor %}
          </div>
      {% endblock %}
      

      Here, you removed the if/else statement and used the loop.cycle('#EEE', '#e6f9ff') helper to cycle between the two colors. The value of background-color will be #EEE one time and #e6f9ff another.

      Save and close the file.

      Open the comments page in your browser, refresh it, and you’ll see that this has the same effect as the if statement.

      You can use if statements for multiple purposes, including controlling what gets displayed on the page. For example, to display all comments except for the second one, you can use an if statement with the condition loop.index != 2 to filter out the second comment.

      Open the comments template:

      • nano templates/comments.html

      And edit it to look as follows:

      flask_app/templates/comments.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Comments {% endblock %}</h1>
          <div style="width: 50%; margin: auto">
              {% for comment in comments %}
                  {% if loop.index != 2 %}
                      <div style="padding: 10px;
                                  background-color: #EEE;
                                  margin: 20px">
                          <p>#{{ loop.index }}</p>
                          <p style="font-size: 24px">{{ comment }}</p>
                      </div>
                  {% endif %}
              {% endfor %}
          </div>
      {% endblock %}
      

      Here, you use {% if loop.index != 2 %} to show only the comments that don’t have the index 2, which means all the comments except for the second one. You also use a hard-coded value for the background color instead of the loop.cycle() helper to make things simpler, and the rest is not changed. You end the if statement using {% endif %}.

      Save and close the file.

      Refresh the comments page and you’ll see that the second comment is not displayed.

      You now need to add a link that takes users to the Comments page in the navigation bar. Open the base template for editing:

      Edit the contents of the <nav> tag by adding a new <a> link to it:

      flask_app/templates/base.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{% block title %} {% endblock %} - FlaskApp</title>
          <style>
              nav a {
                  color: #d64161;
                  font-size: 3em;
                  margin-left: 50px;
                  text-decoration: none;
              }
          </style>
      </head>
      <body>
          <nav>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("hello') }}">FlaskApp</a>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("comments') }}">Comments</a>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("about') }}">About</a>
          </nav>
          <hr>
          <div class="content">
              {% block content %} {% endblock %}
          </div>
      </body>
      </html>
      

      Here, you use the url_for() helper to link to the comments() view function.

      Save and close the file.

      The navigation bar will now have a new link that links to the comments page.

      You used if statements in your templates to control what to display depending on certain conditions. You used for loops to go through Python lists and display each item in the list, and you learned about the special loop variable in Jinja. Next, you’ll use Jinja filters to control how variable data is displayed.

      Step 5 — Using Filters

      In this step, you’ll learn how to use Jinja filters in your templates. You’ll use the upper filter to convert comments you added in the previous step to uppercase, you’ll use the join filter to join a sequence of strings into one string, and you’ll learn how to render trusted HTML code without escaping it using the safe filter.

      First, you will convert the comments in the comments page to uppercase. Open the comments.html template for editing:

      • nano templates/comments.html

      Edit it to look as follows:

      flask_app/templates/comments.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Comments {% endblock %}</h1>
          <div style="width: 50%; margin: auto">
              {% for comment in comments %}
                  {% if loop.index != 2 %}
                      <div style="padding: 10px;
                                  background-color: #EEE;
                                  margin: 20px">
                          <p>#{{ loop.index }}</p>
                          <p style="font-size: 24px">{{ comment | upper }}</p>
                      </div>
                  {% endif %}
              {% endfor %}
          </div>
      {% endblock %}
      

      Here, you added the upper filter using the pipe symbol (|). This will modify the value of the comment variable to be uppercase.

      Save and close the file.

      With the development server running, open the comments page with your browser:

      http://127.0.0.1:5000/comments
      

      You can see that the comments are all in uppercase after applying the filter.

      Filters can also take arguments in parentheses. To demonstrate this, you’ll use the join filter to join all the comments in the comments list.

      Open the comments template:

      • nano templates/comments.html

      Edit it to look as follows:

      flask_app/templates/comments.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Comments {% endblock %}</h1>
          <div style="width: 50%; margin: auto">
              {% for comment in comments %}
                  {% if loop.index != 2 %}
                      <div style="padding: 10px;
                                  background-color: #EEE;
                                  margin: 20px">
                          <p>#{{ loop.index }}</p>
                          <p style="font-size: 24px">{{ comment | upper }}</p>
                      </div>
                  {% endif %}
              {% endfor %}
              <hr>
              <div>
                  <p>{{ comments | join(" | ") }}</p>
              </div>
          </div>
      {% endblock %}
      

      Here you added an <hr> tag and a <div> tag where you join all the comments in the comments list using the join() filter.

      Save and close the file.

      Refresh the comments page and you’ll see a page similar to the following:

      Comments Page With Join Filter

      As you can see, the comments list is displayed with the comments separated by a pipe symbol, which is what you passed to the join() filter.

      Another important filter is the safe filter, which allows you to render trusted HTML on the browser. To illustrate this, you’ll add some text containing an HTML tag to your comments template using the {{ }} Jinja delimiter. In a real-world scenario, this would come as a variable from the server. Then you’ll edit the join() argument to be the <hr> tag instead of the pipe symbol.

      Open the comments template:

      • nano templates/comments.html

      Edit it to look as follows:

      flask_app/templates/comments.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Comments {% endblock %}</h1>
          <div style="width: 50%; margin: auto">
              {% for comment in comments %}
                  {% if loop.index != 2 %}
                      <div style="padding: 10px;
                                  background-color: #EEE;
                                  margin: 20px">
                          <p>#{{ loop.index }}</p>
                          <p style="font-size: 24px">{{ comment | upper }}</p>
                      </div>
                  {% endif %}
              {% endfor %}
              <hr>
              <div>
                  {{ "<h1>COMMENTS</h1>" }}
                  <p>{{ comments | join("https://www.digitalocean.com/community/tutorials/ <hr> ") }}</p>
              </div>
          </div>
      {% endblock %}
      

      Here, you added the value "<h1>COMMENTS</h1>" and changed the join argument to the <hr> tag.

      Save and close the file.

      Refresh the comments page and you’ll see a page similar to the following:

      Comments Page With No Safe Filter

      As you can see, the HTML tags were not rendered. This is a safety feature in Jinja, because some HTML tags can be harmful and may result in a Cross Site Scripting (XSS) attack. You should allow only trusted HTML to be rendered in the browser.

      To render the HTML tags above, open the comments template file:

      • nano templates/comments.html

      Edit it by adding the safe filter:

      flask_app/templates/comments.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Comments {% endblock %}</h1>
          <div style="width: 50%; margin: auto">
              {% for comment in comments %}
                  {% if loop.index != 2 %}
                      <div style="padding: 10px;
                                  background-color: #EEE;
                                  margin: 20px">
                          <p>#{{ loop.index }}</p>
                          <p style="font-size: 24px">{{ comment | upper }}</p>
                      </div>
                  {% endif %}
              {% endfor %}
              <hr>
              <div>
                  {{ "<h1>COMMENTS</h1>"https://www.digitalocean.com/community/tutorials/ | safe }}
                  <p>{{ comments | join(" <hr> ") | safe }}</p>
              </div>
          </div>
      {% endblock %}
      

      You can see that you can also chain filters like in the line <p>{{ comments | join(" <hr> ") | safe }}</p>. Each filter gets applied to the result of the previous filtering.

      Save and close the file.

      Refresh the comments page and you’ll see that the HTML tags are now rendered as expected:

      Comments Page With Safe Filter

      Warning: Using the safe filter on HTML from unknown data sources may open up your application to XSS attacks. Do not use it unless the HTML you are rendering is from a trusted source.

      For more information, check out the list of built-in Jinja filters.

      You have now learned how to use filters in your Jinja templates to modify variable values. Next, you’ll integrate the Bootstrap toolkit to style your application.

      Step 6 — Integrating Bootstrap

      In this step, you’ll learn how to use the Bootstrap toolkit to style your application. You’ll add a Bootstrap navigation bar in the base template that will appear in all the pages that inherit from the base template.

      The Bootstrap toolkit helps you style your application so it is more visually appealing. It will also help you incorporate responsive web pages in your web application so that it works well on mobile browsers without writing your own HTML, CSS, and JavaScript code to achieve these goals.

      To use Bootstrap, you’ll need to add it to the base template so you can use it in all other templates.

      Open your base.html template, for editing:

      Edit it to look as follows:

      flask_app/templates/base.html

      <!doctype html>
      <html lang="en">
        <head>
          <!-- Required meta tags -->
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
      
          <!-- Bootstrap CSS -->
          <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
      
          <title>{% block title %} {% endblock %} - FlaskApp</title>
        </head>
        <body>
          <nav class="navbar navbar-expand-lg navbar-light bg-light">
          <div class="container-fluid">
              <a class="navbar-brand" href="https://www.digitalocean.com/community/tutorials/{{ url_for("hello') }}">FlaskApp</a>
              <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
              </button>
              <div class="collapse navbar-collapse" id="navbarNav">
              <ul class="navbar-nav">
                  <li class="nav-item">
                    <a class="nav-link" href="https://www.digitalocean.com/community/tutorials/{{ url_for("comments') }}">Comments</a>
                  </li>
                  <li class="nav-item">
                    <a class="nav-link" href="https://www.digitalocean.com/community/tutorials/{{ url_for("about') }}">About</a>
                  </li>
              </ul>
              </div>
          </div>
          </nav>
          <div class="container">
              {% block content %} {% endblock %}
          </div>
      
          <!-- Optional JavaScript -->
      
          <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script>
      
        </body>
      </html>
      

      Most of the code above is Bootstrap boilerplate required to use it. You have some meta tags, a link to the Bootstrap CSS file in the <head> section, and at the bottom you have a link to optional JavaScript. The highlighted parts of the code contain Jinja code explained in the previous steps. Notice how you use specific tags and CSS classes to tell Bootstrap how to display each element.

      In the <nav> tag above, you have an <a> tag with the class navbar-brand, which determines the brand link in the navigation bar. Inside the <ul class="navbar-nav"> tag, you have regular navigation bar items inside an <a> tag in an <li> tag.

      To learn more about these tags and CSS classes, see the Bootstrap components.

      Save and close the file.

      With the development server running, open the index page with your browser:

      http://127.0.0.1:5000/
      

      You’ll see a page similar to the following:

      Index Page with Bootstrap

      You can now use Bootstrap components to style items in your Flask application in all of your templates.

      Conclusion

      You now know how to use HTML templates in your Flask web application. You’ve used variables to pass data from the server to templates, employed template inheritance to avoid repetition, incorporated elements such as if conditionals and for loops, and linked between different pages. You learned about filters to modify text and display trusted HTML, and you integrated Bootstrap into your application.

      If you would like to read more about Flask, check out the Flask topic page.



      Source link

      How To Create and Use Templates in Ansible Playbooks



      Part of the Series:
      How To Write Ansible Playbooks

      Ansible is a modern configuration management tool that doesn’t require the use of an agent software on remote nodes, using only SSH and Python to communicate and execute commands on managed servers. This series will walk you through the main Ansible features that you can use to write playbooks for server automation. At the end, we’ll see a practical example of how to create a playbook to automate setting up a remote Nginx web server and deploy a static HTML website to it.

      Templates allow you to create new files on the nodes using predefined models based on the Jinja2 templating system. Ansible templates are typically saved as .tpl files and support the use of variables, loops, and conditional expressions.

      Templates are commonly used to configure services based on variable values that can be set up on the playbook itself, in included variable files, or obtained via facts. This enables you to create more versatile setups that adapt behavior based on dynamic information.

      To try it out this feature with a practical example, create a new directory to hold non-playbook files inside your ansible-practice directory:

      • mkdir ~/ansible-practice/files

      Next, create a new template file for an HTML landing page. Later on, we’ll set up a playbook which will configure your remote nodes to serve the landing page with Nginx:

      • nano ~/ansible-practice/files/landing-page.html.j2

      Add the following content to the template file:

      ~/ansible-practice/files/landing-page.html.j2

      <!doctype html>
      <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>{{ page_title }}</title>
        <meta name="description" content="Created with Ansible">
      </head>
      <body>
          <h1>{{ page_title }}</h1>
          <p>{{ page_description }}</p>
      </body>
      </html>
      

      Save and close the file when you’re done.

      This template uses two variables that must be provided whenever the template is applied in a playbook: page_title and page_description.

      The following playbook sets up the required variables, installs Nginx, and then applies the specified template to replace the existing, default Nginx landing page located at /var/www/html/index.nginx-debian.html. The last task uses the ufw module to enable tcp access on port 80, in case you have your firewall enabled as recommended in our initial server setup guide.

      Create a new file called playbook-11.yml in your ansible-practice directory:

      • nano ~/ansible-practice/playbook-11.yml

      Add the following content to the new playbook file:

      ~/ansible-practice/playbook-11.yml

      ---
      - hosts: all
        become: yes
        vars:
          page_title: My Landing Page
          page_description: This is my landing page description.
        tasks:
          - name: Install Nginx
            apt:
              name: nginx
              state: latest
      
          - name: Apply Page Template
            template:
              src: files/landing-page.html.j2
              dest: /var/www/html/index.nginx-debian.html
      
          - name: Allow all access to tcp port 80
            ufw:
              rule: allow
              port: '80'
              proto: tcp    
      

      Remember to provide the -K option if you run this playbook, since it requires sudo permissions:

      • ansible-playbook -i inventory playbook-11.yml -u sammy -K

      Output

      BECOME password: PLAY [all] ********************************************************************************************** TASK [Gathering Facts] ********************************************************************************** ok: [203.0.113.10] TASK [Install Nginx] ************************************************************************************ changed: [203.0.113.10] TASK [Apply Page Template] ****************************************************************************** changed: [203.0.113.10] TASK [Allow all access to tcp port 80] ****************************************************************** changed: [203.0.113.10] PLAY RECAP ********************************************************************************************** 203.0.113.10 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      When the play has finished, you can access the web server’s public IP address from your browser. You’ll see a page like this:

      Screenshot showing custom landing page

      That means your playbook worked as expected, and the default Nginx page was replaced by the template you have created.



      Source link

      How To Create Reusable Infrastructure with Terraform Modules and Templates


      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      One of the main benefits of Infrastructure as Code (IAC) is reusing parts of the defined infrastructure. In Terraform, you can use modules to encapsulate logically connected components into one entity and customize them using input variables you define. By using modules to define your infrastructure at a high level, you can separate development, staging, and production environments by only passing in different values to the same modules, which minimizes code duplication and maximizes conciseness.

      You are not limited to using only your custom modules. Terraform Registry is integrated into Terraform and lists modules and providers that you can incorporate in your project right away by defining them in the required_providers section. Referencing public modules can speed up your workflow and reduce code duplication. If you have a useful module and would like to share it with the world, you can look into publishing it on the Registry for other developers to use.

      In this tutorial, we’ll consider some of the ways of defining and reusing code in Terraform projects. You’ll reference modules from the Terraform Registry, separate development and production environments using modules, learn about templates and how they are used, and how to specify resource dependencies explicitly using the depends_on meta argument.

      Prerequisites

      • A DigitalOcean Personal Access Token, which you can create via the DigitalOcean control panel. You can find instructions to do that at: How to Generate a Personal Access Token.
      • Terraform installed on your local machine and a project set up with the DigitalOcean provider. Complete Step 1 and Step 2 of the How To Use Terraform with DigitalOcean tutorial and be sure to name the project folder terraform-reusability, instead of loadbalance. During Step 2, do not include the pvt_key variable and the SSH key resource.
      • The droplet-lb module available under modules in terraform-reusability. Follow the How to Build a Custom Module tutorial and work through it until the droplet-lb module is functionally complete. (That is, until the cd ../.. command in the Creating a Module section.)
      • Knowledge of Terraform project structuring approaches. For more information, see How To Structure a Terraform Project.
      • (Optional) Two separate domains whose nameservers are pointed to DigitalOcean at your registrar. Refer to the How To Point to DigitalOcean Nameservers From Common Domain Registrars tutorial to set this up. Note that you don’t need to do this if you don’t plan on deploying the project you’ll create through this tutorial.

      Note: We have specifically tested this tutorial using Terraform 0.13.

      Separating Development and Production Environments

      In this section, you’ll use modules to achieve separation between your target deployment environments. You’ll arrange these according to the structure of a more complex project. You’ll first create a project with two modules, one of which will define the Droplets and Load Balancers, and the other one will set up the DNS domain records. After, you’ll write configuration for two different environments (dev and prod), which will call the same modules.

      Creating the dns-records module

      As part of the prerequisites, you have set up the project initially under terraform-reusability and created the droplet-lb module in its own subdirectory under modules. You’ll now set up the second module, called dns-records, containing variables, outputs, and resource definitions. Assuming you’re in terraform-reusability, create dns-records by running:

      • mkdir modules/dns-records

      Navigate to it:

      This module will comprise the definitions for your domain and the DNS records that you’ll later point to the Load Balancers. You’ll first define the variables, which will become inputs that this module will expose. You’ll store them in a file called variables.tf. Create it for editing:

      Add the following variable definitions:

      terraform-reusability/modules/dns-records/variables.tf

      variable "domain_name" {}
      variable "ipv4_address" {}
      

      Save and close the file. You’ll now define the domain and the accompanying A and CNAME records in a file named records.tf. Create and open it for editing by running:

      Add the following resource definitions:

      terraform-reusability/modules/dns-records/records.tf

      resource "digitalocean_domain" "domain" {
        name = var.domain_name
      }
      
      resource "digitalocean_record" "domain_A" {
        domain = digitalocean_domain.domain.name
        type   = "A"
        name   = "@"
        value  = var.ipv4_address
      }
      
      resource "digitalocean_record" "domain_CNAME" {
        domain = digitalocean_domain.domain.name
        type   = "CNAME"
        name   = "www"
        value  = var.ipv4_address
      }
      

      First, you define the domain in your DigitalOcean account for your domain name. The cloud will automatically add the three DigitalOcean nameservers as NS records. Then, you define an A record for your domain, routing it (the @ as value signifies the true domain name, without subdomains) to the IP address supplied as the variable ipv4_address. For the sake of completeness, the CNAME record that follows specifies that the www subdomain should also point to the same IP address. Save and close the file when you’re done.

      Next, you’ll define the outputs for this module. The outputs will show the FQDN (fully qualified domain name) of the created records. Create and open outputs.tf for editing:

      Add the following lines:

      terraform-reusability/modules/dns-records/outputs.tf

      output "A_fqdn" {
        value = digitalocean_record.domain_A.fqdn
      }
      
      output "CNAME_fqdn" {
        value = digitalocean_record.domain_CNAME.fqdn
      }
      

      Save and close the file when you’re done.

      With the variables, DNS records, and outputs defined, the last thing you’ll need to specify are the provider requirements for this module. You’ll specify that the dns-records module requires the digitalocean provider in a file called provider.tf. Create and open it for editing:

      Add the following lines:

      terraform-reusability/modules/dns-records/provider.tf

      terraform {
        required_providers {
          digitalocean = {
            source = "digitalocean/digitalocean"
          }
        }
        required_version = ">= 0.13"
      }
      

      When you’re done, save and close the file. The dns-records module now requires the digitalocean provider and is functionally complete.

      Creating Different Environments

      The following is the current structure of the terraform-reusability project:

      terraform_reusability/
      ├─ modules/
      │  ├─ dns-records/
      │  │  ├─ outputs.tf
      │  │  ├─ provider.tf
      │  │  ├─ records.tf
      │  │  ├─ variables.tf
      │  ├─ droplet-lb/
      │  │  ├─ droplets.tf
      │  │  ├─ lb.tf
      │  │  ├─ outputs.tf
      │  │  ├─ provider.tf
      │  │  ├─ variables.tf
      ├─ main.tf
      ├─ provider.tf
      

      So far, you have two modules in your project: the one you just created (dns-records) and droplet-lb, which you created as part of the prerequisites.

      To facilitate different environments, you’ll store the dev and prod environment config files under a directory called environments, which will reside in the root of the project. Both environments will call the same two modules, but with different parameter values. The advantage of this is when the modules change internally in the future, you’ll only need to update the values you are passing in.

      First, navigate to the root of the project by running:

      Then, create the dev and prod directories under environments at the same time:

      • mkdir -p environments/dev && mkdir environments/prod

      The -p argument orders mkdir to create all directories in the given path.

      Navigate to the dev directory, as you’ll first configure that environment:

      You’ll store the code in a file named main.tf, so create it for editing:

      Add the following lines:

      terraform-reusability/environments/dev/main.tf

      module "droplets" {
        source   = "../../modules/droplet-lb"
      
        droplet_count = 2
        group_name    = "dev"
      }
      
      module "dns" {
        source   = "../../modules/dns-records"
      
        domain_name   = "your_dev_domain"
        ipv4_address  = module.droplets.lb_ip
      }
      

      Here you call and configure the two modules, droplet-lb and dns-records, which will together result in the creation of two Droplets. They’re fronted by a Load Balancer; the DNS records for the supplied domain are set up to point to that Load Balancer. Remember to replace your_dev_domain with your desired domain name for the dev environment, then save and close the file.

      Next, you’ll configure the DigitalOcean provider and create a variable for it to be able to accept the personal access token you’ve created as part of the prerequisites. Open a new file, called provider.tf, for editing:

      Add the following lines:

      terraform-reusability/environments/dev/provider.tf

      terraform {
        required_providers {
          digitalocean = {
            source = "digitalocean/digitalocean"
            version = "1.22.2"
          }
        }
      }
      
      variable "do_token" {}
      
      provider "digitalocean" {
        token = var.do_token
      }
      

      In this code, you require the digitalocean provider to be available and pass in the do_token variable to its instance. Save and close the file.

      Initialize the configuration by running:

      You’ll receive the following output:

      Output

      Initializing modules... - dns in ../../modules/dns-records - droplets in ../../modules/droplet-lb Initializing the backend... Initializing provider plugins... - Finding latest version of digitalocean/digitalocean... - Installing digitalocean/digitalocean v2.0.2... - Installed digitalocean/digitalocean v2.0.2 (signed by a HashiCorp partner, key ID F82037E524B9C0E8) Partner and community providers are signed by their developers. If you'd like to know more about provider signing, you can read about it here: https://www.terraform.io/docs/plugins/signing.html The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, we recommend adding version constraints in a required_providers block in your configuration, with the constraint strings suggested below. * digitalocean/digitalocean: version = "~> 2.0.2" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.

      The configuration for the prod environment is similar. Navigate to its directory by running:

      Create and open main.tf for editing:

      Add the following lines:

      terraform-reusability/environments/prod/main.tf

      module "droplets" {
        source   = "../../modules/droplet-lb"
      
        droplet_count = 5
        group_name    = "prod"
      }
      
      module "dns" {
        source   = "../../modules/dns-records"
      
        domain_name   = "your_prod_domain"
        ipv4_address  = module.droplets.lb_ip
      }
      

      The difference between this and your dev code is that there will be five Droplets deployed. Furthermore, the domain name, which you should replace with your prod domain name, will be different. Save and close the file when you’re done.

      Then, copy over the provider configuration from dev:

      Initialize this configuration as well:

      The output of this command will be the same as the previous time you ran it.

      You can try planning the configuration to see what resources Terraform would create by running:

      • terraform plan -var "do_token=${DO_PAT}"

      The output for prod will be the following:

      Output

      ... An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.dns.digitalocean_domain.domain will be created + resource "digitalocean_domain" "domain" { + id = (known after apply) + name = "your_prod_domain" + urn = (known after apply) } # module.dns.digitalocean_record.domain_A will be created + resource "digitalocean_record" "domain_A" { + domain = "your_prod_domain" + fqdn = (known after apply) + id = (known after apply) + name = "@" + ttl = (known after apply) + type = "A" + value = (known after apply) } # module.dns.digitalocean_record.domain_CNAME will be created + resource "digitalocean_record" "domain_CNAME" { + domain = "your_prod_domain" + fqdn = (known after apply) + id = (known after apply) + name = "www" + ttl = (known after apply) + type = "CNAME" + value = (known after apply) } # module.droplets.digitalocean_droplet.droplets[0] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "prod-0" ... } # module.droplets.digitalocean_droplet.droplets[1] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "prod-1" ... } # module.droplets.digitalocean_droplet.droplets[2] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "prod-2" ... } # module.droplets.digitalocean_droplet.droplets[3] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "prod-3" ... } # module.droplets.digitalocean_droplet.droplets[4] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "prod-4" ... } # module.droplets.digitalocean_loadbalancer.www-lb will be created + resource "digitalocean_loadbalancer" "www-lb" { ... + name = "lb-prod" ... Plan: 9 to add, 0 to change, 0 to destroy. ...

      This would deploy five Droplets with a Load Balancer. Also it would create the prod domain you specified with the two DNS records pointing to the Load Balancer. You can try planning the configuration for the dev environment as well—you’ll note that two Droplets would be planned for deployment.

      Note: You can apply this configuration for the dev and prod environments with the following command:

      • terraform apply -var "do_token=${DO_PAT}"

      The following demonstrates how you have structured this project:

      terraform_reusability/
      ├─ environments/
      │  ├─ dev/
      │  │  ├─ main.tf
      │  │  ├─ provider.tf
      │  ├─ prod/
      │  │  ├─ main.tf
      │  │  ├─ provider.tf
      ├─ modules/
      │  ├─ dns-records/
      │  │  ├─ outputs.tf
      │  │  ├─ provider.tf
      │  │  ├─ records.tf
      │  │  ├─ variables.tf
      │  ├─ droplet-lb/
      │  │  ├─ droplets.tf
      │  │  ├─ lb.tf
      │  │  ├─ outputs.tf
      │  │  ├─ provider.tf
      │  │  ├─ variables.tf
      ├─ main.tf
      ├─ provider.tf
      

      The addition is the environments directory, which holds the code for the dev and prod environments.

      The benefit of this approach is that further changes to modules automatically propagate to all areas of your project. Barring any possible customizations to module inputs, this approach is not repetitive and promotes reusability as much as possible, even across deployment environments. Overall this reduces clutter and allows you to trace the modifications using a version-control system.

      In the final two sections of this tutorial, you’ll review the depends_on meta argument and the templatefile function.

      Declaring Dependencies to Build Infrastructure in Order

      While planning actions, Terraform automatically tries to sense existing dependencies and builds them into its dependency graph. The main dependencies it can detect are clear references; for example, when an output value of a module is passed to a parameter on another resource. In this scenario the module must first complete its deployment to provide the output value.

      The dependencies that Terraform can’t detect are hidden—they have side effects and mutual references not inferable from the code. An example of this is when an object depends not on the existence, but on the behavior of another one, and does not access its attributes from code. To overcome this, you can use depends_on to manually specify the dependencies in an explicit way. Since Terraform 0.13, you can also use depends_on on modules to force the listed resources to be fully deployed before deploying the module itself. It’s possible to use the depends_on meta argument with every resource type. depends_on will also accept a list of other resources on which its specified resource depends.

      In the previous step of this tutorial, you haven’t specified any explicit dependencies using depends_on, because the resources you’ve created have no side effects not inferable from the code. Terraform is able to detect the references made from the code you’ve written, and will schedule the resources for deployment accordingly.

      depends_on accepts a list of references to other resources. Its syntax looks like this:

      resource "resource_type" "res" {
        depends_on = [...] # List of resources
      
        # Parameters...
      }
      

      Remember that you should only use depends_on as a last-resort option. If used, it should be kept well documented, because the behavior that the resources depend on may not be immediately obvious.

      Using Templates for Customization

      In Terraform, templating is substituting results of expressions in appropriate places, such as when setting attribute values on resources or constructing strings. You’ve used it in the previous steps and the tutorial prerequisites to dynamically generate Droplet names and other parameter values.

      When substituting values in strings, the values are specified and surrounded by ${}. Template substitution is often used in loops to facilitate customization of the created resources. It also allows for module customization by substituting inputs in resource attributes.

      Terraform offers the templatefile function, which accepts two arguments: the file from the disk to read and a map of variables paired with their values. The value it returns is the contents of the file rendered with the expression substituted—just as Terraform would normally do when planning or applying the project. Because functions are not part of the dependency graph, the file cannot be dynamically generated from another part of the project.

      Imagine that the contents of the template file called droplets.tmpl is as follows:

      %{ for address in addresses ~}
      ${address}:80
      %{ endfor ~}
      

      Longer declarations must be surrounded with %{}, as is the case with the for and endfor declarations, which signify the start and end of the for loop respectively. The contents and type of the droplets variable are not known until the function is called and actual values provided, like so:

      templatefile("${path.module}/droplets.tmpl", { addresses = ["192.168.0.1", "192.168.1.1"] })
      

      The value that this templatefile call will return is the following:

      Output

      192.168.0.1:80 192.168.1.1:80

      This function has its use cases, but they are uncommon. For example, you could use it when a part of the configuration is necessary to exist in a proprietary format, but is dependent on the rest of the values and must be generated dynamically. In the majority of cases, it’s better to specify all configuration parameters directly in Terraform code, where possible.

      Conclusion

      In this article, you’ve maximized code reuse in an example Terraform project. The main way is to package often-used features and configurations as a customizable module and use it whenever needed. By doing so, you do not duplicate the underlying code (which can be error prone) and enable faster turnaround times, since modifying the module is almost all you need to do to introduce changes.

      You’re not limited to your own modules. As you’ve seen, Terraform Registry provides third-party modules and providers that you can incorporate in your project.

      Check out the rest of the How To Manage Infrastructure with Terraform series.



      Source link