App Templating API
Last Updated: May 2017
The pages of a Tethys app are created using the Django template language. This provides an overview of important Django templating concepts and introduces the base templates that are provided to make templating easier.
Django Templating Concepts
The Django template language allows you to create dynamic HTML templates and minmizes the amount of HTML you need to write for your app pages. This section will provide a crash course in Django template language basics, but we highly recommend a review of the Django Template Language documentation.
Tip
Review the Django Template Language to get a better grasp on templating in Tethys.
Variables
In Django templates, variables are denoted by double curly brace syntax: {{ variable }}
. The variable expression will be replaced by the value of the variable. Dot notation can be used access attributes of a variable: {{ variable.attribute }}
.
Examples:
# Examples of Django template variable syntax
{{ variable }}
# Access items in a list or tuple using dot notation
{{ list.0 }}
# Access items in a dictionary using dot notation
{{ dict.key }}
# Access attributes of objects using dot notation
{{ object.attribute }}
Hint
See Django template Variables documentation for more information.
Filters
Variables can be modified by filters which look like this: {{ variable|filter:argument }}
. Filters perform modifying functions on variable output such as formatting dates, formatting numbers, changing the letter case, and concatenating multiple variables.
Examples:
# The default filter can be used to print a default value when the variable is falsy
{{ variable|default:"nothing" }}
# The join filter can be used to join a list with a the separator given
{{ list|join:", " }}
Hint
Refer to the Django Filter Reference for a full list of the filters available.
Template Inheritance
One of the advantages of using the Django template language is that it provides a method for child templates to extend parent templates, which can reduce the amount of HTML you need to write. Template inheritance is accomplished using two tags, extends
and block
. Parent templates provide blocks
of content that can be overridden by child templates. Child templates can extend parent templates by using the extends
tag. Calling the block
tag of a parent template in a child template will override any content in that block
tag with the content in the child template.
Hint
The Django Template Inheritance documentation provides an excellent example that illustrates how inheritance works.
Base Templates
There are two layers of templates provided for Tethys app development. The app_base.html
or any of its derivatives (See Additional Base Templates) from which all Tethys apps inherit, and the base.html
at the app level from which all pages in an app project can inherit.
The app_base.html
template provides the HTML skeleton for all Tethys app templates, which includes the base HTML structural elements (e.g.: <html>
, <head>
, and <body>
elements), the base style sheets and JavaScript libraries, and many blocks for customization.
All Tethys app projects also include a base.html
template that inherits from the app_base.html
template.
App developers are encouraged to use the base.html
file as the base template for all of their templates within an app, rather than extending the app_base.html
file directly. The base.html
template is easier to work with, because it includes only the blocks that will be used most often from the app_base.html
template or its derivatives (See Additional Base Templates). However, all of the blocks that are available from app_base.html
template or its selected derivative template will also be available for use in the base.html
template and any templates that extend it.
Many of the blocks in the template correspond with different portions of the app interface. Figure 1 provides a graphical explanation of these blocks. An explanation of all the blocks provided in the app_base.html
and base.html
templates can be found in the section that follows.
Blocks
This section provides an explanation of the blocks are available for use in child templates of either the app_base.html
or the base.html
templates.
htmltag
Override the <html>
element open tag.
Example:
{% block htmltag %}<html lang="es">{% endblock %}
headtag
Add attributes to the <head>
element.
Example:
{% block headtag %}style="display: block;"{% endblock %}
meta
Override or append <meta>
elements to the <head>
element. To append to existing elements, use block.super
.
Example:
{% block meta %}
{{ block.super }}
<meta name="description" value="My website description" />
{% endblock %}
title
Change title for the page. The title is used as metadata for the site and shows up in the browser in tabs and bookmark names.
Example:
{% block title %}My Sub Title{% endblock %}
links
Add content before the stylesheets such as rss feeds and favicons. Use block.super
to preserve the default favicon or override completely to specify custom favicon.
Example:
{% block links %}
<link rel="shortcut icon" href="/path/to/favicon.ico" />
{% endblock %}
import_gizmos
The import_gizmos block allows you register gizmos to be added to your page so that the dependencies load properly.
Example:
{% block import_gizmos %}
{% import_gizmo_dependency map_view %}
{% endblock %}
styles
Add additional stylesheets to the page. Use block.super
to preserve the existing styles for the app (recommended) or override completely to use your own custom stylesheets.
Example:
{% block styles %}
{{ block.super }}
<link href="/path/to/styles.css" rel="stylesheet" />
{% endblock %}
global_scripts
Add JavaScript libraries that need to be loaded prior to the page being loaded. This is a good block to use for libraries that are referenced globally. The global libraries included as global scripts by default are JQuery and Bootstrap. Use block.super
to preserve the default global libraries.
Example:
{% block global_scripts %}
{{ block.super }}
<script src="/path/to/script.js" type="text/javascript"></script>
{% endblock %}
bodytag
Add attributes to the body
element.
Example:
{% block bodytag %}class="a-class" onload="run_this();"{% endblock %}
app_content_wrapper_override
Override the app content structure completely. The app content wrapper contains all content in the <body>
element other than the scripts. Use this block to override all of the app template structure completely.
Override Eliminates:
app_header_override, app_navigation_toggle_override, app_icon_override, app_icon, app_title_override, app_title, exit_button_override, app_content_override, flash, app_navigation_override, app_navigation, app_navigation_items, app_content, app_actions_override, app_actions.
Example:
{% block app_content_wrapper_override %}
<div>
<p>My custom content</p>
</div>
{% endblock %}
app_header_override
Override the app header completely including any wrapping elements. Useful for creating a custom header for your app.
Override Eliminates:
app_navigation_toggle_override, app_icon_override, app_icon, app_title_override, app_title, exit_button_override
app_icon_override
Override the app icon in the header completely including any wrapping elements.
Override Eliminates:
app_icon
app_icon
Override the app icon <img>
element in the header.
Example:
{% block app_icon %}<img src="/path/to/icon.png">{% endblock %}
app_title_override
Override the app title in the header completely including any wrapping elements.
Override Eliminates:
app_title
app_title
Override the app title element in the header.
Example:
{% block app_title %}My App Title{% endblock %}
app_content_override
Override only the app content area while preserving the header. The navigation and actions areas will also be overridden.
Override Eliminates:
flash, app_navigation_override, app_navigation, app_navigation_items, app_content, app_actions_override, app_actions
flash
Override the flash messaging capabilities. Flash messages are used to display dismissible messages to the user using the Django messaging capabilities. Override if you would like to implement your own messaging system or eliminate functionality all together.
app_content
Add content to the app content area. This should be the primary block used to add content to the app.
Example:
{% block app_content %}
<p>Content for my app.</p>
{% endblock %}
after_app_content
Use this block for adding elements after the app content such as Bootstrap modals (Bootstrap modals will not work properly if they are placed in the main app_content
block).
Example:
{% block after_app_content %}
{% gizmo my_modal %}
{% endblock %}
app_actions_override
Override app content elements including any wrapping elements.
app_actions
Override or append actions to the action area. These are typically buttons or links. The actions are floated right, so they need to be listed in right to left order.
Example:
{% block app_actions %}
<a href="" class="btn btn-secondary">Next</a>
<a href="" class="btn btn-secondary">Back</a>
{% endblock %}
scripts
Add additional JavaScripts to the page. Use block.super
to preserve the existing scripts for the app (recommended) or override completely to use your own custom scripts.
Example:
{% block scripts %}
{{ block.super }}
<script href="/path/to/script.js" type="text/javascript"></script>
{% endblock %}
base.html
The base.html
is the base template that is used directly by app templates. This file is generated in all new Tethys app projects that are created using the scaffold. The contents are provided here for reference.
All of the blocks provided by the base.html
template are inherited from the app_base.html
template. The base.html
template is intended to be a simplified version of the app_base.html
template, providing only the the blocks that should be used in a default app configuration. However, the blocks that are excluded from the base.html
template can be used by advanced Tethys app developers who wish customize parts or all of the app template structure.
See the Blocks section for an explanation of each block.
{% extends "tethys_apps/app_base.html" %}
{% load static %}
{% block title %}{{ tethys_app.name }}{% endblock %}
{% block app_icon %}
{# The path you provided in your app.py is accessible through the tethys_app.icon context variable #}
<img src="{% if 'http' in tethys_app.icon %}{{ tethys_app.icon }}{% else %}{% static tethys_app.icon %}{% endif %}" />
{% endblock %}
{# The name you provided in your app.py is accessible through the tethys_app.name context variable #}
{% block app_title %}{{ tethys_app.name }}{% endblock %}
{% block app_navigation_items %}
<li class="nav-item title">App Navigation</li>
<li class="nav-item"><a class="nav-link active" href="">Home</a></li>
<li class="nav-item"><a class="nav-link" href="">Jobs</a></li>
<li class="nav-item"><a class="nav-link" href="">Results</a></li>
<li class="nav-item title">Steps</li>
<li class="nav-item"><a class="nav-link" href="">1. The First Step</a></li>
<li class="nav-item"><a class="nav-link" href="">2. The Second Step</a></li>
<li class="nav-item"><a class="nav-link" href="">3. The Third Step</a></li>
<li class="nav-item separator"></li>
<li class="nav-item"><a class="nav-link" href="">Get Started</a></li>
{% endblock %}
{% block app_content %}
{% endblock %}
{% block app_actions %}
{% endblock %}
{% block content_dependent_styles %}
{{ block.super }}
<link href="{% static 'my_first_app/css/main.css' %}" rel="stylesheet"/>
{% endblock %}
{% block scripts %}
{{ block.super }}
<script src="{% static 'my_first_app/js/main.js' %}" type="text/javascript"></script>
{% endblock %}
app_base.html
This section provides the complete contents of the app_base.html
template. It is meant to be used as a reference for app developers, so they can be aware of the HTML structure underlying their app templates.
{% load static app_theme tethys_gizmos %}
<!DOCTYPE html>
{# Allows custom attributes to be added to the html tag #}
{% block htmltag %}
<!--[if IE 7]> <html lang="en" class="ie ie7 h-100"> <![endif]-->
<!--[if IE 8]> <html lang="en" class="ie ie8 h-100"> <![endif]-->
<!--[if IE 9]> <html lang="en" class="ie9 h-100"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en" class="h-100"> <!--<![endif]-->
{% endblock %}
{# Allows custom attributes to be added to the head tag #}
<head {% block headtag %}{% endblock %}>
{% if has_analytical %}
{% include "analytical_head_top.html" %}
{% endif %}
{% comment "meta explanation" %}
Add custom meta tags to the page. Call block.super to get the default tags
such as charset, viewport and generator.
Example:
{% block meta %}
{{ block.super }}
<meta name="description" value="My website description" />
{% endblock %}
{% endcomment %}
{% block meta %}
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="generator" content="Django" />
{% endblock %}
{% comment "title explanation" %}
Add a custom title to the page by extending the title block. Call block.super
to get the default page title.
Example:
{% block title %}My Subtitle - {{ block.super }}{% endblock %}
{% endcomment %}
<title>
{% block title %}{{ tethys_app.name }}{% endblock %}
</title>
{% comment "links explanation" %}
The links block allows you to add additional content before the stylesheets
such as rss feeds and favicons in the same way as the meta block.
{% endcomment %}
{% block links %}
<link rel="shortcut icon" href="{% if site_globals.favicon and 'http' in site_globals.favicon %}{{ site_globals.favicon }}{% elif site_globals.favicon %}{% static site_globals.favicon %}{% else %}{% static 'tethys_portal/images/default_favicon.png' %}{% endif %}" />
{% endblock %}
{% comment "import_gizmos explanation" %}
The import_gizmos block allows you register gizmos to be added to your
page so that the dependencies load properly.
Example:
{% block import_gizmos %}
{% import_gizmo_dependency map_view %}
{% endblock %}
{% endcomment %}
{% block import_gizmos %}
{% endblock %}
{% comment "styles explanation" %}
The styles block allows you to add additional stylesheets to the page in
the same way as the meta block. Use block.super to include the default
stylesheets before or after your own.
Example:
{% block styles %}
{{ block.super }}
<link href="{% static 'custom/css/foo.css' %}" rel="stylesheet" />
{% endblock %}
{% endcomment %}
{% block styles %}
{{ tethys.bootstrap.link_tag|safe }}
{{ tethys.bootstrap_icons.link_tag|safe }}
{% block app_base_styles %}
<link href="{% static 'tethys_apps/css/app_base.min.css' %}" rel="stylesheet" />
{% endblock %}
{% if tethys_app.enable_feedback %}
<link href="{% static 'tethys_apps/css/feedback.css' %}" rel="stylesheet" />
{% endif %}
<link href="{% static 'tethys_portal/css/termsandconditions.min.css' %}" rel="stylesheet" />
{% gizmo_dependencies global_css %}
{% endblock %}
{% block app_styles %}
<style>
:root {
--app-primary-color: {{ tethys_app.color }};
--app-secondary-color: {{ tethys_app.color|lighten:20 }};
}
#app-header .tethys-app-header {
background: var(--app-primary-color, '#7ec1f7');
}
#app-header .tethys-app-header .icon-wrapper img {
background: var(--app-primary-color, '#7ec1f7');
}
#app-navigation .nav li a {
color: var(--app-primary-color, '#7ec1f7');
}
#app-navigation .nav li.active a {
color: var(--app-primary-color, '#7ec1f7');
}
#app-navigation .nav li a.active {
background: var(--app-primary-color, '#7ec1f7');
color: white;
}
#app-content-wrapper #app-content #app-actions {
background: var(--app-secondary-color);
}
</style>
{% endblock %}
{% block global_scripts %}
{{ tethys.jquery.script_tag|safe }}
{{ tethys.bootstrap.script_tag|safe }}
{% gizmo_dependencies global_js %}
{% endblock %}
{% if has_session_security %}
{% block session_timeout_modal %}
{% include 'session_security/all.html' %}
<link href="{% static 'tethys_portal/css/session_security_override.min.css' %}" rel="stylesheet" />
{% endblock %}
{% endif %}
{% if has_analytical %}
{% include "analytical_head_bottom.html" %}
{% endif %}
</head>
{# Allows custom attributes to be added to the body tag #}
<body {% block bodytag %}class="h-100"{% endblock %}>
{% if has_analytical %}
{% include "analytical_body_top.html" %}
{% endif %}
{% block app_content_wrapper_override %}
<div id="app-content-wrapper" class="{% block show_nav_override %}show-nav{% endblock %}">
{% block app_header_override %}
<div id="app-header" class="clearfix">
<div class="tethys-app-header">
<div id="nav-title-wrapper">
{% block app-navigation-toggle-override %}
<a href="javascript:void(0);" class="toggle-nav">
<div></div>
<div></div>
<div></div>
</a>
{% endblock %}
{% block app_icon_override %}
<div class="icon-wrapper">
{% block app_icon %}<img src="{% if tethys_app.icon %}{% if 'http' in tethys_app.icon %}{{ tethys_app.icon }}{% else %}{% static tethys_app.icon %}{% endif %}{% else %}{% static 'tethys_apps/images/default_app_icon.gif' %}{% endif %}" />{% endblock %}
</div>
{% endblock %}
{% block app_title_override %}
<div class="app-title-wrapper">
<span class="app-title">{% block app_title %}{{ tethys_app.name }}{% endblock %}</span>
</div>
{% endblock %}
</div>
{% block header_buttons_override %}
<div id="header-buttons-wrapper">
{% block header_buttons %}
{% endblock %}
{% block login_button_override %}
{% if not request.user.is_authenticated %}
<div class="header-button login-button">
<a href="javascript:void(0);" onclick="TETHYS_APP_BASE.exit_app('{% url 'accounts:login' %}?next={{request.path}}');"data-bs-toggle="tooltip" data-bs-placement="bottom" title="Log In">Log In</a>
</div>
{% endif %}
{% endblock %}
{% block settings_button_override %}
{% if request.user.is_staff %}
<div class="header-button settings-button">
<a href="javascript:void(0);" onclick="TETHYS_APP_BASE.exit_app('{% url 'admin:index' %}tethys_apps/tethysapp/{{ tethys_app.id }}/change/');" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Settings"><i class="bi bi-gear"></i></a>
</div>
{% endif %}
{% endblock %}
{% block exit_button_override %}
<div class="header-button exit-button">
<a href="javascript:void(0);" onclick="TETHYS_APP_BASE.exit_app('{% url 'app_library' %}');"data-bs-toggle="tooltip" data-bs-placement="bottom" title="Exit"><i class="bi bi-x"></i></a>
</div>
{% endblock %}
</div>
{% endblock %}
</div>
</div>
{% endblock %}
{% block app_content_override %}
<div id="app-content">
{# Off canvas navigation menu #}
{% block app_navigation_override %}
<div id="app-navigation">
{% block app_navigation %}
<ul class="nav nav-pills flex-column">
{% block app_navigation_items %}{% endblock %}
</ul>
{% endblock %}
</div>
{% endblock %}
{# App content starts here #}
{% block inner_app_content %}
<div id="inner-app-content">
{% block app_content %}{% endblock %}
{# App actions are fixed to the bottom #}
{% block app_actions_override %}
<div id="app-actions">
{% block app_actions %}{% endblock %}
<div id="app-actions-spacer"></div>
</div>
{% endblock %}
</div>
{% endblock %}
</div>
{% endblock %}
</div>
{% endblock %}
{% comment "after_app_content explanation" %}
Use this block for adding elements after the app content such as
bootstrap modals.
Example:
{% block after_app_content %}
{% gizmo my_modal %}
{% endblock %}
{% endcomment %}
{% block after_app_content %}
{% block modals %}
{% endblock %}
{% endblock %}
{% block gizmo_modals%}
{% gizmo_dependencies modals %}
{% endblock %}
{% block flash %}
{% if messages %}
<div class="flash-messages">
{% comment "flash_messages explanation" %}
Use the flash messages block to display temporary feedback to the user. Pass
a list of dictionaries called "flash_messages". Each dictionary should have the
keys "category" and "text". The category can be any of: "success", "info",
"warning", and "danger". The category is used to style the message. The text is
the text of the message to be displayed. The alerts that will be displayed are
dismissible. To create custom alerts, override the "flash" block.
{% endcomment %}
{% for message in messages %}
<div class="alert {% if message.tags %}{{ message.tags }}{% endif %} alert-dismissible fade show mx-auto" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}
{% block terms-of-service-override %}
{% if has_terms %}
{% include "terms.html" %}
{% endif %}
{% endblock %}
{% block page_attributes_override %}
<div id="page-attributes" data-username="{{ user.username }}" style="display: none;"></div>
{% endblock %}
{% block content_dependent_styles %}
{% gizmo_dependencies css %}
{% endblock %}
{% block csrf_token %}
{% csrf_token %}
{% endblock %}
{% comment "scripts explanation" %}
Use this block for adding scripts. Call with block.super to include the default
scripts.
Example:
{% block scripts %}
{{ block.super }}
<script type="text/javascript" src="{% static 'custom/js/bar.js' %}"></script>
{% endblock %}
{% endcomment %}
{% block scripts %}
{{ tethys.doc_cookies.script_tag|safe }}
{% block app_base_js %}
<script src="{% static 'tethys_apps/js/app_base.js' %}" type="text/javascript"></script>
{% endblock %}
{% if tethys_app.enable_feedback %}
<script src="{% static 'tethys_apps/js/feedback.js' %}" type="text/javascript"></script>
{% endif %}
{% gizmo_dependencies js %}
{% endblock %}
{% if has_analytical %}
{% include "analytical_body_bottom.html" %}
{% endif %}
</body>
</html>
Additional Base Templates
Additional templates that inherit from the app_base.html
template have been added to Tethys to facilitate app customization. These templates include:
app_content_only.html
This template contains only the app content. Code referencing displays block other than the app_content
block will have no effect on this template. Override
, JavaScript
, and Style
blocks retain their regular behavior.
app_header_content.html
This template contains only the header and app content. Code referencing any display block other than the app_content
block or blocks contained in the app header (app_icon
, app_title
, or header_buttons
) will have no effect on this template. Override
, JavaScript
, and Style
blocks retain their regular behavior.
app_no_actions.html
This template is the same as normal app_base.html
, but with no app actions section. Code referencing the app_actions
block will have no effect on this template. Other blocks retain their regular behavior.
app_left_actions.html
This template is the same as app_header_content.html
with the actions bar on left.
app_right_actions.html
This template is the same as app_header_content.html
with the actions bar on right.
app_quad_split.html
This template is the same as app_header_content.html
but with a 2 x 2 Bootstrap Grid in the content area.
Instead of an app_content
block, this app uses the following four blocks:
app_content_tl: The app content that will be displayed in the top left section of the 2 x 2 grid.
app_content_tr: The app content that will be displayed in the top right section of the 2 x 2 grid.
app_content_bl: The app content that will be displayed in the bottom left section of the 2 x 2 grid.
app_content_br: The app content that will be displayed in the bottom right section of the 2 x 2 grid.
Example:
{% block app_content_tl %}
<p>Top left content for my app.</p>
{% endblock %}
{% block app_content_tr %}
<p>Top right content for my app.</p>
{% endblock %}
{% block app_content_bl %}
<p>Bottom left content for my app.</p>
{% endblock %}
{% block app_content_br %}
<p>Bottom right content for my app.</p>
{% endblock %}
app_three_columns.html
This template is the same as app_header_content.html
but with a three-column Bootstrap Grid in the content area.
Instead of an app_content
block, this app uses the following three blocks:
app_content_lc: The app content that will be displayed in the left column of the three-column grid.
app_content_mc: The app content that will be displayed in the middle column of the three-column grid.
app_content_rc: The app content that will be displayed in the right column of the three-column grid.
Example:
{% block app_content_lc %}
<p>Left column content for my app.</p>
{% endblock %}
{% block app_content_tr %}
<p>Middle column content for my app.</p>
{% endblock %}
{% block app_content_bl %}
<p>Right column content for my app.</p>
{% endblock %}
app_two_columns.html
This template is the same as app_header_content.html
but with a two-column Bootstrap Grid in the content area.
Instead of an app_content
block, this app uses the following two blocks:
app_content_lc: The app content that will be displayed in the left column of the two-column grid.
app_content_rc: The app content that will be displayed in the right column of the two-column grid.
Example:
{% block app_content_lc %}
<p>Left column content for my app.</p>
{% endblock %}
{% block app_content_bl %}
<p>Right column content for my app.</p>
{% endblock %}