Notification Templates¶
Keystone issues automated notifications alerting users to system changes and platform events. Administrators can customize these notifications using templates to reflect organization-specific branding and messaging.
Overriding Templates¶
Keystone renders HTML notifications using the Jinja2 templating engine. The location of custom templates is configurable via application settings.
When a notification is triggered, Keystone first checks for a custom template file. If no custom template is available, Keystone falls back to an internal default. The selected template is then rendered using the context data outlined below.
Email notification templates can be rendered and written to disk using the keystone-api render_templates command.
It is strongly recommended to develop and test templates locally before deploying them to a production environment.
See keystone-api render_templates --help for details.
Template Security¶
Template files are required to have no write permissions for users other than the owner or group (i.e., o+w).
If a template file has world-writeable permissions, Keystone will refuse to load it.
This restriction is provided for security and ensures templates cannot be modified by unauthorized users in production.
Notification templates are automatically sanitized to remove any JavaScript or externally loaded resources (e.g. CSS imports). Users familiar with the Jinja2 templating engine will also find certain Jinja features are not available, including access to application internals and the ability to bypass variable sanitation.
Partials¶
Partials are small, reusable fragments used to render content shared across templates. Overriding a partial changes the corresponding content for all notifications at once, making partials the preferred customization point for organization-specific greetings and branding. Each partial is rendered using the same context as the template including it.
Greeting¶
Template file: partials/greeting.html
The greeting partial renders the salutation displayed above the message body.
The partial is included by the greeting block of the base template.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
Any field available to the including template can also be referenced from the partial.
Signoff¶
Template file: partials/signoff.html
The signoff partial renders the closing remarks and signature displayed below the message body.
The partial is included by the signoff block of the base template.
The default content does not reference any context fields.
Default Template Content
Footer¶
Template file: partials/footer.html
The footer partial renders organization branding and copyright notices at the bottom of the message.
The partial is included by the footer block of the base template.
The default content does not reference any context fields.
Templates¶
The following templates are available for customization.
Base Template¶
Template file: base.html
The base template serves as the parent layout for all notification content, providing top-level styling and structure. The template defines four content blocks that child templates override to inject content. The greeting, signoff, and footer blocks are rendered using partial templates by default.
Available Template Blocks
| Block Name | Description |
|---|---|
greeting |
Salutation rendered above the message body. |
main |
Main body content of the email notification. |
signoff |
Closing remarks rendered below the message body. |
footer |
Footer content displayed at the bottom of the message. |
Default Template Content
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Keystone User Notification</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Helvetica, sans-serif;
font-size: 16px;
line-height: 1.4;
background-color: #f5f5f5;
margin: 0;
padding: 0;
}
table.body {
width: 100%;
}
table.main {
width: 100%;
background-color: #ffffff;
border: 1px solid #eaeaea;
border-radius: 0;
}
table.footer {
width: 100%;
text-align: center;
padding-top: 24px;
padding-bottom: 24px;
}
td.container {
width: 800px;
padding-top: 24px;
}
td.wrapper {
padding: 24px;
}
td.footer-text {
color: #a1a1a1;
font-size: 11px;
}
</style>
</head>
<body>
<table role="presentation" class="body">
<tr>
<td> </td>
<td class="container">
<table role="presentation" class="main">
<tr>
<td class="wrapper">
{% block greeting %}
{% include "partials/greeting.html" %}
{% endblock %}
{% block main %}{% endblock %}
{% block signoff %}
{% include "partials/signoff.html" %}
{% endblock %}
</td>
</tr>
</table>
<table role="presentation" class="footer">
<tr>
<td class="footer-text">
{% block footer %}
{% include "partials/footer.html" %}
{% endblock %}
</td>
</tr>
</table>
</td>
<td> </td>
</tr>
</table>
</body>
</html>
General Notification¶
Template file: general.html
The general notification is used for one-off and administratively issued messages. The message body is provided by the notification sender and rendered as the entire content of the email.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
message |
str |
Body of the message sent to the user. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
New Grant Record¶
Template file: grant_created.html
The grant created notification alerts team members that a new grant record has been added to their team. A summary of the new record is included in the message body.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who created the grant record. |
event_date |
datetime |
Date and time when the grant record was created. |
grant_id |
int |
ID of the new grant record. |
grant_title |
str |
Title of the grant. |
grant_agency |
str |
Name of the agency funding the grant. |
grant_number |
str or None |
Identification number assigned to the grant by the agency. |
grant_pi |
str or None |
Name of the principal investigator. |
grant_amount |
float |
Total amount awarded under the grant. |
grant_start |
date |
Date when the grant period begins. |
grant_end |
date or None |
Date when the grant period ends. |
team_name |
str |
Name of the team associated with the grant. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set grant_url = frontend_url ~ '/team/' ~ team_slug ~ '/grant/' ~ grant_id %}
<p>
A new grant record was added to the <em>{{ team_name }}</em> team by
<strong>{{ submitter_name }}</strong> on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
The record
{% if frontend_url %}
<em><a href="{{ grant_url }}">{{ grant_title }}</a></em>
{% else %}
<em>{{ grant_title }}</em>
{% endif %}
(ID: {{ grant_id }}) is summarized below.
</p>
<table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
<tbody>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Title</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_title }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Funding Agency</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_agency }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Grant Number</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_number or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Principal Investigator</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_pi or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Award Amount</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">${{ "{:,.2f}".format(grant_amount) }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Start Date</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ grant_start.strftime('%B %d, %Y') }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">End Date</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ grant_end.strftime('%B %d, %Y') if grant_end else '—' }}</td>
</tr>
</tbody>
</table>
<p style="padding-top: 10px;">
You are receiving this notification because you have enabled grant notifications for the
<em>{{ team_name }}</em> team.
{% if frontend_url %}
The record can be reviewed from the
<a href="{{ grant_url }}">grant detail page</a>.
{% endif %}
</p>
{% endblock %}
Modified Grant Record¶
Template file: grant_modified.html
The grant modified notification alerts team members that an existing grant record has been updated. The message body reflects the state of the record after the modification was applied.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who modified the grant record. |
event_date |
datetime |
Date and time when the grant record was modified. |
grant_id |
int |
ID of the modified grant record. |
grant_title |
str |
Title of the grant. |
grant_agency |
str |
Name of the agency funding the grant. |
grant_number |
str or None |
Identification number assigned to the grant by the agency. |
grant_pi |
str or None |
Name of the principal investigator. |
grant_amount |
float |
Total amount awarded under the grant. |
grant_start |
date |
Date when the grant period begins. |
grant_end |
date or None |
Date when the grant period ends. |
team_name |
str |
Name of the team associated with the grant. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set grant_url = frontend_url ~ '/team/' ~ team_slug ~ '/grant/' ~ grant_id %}
<p>
An existing grant record was modified for the <em>{{ team_name }}</em> team by
<strong>{{ submitter_name }}</strong> on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
The record
{% if frontend_url %}
<em><a href="{{ grant_url }}">{{ grant_title }}</a></em>
{% else %}
<em>{{ grant_title }}</em>
{% endif %}
(ID: {{ grant_id }}) was updated. The new record details are summarized below.
</p>
<table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
<tbody>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Title</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_title }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Funding Agency</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_agency }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Grant Number</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_number or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Principal Investigator</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ grant_pi or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Award Amount</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">${{ "{:,.2f}".format(grant_amount) }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Start Date</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ grant_start.strftime('%B %d, %Y') }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">End Date</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ grant_end.strftime('%B %d, %Y') if grant_end else '—' }}</td>
</tr>
</tbody>
</table>
<p style="padding-top: 10px;">
You are receiving this notification because you have enabled grant notifications for the
<em>{{ team_name }}</em> team.
{% if frontend_url %}
The record can be reviewed from the
<a href="{{ grant_url }}">grant detail page</a>.
{% endif %}
</p>
{% endblock %}
Deleted Grant Record¶
Template file: grant_deleted.html
The grant deleted notification alerts team members that a grant record has been removed from their team. Only identifying details of the deleted record are available to the template.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who deleted the grant record. |
event_date |
datetime |
Date and time when the grant record was deleted. |
grant_id |
int |
ID of the deleted grant record. |
grant_title |
str |
Title of the deleted grant. |
team_name |
str |
Name of the team associated with the grant. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
<p>
The grant record <em>{{ grant_title }}</em> (ID: {{ grant_id }}) was deleted from the
<em>{{ team_name }}</em> team by <strong>{{ submitter_name }}</strong> on
<strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
The record no longer appears in the team's records and has been removed from any
associated resource allocation requests.
</p>
<p style="padding-top: 10px;">
You are receiving this notification because you have enabled grant notifications for the
<em>{{ team_name }}</em> team.
</p>
{% endblock %}
New Publication Record¶
Template file: publication_created.html
The publication created notification alerts team members that a new publication record has been added to their team. A summary of the new record is included in the message body.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who created the publication record. |
event_date |
datetime |
Date and time when the publication record was created. |
publication_id |
int |
ID of the new publication record. |
publication_title |
str |
Title of the publication. |
publication_journal |
str or None |
Name of the journal the publication appears in. |
publication_volume |
str or None |
Journal volume the publication appears in. |
publication_issue |
str or None |
Journal issue the publication appears in. |
publication_doi |
str or None |
Digital Object Identifier assigned to the publication. |
publication_submitted |
date or None |
Date when the publication was submitted. |
publication_published |
date or None |
Date when the publication was published. |
team_name |
str |
Name of the team associated with the publication. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set publication_url = frontend_url ~ '/team/' ~ team_slug ~ '/publication/' ~ publication_id %}
<p>
A new publication record was added to the <em>{{ team_name }}</em> team by
<strong>{{ submitter_name }}</strong> on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
The record
{% if frontend_url %}
<em><a href="{{ publication_url }}">{{ publication_title }}</a></em>
{% else %}
<em>{{ publication_title }}</em>
{% endif %}
(ID: {{ publication_id }}) is summarized below.
</p>
<table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
<tbody>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Title</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_title }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Journal</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_journal or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Volume</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_volume or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Issue</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_issue or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">DOI</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_doi or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Date Submitted</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_submitted.strftime('%B %d, %Y') if publication_submitted else '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Date Published</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_published.strftime('%B %d, %Y') if publication_published else '—' }}</td>
</tr>
</tbody>
</table>
<p style="padding-top: 10px;">
You are receiving this notification because you have enabled publication notifications for the
<em>{{ team_name }}</em> team.
{% if frontend_url %}
The record can be reviewed from the
<a href="{{ publication_url }}">publication detail page</a>.
{% endif %}
</p>
{% endblock %}
Modified Publication Record¶
Template file: publication_modified.html
The publication modified notification alerts team members that an existing publication record has been updated. The message body reflects the state of the record after the modification was applied.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who modified the publication record. |
event_date |
datetime |
Date and time when the publication record was modified. |
publication_id |
int |
ID of the modified publication record. |
publication_title |
str |
Title of the publication. |
publication_journal |
str or None |
Name of the journal the publication appears in. |
publication_volume |
str or None |
Journal volume the publication appears in. |
publication_issue |
str or None |
Journal issue the publication appears in. |
publication_doi |
str or None |
Digital Object Identifier assigned to the publication. |
publication_submitted |
date or None |
Date when the publication was submitted. |
publication_published |
date or None |
Date when the publication was published. |
team_name |
str |
Name of the team associated with the publication. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set publication_url = frontend_url ~ '/team/' ~ team_slug ~ '/publication/' ~ publication_id %}
<p>
An existing publication record was modified for the <em>{{ team_name }}</em> team by
<strong>{{ submitter_name }}</strong> on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
The record
{% if frontend_url %}
<em><a href="{{ publication_url }}">{{ publication_title }}</a></em>
{% else %}
<em>{{ publication_title }}</em>
{% endif %}
(ID: {{ publication_id }}) was updated. The new record details are summarized below.
</p>
<table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
<tbody>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Title</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_title }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Journal</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_journal or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Volume</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_volume or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Issue</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_issue or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">DOI</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">{{ publication_doi or '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Date Submitted</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_submitted.strftime('%B %d, %Y') if publication_submitted else '—' }}</td>
</tr>
<tr>
<th style="white-space: nowrap; width: 1%; text-align: left; padding: 8px; border: 1px solid #ccc; background-color: #f2f2f2;">Date Published</th>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc;">{{ publication_published.strftime('%B %d, %Y') if publication_published else '—' }}</td>
</tr>
</tbody>
</table>
<p style="padding-top: 10px;">
You are receiving this notification because you have enabled publication notifications for the
<em>{{ team_name }}</em> team.
{% if frontend_url %}
The record can be reviewed from the
<a href="{{ publication_url }}">publication detail page</a>.
{% endif %}
</p>
{% endblock %}
Deleted Publication Record¶
Template file: publication_deleted.html
The publication deleted notification alerts team members that a publication record has been removed from their team. Only identifying details of the deleted record are available to the template.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who deleted the publication record. |
event_date |
datetime |
Date and time when the publication record was deleted. |
publication_id |
int |
ID of the deleted publication record. |
publication_title |
str |
Title of the deleted publication. |
team_name |
str |
Name of the team associated with the publication. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
<p>
The publication record <em>{{ publication_title }}</em> (ID: {{ publication_id }}) was deleted
from the <em>{{ team_name }}</em> team by <strong>{{ submitter_name }}</strong> on
<strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
The record no longer appears in the team's records and has been removed from any
associated resource allocation requests.
</p>
<p style="padding-top: 10px;">
You are receiving this notification because you have enabled publication notifications for the
<em>{{ team_name }}</em> team.
</p>
{% endblock %}
New Allocation Request¶
Template file: request_created.html
The request created notification alerts users that a new resource allocation request has been submitted on behalf of their team. The message body includes a snapshot of the request as it was submitted, including the service units requested on each cluster.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who submitted the request. |
event_date |
date or None |
Date when the allocation request was submitted. |
request_id |
int |
ID of the allocation request being notified about. |
request_title |
str |
Title of the allocation request. |
request_status |
str |
Human readable status of the allocation request. |
request_active |
date or None |
Date when the allocation request becomes active. |
request_expire |
date or None |
Date when the allocation request expires. |
team_name |
str |
Name of the team associated with the allocation request. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
allocations |
tuple[dict] |
Allocated resources tied to the request. Each item includes: |
├ cluster |
str |
Name of the cluster where the resource is allocated. |
└ requested |
int |
Number of service units requested (or 0 if unavailable). |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}
<p>
A new HPC resource request has been submitted by <strong>{{ submitter_name }}</strong> for the
<em>{{ team_name }}</em> team. The request
{% if frontend_url %}
<em><a href="{{ request_url }}">{{ request_title }}</a></em>
{% else %}
<em>{{ request_title }}</em>
{% endif %}
(ID: {{ request_id }}) was submitted on <strong>{{ event_date.strftime('%B %d, %Y') }}</strong>
and is now pending administrator review.
</p>
<p>
The table below summarizes the requested resources. Please note these values are pending review and
the total awarded resources may differ from those shown below.
</p>
<table style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
<thead>
<tr style="background-color: #f2f2f2;">
<th style="width: 70%; text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
<th style="width: 30%; text-align: center; padding: 8px; border: 1px solid #ccc;">Requested SUs</th>
</tr>
</thead>
<tbody>
{% for allocation in allocations %}
<tr>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
{{ allocation.cluster }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ "{:,}".format(allocation.requested) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p style="padding-top: 10px">
<strong>What Does This Mean</strong>
</p>
<p>
Resource requests are used by teams to petition for new or additional HPC resources.
Formal approval is required by an HPC administrator before your team can access the requested resources.
Any existing resources that have already been awarded to your team are not affected by this request.
</p>
<p style="padding-top: 10px;">
<strong>Recommended Actions</strong>
</p>
<p>
No action is required from you at this time.
An administrator will review the request and decide which resources to award and in what quantity.
{% if frontend_url %}
In the meantime, you can monitor any status updates online from the
<a href="{{ request_url }}">request detail page</a>.
{% endif %}
</p>
{% endblock %}
Reviewer Assigned¶
Template file: request_reviewer_assigned.html
The reviewer assigned notification alerts a staff user that they have been assigned as a reviewer for a resource allocation request. In addition to a snapshot of the request, the template has access to any other reviewers assigned to the same request so reviewers can coordinate their work.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who made the reviewer assignment. |
event_date |
datetime |
Date and time when the assignment was made. |
request_id |
int |
ID of the allocation request being notified about. |
request_title |
str |
Title of the allocation request. |
request_status |
str |
Human readable status of the allocation request. |
request_submitted |
date or None |
Date when the allocation request was submitted. |
request_active |
date or None |
Date when the allocation request becomes active. |
request_expire |
date or None |
Date when the allocation request expires. |
request_coassignees |
tuple[dict] |
Other reviewers assigned to the request. Each item includes: |
└ name |
str |
Display name of the coassigned reviewer. |
team_name |
str |
Name of the team associated with the allocation request. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
allocations |
tuple[dict] |
Allocated resources tied to the request. Each item includes: |
├ cluster |
str |
Name of the cluster where the resource is allocated. |
└ requested |
int |
Number of service units requested (or 0 if unavailable). |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}
<p>
You have been assigned by <strong>{{ submitter_name }}</strong> as a reviewer for the
<em>{{ team_name }}</em> team's allocation request
{% if frontend_url %}
<em><a href="{{ request_url }}">{{ request_title }}</a></em>
{% else %}
<em>{{ request_title }}</em>
{% endif %}
(ID: {{ request_id }}).
The HPC resources included in the request are listed below.
</p>
<table style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
<thead>
<tr style="background-color: #f2f2f2;">
<th style="width: 70%; text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
<th style="width: 30%; text-align: center; padding: 8px; border: 1px solid #ccc;">Requested SUs</th>
</tr>
</thead>
<tbody>
{% for allocation in allocations %}
<tr>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
{{ allocation.cluster }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ "{:,}".format(allocation.requested) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p style="padding-top: 10px;">
<strong>Recommended Actions</strong>
</p>
<p>
A review is required to determine which HPC resources should be awarded and in what amount(s).
If additional information is required, further clarification can be requested from the submitter using
by submitting a comment on the request.
{% if frontend_url %}
You can review the request online from the <a href="{{ request_url }}">request detail page</a>.
{% endif %}
</p>
{% endblock %}
New Request Comment¶
Template file: request_comment_created.html
The comment created notification alerts users that a new comment has been posted on a resource allocation request. The comment body is rendered as written and the template indicates whether the comment was marked private to staff reviewers.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the comment author. |
event_date |
datetime |
Date and time when the comment was created. |
request_id |
int |
ID of the allocation request being notified about. |
request_title |
str |
Title of the allocation request. |
request_status |
str |
Human readable status of the allocation request. |
comment_content |
str |
Body of the comment, sanitized for safe rendering as HTML. |
comment_is_private |
bool |
Whether the comment is only visible to staff reviewers. |
team_name |
str |
Name of the team associated with the allocation request. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}
<p>
A new comment was made by <strong>{{ submitter_name }}</strong> on allocation request
{% if frontend_url %}
<em><a href="{{ request_url }}">{{ request_title }}</a></em>
{% else %}
<em>{{ request_title }}</em>
{% endif %}
(ID: {{ request_id }}).
{% if comment_is_private %}
This comment was marked as private and is only visible to staff users.
{% endif %}
</p>
<table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px; margin-top: 20px">
<tbody>
<tr>
<td style="text-align: left; padding: 8px 12px; border: 1px solid #ccc; border-bottom: none; background-color: #f2f2f2;">
<strong>{{ submitter_name }}</strong> commented on {{ event_date.strftime('%B %d, %Y') }}
</td>
</tr>
<tr>
<td style="text-align: left; padding: 12px; border: 1px solid #ccc; background-color: #f9f9f9; overflow-wrap: break-word; word-break: break-word;">{{ comment_content }}</td>
</tr>
</tbody>
</table>
<p style="padding-top: 10px;">
<strong>Recommended Actions</strong>
</p>
<p>
A comment does not necessarily change the status of a request.
Unless otherwise indicated any service units already awarded to your team remain unaffected.
{% if frontend_url %}
If you wish to reply to the comment or to view the allocation request, you can do so from the
<a href="{{ request_url }}">request detail page</a>.
{% endif %}
</p>
{% endblock %}
Request Status Change¶
Template file: request_status_changed.html
The status changed notification alerts users that the status of a resource allocation request has changed.
The default template branches on the request_status field to render dedicated messaging for approved requests,
including a summary of the awarded resources, and falls back to a generic message describing the old and new status
for all other transitions.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
submitter_name |
str |
Display name of the user who changed the request status. |
event_date |
datetime |
Date and time when the status change was recorded. |
request_id |
int |
ID of the allocation request being notified about. |
request_title |
str |
Title of the allocation request. |
request_status |
str |
Human readable status of the request after the change. |
request_status_previous |
str |
Human readable status of the request before the change. |
request_active |
date or None |
Date when the allocation request becomes active. |
request_expire |
date or None |
Date when the allocation request expires. |
team_name |
str |
Name of the team associated with the allocation request. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
allocations |
tuple[dict] |
Allocated resources tied to the request. Each item includes: |
├ cluster |
str |
Name of the cluster where the resource is allocated. |
├ requested |
int |
Number of service units requested (or 0 if unavailable). |
└ awarded |
int or None |
Number of service units awarded (or None if not yet awarded). |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}
{% macro request_link() %}
{% if frontend_url %}
<em><a href="{{ request_url }}">{{ request_title }}</a></em>
{% else %}
<em>{{ request_title }}</em>
{% endif %}
{% endmacro %}
{% if request_status == 'Approved' %}
<p>
The <em>{{ team_name }}</em> team's allocation request
{{ request_link() }} (ID: {{ request_id }}) was approved on
<strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
</p>
<p>
The HPC resources listed below have been approved for use starting <strong>
{{ request_active.strftime('%B %d, %Y') }}</strong>.
These resource
{% if request_expire %}
will expire on <strong>{{ request_expire.strftime('%B %d, %Y') }}</strong>.
{% else %}
do not have an expiration date.
{% endif %}
</p>
<table
style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
<thead>
<tr style="background-color: #f2f2f2;">
<th style="width: 40%; text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
<th style="width: 30%; text-align: center; padding: 8px; border: 1px solid #ccc;">Requested SUs</th>
<th style="width: 30%; text-align: center; padding: 8px; border: 1px solid #ccc;">Awarded SUs</th>
</tr>
</thead>
<tbody>
{% for allocation in allocations %}
<tr>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
{{ allocation.cluster }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ "{:,}".format(allocation.requested) }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ "{:,}".format(allocation.awarded) if allocation.awarded is not none else '—' }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p style="padding-top: 10px;">
<strong>What Does This Mean</strong>
</p>
<p>
An administrator has reviewed the allocation request and awarded the resources listed above.
These resources will be allocated alongside any others your team already holds.
Unused service units do not carry over once an allocation expires.
</p>
{% else %}
<p>
The <em>{{ team_name }}</em> team's allocation request
{{ request_link() }} (ID: {{ request_id }}) was changed from
<strong>{{ request_status_previous }}</strong> to <strong>{{ request_status }}</strong> on
<strong>{{ event_date.strftime('%B %d, %Y') }}</strong>.
</p>
{% endif %}
<p style="padding-top: 10px;">
<strong>Recommended Actions</strong>
</p>
<p>
If you have questions regarding this change, please contact your HPC administrator.
Please note it may take several hours for any changes mentioned above to go into full effect.
{% if frontend_url %}
You can review this request online from the <a href="{{ request_url }}">request detail page</a>.
{% endif %}
</p>
{% endblock %}
Upcoming Resource Expiration¶
Template file: request_nearing_expiration.html
The upcoming expiration notification alerts users that one or more of their active resource allocations is nearing its expiration date.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
request_id |
int |
ID of the allocation request being notified about. |
request_title |
str |
Title of the allocation request. |
request_active |
date |
Date when the allocation request became active. |
request_expire |
date |
Date when the allocation request expires. |
request_days_until_expire |
int |
Number of days remaining until the request expires. |
team_name |
str |
Name of the team associated with the allocation request. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
allocations |
tuple[dict] |
Allocated resources tied to the request. Each item includes: |
├ awarded |
int |
Number of service units awarded (or 0 if unavailable). |
└ cluster |
str |
Name of the cluster where the resource is allocated. |
upcoming_requests |
tuple[dict] |
Other active or pending requests for the team. Each item includes: |
├ active |
date or None |
Date when the upcoming request became active. |
├ expire |
date or None |
Date when the upcoming request expires. |
├ id |
int |
ID of the upcoming allocation request. |
├ status |
str |
Human readable status of the upcoming allocation request. |
├ submitted |
date or None |
Date when the upcoming request was submitted. |
└ title |
str |
Title of the upcoming allocation request. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% block main %}
{% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}
<p>
This is a reminder that the <em>{{ team_name }}</em> team's HPC allocation
{% if frontend_url %}
<em><a href="{{ request_url }}">{{ request_title }}</a></em>
{% else %}
<em>{{ request_title }}</em>
{% endif %}
(ID: {{ request_id }}) is expiring in
<strong>{{ request_days_until_expire }} day{{ "s" if request_days_until_expire != 1 }}</strong>,
on <strong>{{ request_expire.strftime('%B %d, %Y') }}</strong>. After this
date, any unused service units granted by this allocation will no longer
be accessible to your team.
</p>
<p>
This expiration only applies to service units awarded under the allocation
mentioned above. Any service units awarded under different allocations held
by your team remain unaffected. The table below summarizes the service units
associated with the expiring allocation:
</p>
<table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
<thead>
<tr style="background-color: #f2f2f2;">
<th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
<th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Awarded SUs</th>
</tr>
</thead>
<tbody>
{% for allocation in allocations %}
<tr>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ allocation.cluster }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ "{:,}".format(allocation.awarded) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p style="padding-top: 10px;">
<strong>Recommended Actions</strong>
</p>
{% if upcoming_requests %}
<p>
The following table includes all active or upcoming allocations currently
associated with the <em>{{ team_name }}</em> team. Please take a moment to
review each allocation and confirm your team has sufficient resources moving
forward. If your team anticipates needing resources beyond those listed
below, please have a team administrator submit a new allocation request
at their earliest convenience.
</p>
<table
style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
<thead>
<tr style="background-color: #f2f2f2;">
<th style="width: 8%; text-align: center; padding: 8px; border: 1px solid #ccc;">ID</th>
<th style="width: 30%; text-align: left; padding: 8px; border: 1px solid #ccc;">Title</th>
<th style="width: 12%; text-align: center; padding: 8px; border: 1px solid #ccc;">Status</th>
<th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Submitted</th>
<th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Begins</th>
<th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Expires</th>
</tr>
</thead>
<tbody>
{% for upcoming in upcoming_requests %}
<tr>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.id }}
</td>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
{% if frontend_url %}
<a href="{{ frontend_url }}/team/{{ team_slug }}/request/{{ upcoming.id }}">{{ upcoming.title }}</a>
{% else %}
{{ upcoming.title }}
{% endif %}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.status }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.submitted.strftime('%B %d, %Y') if upcoming.submitted else '—' }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.active.strftime('%B %d, %Y') if upcoming.active else '—' }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.expire.strftime('%B %d, %Y') if upcoming.expire else '—' }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>
The <em>{{ team_name }}</em> team has no upcoming or active allocations beyond the one described above.
Once this allocation expires on <strong>{{ request_expire.strftime('%B %d, %Y') }}</strong>, your team will have
zero remaining service units. If your team requires additional service units, please have a team administrator
submit a new allocation request at their earliest convenience.
</p>
<p>
If your team does not require additional service units, then no action is required at this time.
</p>
{% endif %}
{% endblock %}
Expired Resource Allocation¶
Template file: request_past_expiration.html
The past expiration notification alerts users that one or more of their active resource allocations has expired and that the resources granted under that allocation are no longer available for use.
Available Template Fields
| Field Name | Type | Description |
|---|---|---|
recipient_name |
str |
Display name of the notified user. |
request_id |
int |
ID of the allocation request being notified about. |
request_title |
str |
Title of the allocation request. |
request_active |
date |
Date when the allocation request became active. |
request_expire |
date |
Date when the allocation request expired. |
team_name |
str |
Name of the team associated with the allocation request. |
team_slug |
str |
URL safe identifier for the team, suitable for building frontend URLs. |
allocations |
tuple[dict] |
Allocated resources tied to the request. Each item includes: |
├ awarded |
int |
Number of service units awarded (or 0 if unavailable). |
├ cluster |
str |
Name of the cluster where the resource is allocated. |
└ final |
int |
Number of service units used by the team (or 0 if unavailable). |
upcoming_requests |
tuple[dict] |
Other active or pending requests for the team. Each item includes: |
├ active |
date or None |
Date when the upcoming request became active. |
├ expire |
date or None |
Date when the upcoming request expires. |
├ id |
int |
ID of the upcoming allocation request. |
├ status |
str |
Human readable status of the upcoming allocation request. |
├ submitted |
date or None |
Date when the upcoming request was submitted. |
└ title |
str |
Title of the upcoming allocation request. |
frontend_url |
str |
Base URL of the frontend application, without a trailing slash. |
Default Template Content
{% extends "base.html" %}
{% macro percent(numerator, denominator) %}
{% if denominator %}
({{ '%.0f' % ((numerator / denominator) * 100) }}%)
{% else %}
{% endif %}
{% endmacro %}
{% block main %}
{% set request_url = frontend_url ~ '/team/' ~ team_slug ~ '/request/' ~ request_id %}
<p>
This is a reminder that the <em>{{ team_name }}</em> team's HPC allocation
{% if frontend_url %}
<em><a href="{{ request_url }}">{{ request_title }}</a></em>
{% else %}
<em>{{ request_title }}</em>
{% endif %}
(ID: {{ request_id }}) expired on
<strong>{{ request_expire.strftime('%B %d, %Y') }}</strong>. Moving forward,
any unused service units granted by this allocation are no longer usable
by your team.
</p>
<p>
The table below summarizes your team's total resource usage under this allocation:
</p>
<table style="width: 100%; border-collapse: collapse; font-family: sans-serif; font-size: 14px;">
<thead>
<tr style="background-color: #f2f2f2;">
<th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Cluster</th>
<th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Awarded SUs</th>
<th style="text-align: center; padding: 8px; border: 1px solid #ccc;">Utilized SUs</th>
</tr>
</thead>
<tbody>
{% for allocation in allocations %}
<tr>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ allocation.cluster }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ "{:,}".format(allocation.awarded) }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ "{:,}".format(allocation.final) }} {{ percent(allocation.final, allocation.awarded) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p style="padding-top: 10px">
<strong>What Does This Mean</strong>
</p>
<p>
On {{ request_active.strftime('%B %d, %Y') }} your team was granted the
service unit allocation listed above. This allocation expired on
{{ request_expire.strftime('%B %d, %Y') }} and any unused service units
are no longer available for use.
</p>
<p>
Any resources granted to your team by other allocation requests remain
unaffected. The system will automatically apply as much of your system
usage as possible to the expiring request, maximizing the total service
units available in any remaining allocations.
</p>
<p style="padding-top: 10px;">
<strong>Recommended Actions</strong>
</p>
{% if upcoming_requests %}
<p>
The following table includes all active or upcoming allocations currently
associated with the <em>{{ team_name }}</em> team. Please take a moment to
review each allocation and confirm your team has sufficient resources moving
forward. If your team anticipates needing resources beyond those listed
below, please have a team administrator submit a new allocation request
at their earliest convenience.
</p>
<table
style="width: 100%; border-collapse: collapse; table-layout: fixed; font-family: sans-serif; font-size: 14px;">
<thead>
<tr style="background-color: #f2f2f2;">
<th style="width: 8%; text-align: center; padding: 8px; border: 1px solid #ccc;">ID</th>
<th style="width: 30%; text-align: left; padding: 8px; border: 1px solid #ccc;">Title</th>
<th style="width: 12%; text-align: center; padding: 8px; border: 1px solid #ccc;">Status</th>
<th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Submitted</th>
<th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Begins</th>
<th style="width: 16%; text-align: center; padding: 8px; border: 1px solid #ccc;">Expires</th>
</tr>
</thead>
<tbody>
{% for upcoming in upcoming_requests %}
<tr>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.id }}
</td>
<td style="text-align: left; padding: 8px; border: 1px solid #ccc; overflow-wrap: break-word; word-break: break-word;">
{% if frontend_url %}
<a href="{{ frontend_url }}/team/{{ team_slug }}/request/{{ upcoming.id }}">{{ upcoming.title }}</a>
{% else %}
{{ upcoming.title }}
{% endif %}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.status }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.submitted.strftime('%B %d, %Y') if upcoming.submitted else '—' }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.active.strftime('%B %d, %Y') if upcoming.active else '—' }}
</td>
<td style="text-align: center; padding: 8px; border: 1px solid #ccc;">
{{ upcoming.expire.strftime('%B %d, %Y') if upcoming.expire else '—' }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>
The <em>{{ team_name }}</em> team has no upcoming or active allocations.
If your team requires additional service units, please have a team administrator submit a new allocation request
at their earliest convenience.
If your team does not require additional service units, then no action is required at this time.
</p>
{% endif %}
{% endblock %}