Digests
This document describes all the data available in Jinja2 templates for email digests.
Table of Contents
- Main Context Variables
- Digest Information
- Equipment Data
- Qualifications Data
- Quotes Data
- Leave Data
- Scheduled Tasks Data
- Individual Mode
Main Context Variables
The following top-level variables are always available in your templates:
| Variable | Type | Description |
|---|---|---|
digest |
object | The digest configuration and metadata |
equipment |
object | Equipment calibration data |
qualifications |
object | Staff qualifications data |
quotes |
object | Project quotes and enquiries data |
leave |
object | Leave/vacation data |
tasks |
object | Scheduled task data |
today |
date | Current date |
user |
object | Current user (only in individual mode) |
Digest Information
digest object
Contains the digest configuration:
| Field | Type | Description |
|---|---|---|
id |
integer | Unique identifier |
name |
string | Name of the digest |
org |
object | Organization (see below) |
individual |
boolean | Whether individual emails are sent |
subject |
string | Email subject template |
equipment_days_before |
integer | Days before today for equipment data |
equipment_days_after |
integer | Days after today for equipment data |
qualifications_days_before |
integer | Days before today for qualifications |
qualifications_days_after |
integer | Days after today for qualifications |
leave_days_before |
integer | Days before today for leave data |
leave_days_after |
integer | Days after today for leave data |
tasks_days_before |
integer | Days before today for scheduled task data (default 0) |
tasks_days_after |
integer | Days after today for scheduled task data (default 14) |
created |
datetime | Creation timestamp |
created_by |
object | User who created (see User object) |
modified |
datetime | Last modification timestamp |
modified_by |
object | User who modified (see User object) |
digest.org object
| Field | Type | Description |
|---|---|---|
id |
integer | Organization ID |
name |
string | Organization name |
Equipment Data
equipment object
Contains two arrays of equipment items:
| Field | Type | Description |
|---|---|---|
expired |
array | Equipment with expired calibration |
expiring |
array | Equipment with upcoming calibration |
Equipment Item Structure
Each item in equipment.expired or equipment.expiring :
| Field | Type | Description |
|---|---|---|
id |
integer | Equipment ID |
name |
string | Equipment name |
category |
object | Equipment category (id, name) |
serial |
string | Serial number |
calibration |
date | Calibration due date |
identifier |
string | Equipment identifier |
barcode |
string | Barcode value |
Example Usage
{% if equipment.expired %}
<h3>Expired Equipment</h3>
<ul>
{% for item in equipment.expired %}
<li>{{ item.name }} - Expired on {{ item.calibration }}</li>
{% endfor %}
</ul>
{% endif %}
{% if equipment.expiring %}
<h3>Equipment Due for Calibration</h3>
<ul>
{% for item in equipment.expiring %}
<li>{{ item.name }} - Due {{ item.calibration }}</li>
{% endfor %}
</ul>
{% endif %}
Qualifications Data
qualifications object
Contains two arrays of qualification items:
| Field | Type | Description |
|---|---|---|
expired |
array | Expired qualifications |
expiring |
array | Qualifications expiring soon |
Qualification Item Structure
Each item in qualifications.expired or qualifications.expiring :
| Field | Type | Description |
|---|---|---|
id |
integer | Qualification ID |
user |
object | User with qualification (see User object) |
name |
string | Qualification name |
reference |
string | Reference number |
start |
date | Start date |
end |
date | Expiry date |
Example Usage
{% if qualifications.expiring %}
<h3>Qualifications Expiring Soon</h3>
<table>
<tr><th>Employee</th><th>Qualification</th><th>Expires</th></tr>
{% for qual in qualifications.expiring %}
<tr>
<td>{{ qual.user.name }}</td>
<td>{{ qual.name }}</td>
<td>{{ qual.end }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
Quotes Data
quotes object
Contains two arrays of project/quote items:
| Field | Type | Description |
|---|---|---|
created |
array | Enquiries (not yet quoted) |
open |
array | Open quotes (quoted but not decided) |
Quote Item Structure
Each item in quotes.created or quotes.open :
| Field | Type | Description |
|---|---|---|
id |
integer | Project ID |
name |
string | Project name |
reference |
string | Project reference |
client |
object | Client (id, name) |
category |
object | Project category (id, name) |
created |
datetime | Creation date |
created_by |
object | User who created (id, name) |
modified |
datetime | Last modification date |
modified_by |
object | User who modified (id, name) |
quoted |
date | Date quoted (null for enquiries) |
quoted_by |
object | User who quoted (id, name) |
price_net |
decimal | Net price |
currency |
string | Currency code |
deadline |
date | Project deadline |
quote_deadline |
date | Quote validity deadline |
quote_decision |
string | Quote decision status |
Example Usage
{% if quotes.created %}
<h3>New Enquiries</h3>
<ul>
{% for quote in quotes.created %}
<li>
<strong>{{ quote.client.name }}</strong>: {{ quote.name }}
(Created {{ quote.created|date:"Y-m-d" }})
</li>
{% endfor %}
</ul>
{% endif %}
{% if quotes.open %}
<h3>Quotes Awaiting Decision</h3>
<ul>
{% for quote in quotes.open %}
<li>
{{ quote.client.name }}: {{ quote.name }}
- Quoted {{ quote.quoted|date:"Y-m-d" }}
{% if quote.quote_deadline %} (Valid until {{ quote.quote_deadline }}){% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
Leave Data
leave object
Contains two arrays of leave items:
| Field | Type | Description |
|---|---|---|
created |
array | Leave requests pending approval |
approved |
array | Approved leave in date range |
Leave Item Structure
Each item in leave.created or leave.approved :
| Field | Type | Description |
|---|---|---|
id |
integer | Leave ID |
user |
object | Employee (see User object) |
type |
object | Leave type (id, name) |
start |
date | Start date |
end |
date | End date |
days |
decimal | Number of days |
hours |
decimal | Number of hours |
decision |
string | Decision status (empty="Requested", "A"="Approved", "D"="Denied") |
decided |
datetime | When decision was made |
decided_by |
object | Manager who decided (see User object) |
decided_notes |
string | Decision notes |
Example Usage
{% if leave.created %}
<h3>Pending Leave Requests</h3>
<ul>
{% for request in leave.created %}
<li>
{{ request.user.name }} - {{ request.type.name }}
({{ request.start }} to {{ request.end }}, {{ request.days }} days)
</li>
{% endfor %}
</ul>
{% endif %}
{% if leave.approved %}
<h3>Upcoming Leave</h3>
<ul>
{% for absence in leave.approved %}
<li>
{{ absence.user.name }}: {{ absence.start|date:"M d" }} - {{ absence.end|date:"M d" }}
</li>
{% endfor %}
</ul>
{% endif %}
Scheduled Tasks Data
tasks object
Contains a single array of scheduled tasks:
| Field | Type | Description |
|---|---|---|
scheduled |
array | Scheduled tasks within the configured date window |
A task counts as scheduled when it has at least one of start /end set and at least one assigned user. The window is controlled by tasks_days_before and tasks_days_after , and a task is included when its date range overlaps that window. Tasks on abandoned projects are excluded, as are tasks whose closed date has already passed.
In individual mode, tasks.scheduled is narrowed to only the tasks the recipient is assigned to. In non-individual mode it holds the full organization list.
Task Item Structure
Each item in tasks.scheduled :
| Field | Type | Description |
|---|---|---|
id |
integer | Task ID |
name |
string | Task name |
type |
object | Task type (id, name) |
details |
string | Task details (may be null) |
days |
decimal | Budgeted days |
hours |
decimal | Budgeted hours |
start |
date | Scheduled start date (may be null) |
end |
date | Scheduled end date (may be null) |
project |
object | Project (id, name, reference) |
client |
object | Client (id, name) |
assignments |
array | Assigned users (see below) |
Assignment Structure
Each item in task.assignments :
| Field | Type | Description |
|---|---|---|
done |
datetime | When the assignment was marked done (null if not done) |
user |
object | Assigned user (see User object) |
Done assignments are kept in the list rather than filtered out, so you can either display them or skip them by checking the done field.
Example Usage
{% if tasks.scheduled %}
<h3>Scheduled Tasks</h3>
<ul>
{% for task in tasks.scheduled %}
<li>
<strong>{{ task.name }}</strong> ({{ task.project.name }})
{% if task.start %}{{ task.start }}{% endif %}{% if task.end %} to {{ task.end }}{% endif %}
<ul>
{% for assignment in task.assignments %}
<li>{{ assignment.user.name }}{% if assignment.done %} (done){% endif %}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
{% endif %}
User Object Structure
User objects appear in various places and have this structure:
| Field | Type | Description |
|---|---|---|
id |
integer | User ID |
name |
string | Full name |
email |
string | Email address |
first_name |
string | First name |
last_name |
string | Last name |
Individual Mode
When digest.individual is true , each recipient gets a personalized email with an additional user variable:
user object (only in individual mode)
The full user object for the recipient, containing all user profile data.
Note: in individual mode the tasks.scheduled list is also narrowed to the tasks that recipient is assigned to.
Example Usage
{% if user %}
<p>Hello {{ user.first_name }},</p>
<p>Here's your personal digest for {{ today|date:"F j, Y" }}:</p>
{% else %}
<p>Team Digest for {{ today|date:"F j, Y" }}:</p>
{% endif %}
Conditional Display
Use Jinja2 conditionals to show/hide sections:
{% if equipment.expired or equipment.expiring %}
<section class="equipment-section">
<!-- Equipment content -->
</section>
{% endif %}
{% if leave.created|length > 5 %}
<p class="alert">{{ leave.created|length }} leave requests pending!</p>
{% endif %}
Loops and Filters
Common Jinja2 filters useful for digests:
{{ items|length }} <!-- Count items -->
{{ price|floatformat:2 }} <!-- Format decimal -->
{{ text|truncatewords:20 }} <!-- Limit text length -->
{{ value|default:"N/A" }} <!-- Default value -->
{% for item in items|slice:":5" %} <!-- First 5 items -->
<!-- ... -->
{% endfor %}
Complete Example Template
<!DOCTYPE html>
<html>
<head>
<title>{{ digest.org.name }} Digest - {{ today|date:"F j, Y" }}</title>
</head>
<body>
<h1>{{ digest.org.name }} Weekly Digest</h1>
<p>Report generated on {{ today|date:"l, F j, Y" }}</p>
{% if quotes.created or quotes.open %}
<h2>Projects & Quotes</h2>
{% if quotes.created %}
<h3>New Enquiries ({{ quotes.created|length }})</h3>
<ul>
{% for project in quotes.created|slice:":10" %}
<li>{{ project.client.name }}: {{ project.name }}</li>
{% endfor %}
</ul>
{% endif %}
{% if quotes.open %}
<h3>Quotes Pending Decision ({{ quotes.open|length }})</h3>
<ul>
{% for project in quotes.open %}
<li>
{{ project.client.name }}: {{ project.name }}
- {{ project.currency }} {{ project.price_net|floatformat:2 }}
{% if project.quote_deadline %}
(Expires: {{ project.quote_deadline|date:"M d" }})
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
{% if leave.created or leave.approved %}
<h2>Leave Management</h2>
{% if leave.created %}
<h3>Pending Approvals</h3>
<table>
{% for request in leave.created %}
<tr>
<td>{{ request.user.name }}</td>
<td>{{ request.start|date:"M d" }} - {{ request.end|date:"M d" }}</td>
<td>{{ request.days }} days</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endif %}
{% if tasks.scheduled %}
<h2>Scheduled Tasks ({{ tasks.scheduled|length }})</h2>
<ul>
{% for task in tasks.scheduled %}
<li>
{{ task.name }} - {{ task.project.name }}
{% if task.start %}({{ task.start }}{% if task.end %} to {{ task.end }}{% endif %}){% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% if equipment.expired %}
<h2>⚠️ Expired Equipment Calibrations</h2>
<ul>
{% for item in equipment.expired %}
<li>{{ item.name }} ({{ item.category.name }}) - Expired {{ item.calibration }}</li>
{% endfor %}
</ul>
{% endif %}
{% if user %}
<hr>
<p><small>This digest was personalized for {{ user.name }}.</small></p>
{% endif %}
</body>
</html>
Notes for Template Designers
- Always check for data existence before displaying sections to avoid empty headers
- Use the
|lengthfilter to show counts in section headers - Consider using
|sliceto limit long lists in summary views - Format dates consistently throughout the template
- Test with both individual and non-individual modes if your digest supports both
- Remember that
useris only available in individual mode - All date/datetime fields can be null - always check before displaying
Testing Your Templates
To test your templates with sample data:
- Create a test digest in the admin panel
- Set appropriate day ranges for each data type
- Send test emails to verify formatting
- Test both with and without data in each section
- Test individual mode if applicable