Document Template Basics
A Document Template is simply a text document which contains placeholders for data. When a user
generates a document for a Draft or Project using the Document Template the data from that particular Draft or Project
is injected into the template.
For example, to insert the name of the Draft into a template we might have a template:
Draft Name : {{ draft.DraftName }}
When this Template is run against a Draft called "Draft 1" we will get the content:
The {{ and }} denote the start and end of placeholders and the draft.DraftName within tells the system that we want
the value of the DraftName attribute of the draft object.
The draft object has many more attributes than DraftName (see the Object Model Reference). As an example we
might want to list all the Forms in the Draft.
Note
The Object Model Reference is the complete list of what a template can read. A template which
reads an object or an attribute that is not listed there fails with an error naming what it
refused, rather than rendering a blank value - so a typo shows up as a clear message instead of a
silently missing column.
Draft Name : {{ draft.DraftName }}
Forms
=====
{% for form in draft.als_forms_set.all() %}
{{ form.OID }}
{% endfor %}
In this example we loop through all the forms defined in the Draft and insert their OIDs into the generated document.
If Draft1 contained 3 Forms "FORMA","FORMB" and "FORMC" we would see:
Draft Name : Draft1
Forms
=====
FORMA
FORMB
FORMC
TrialGrid uses the Jinja2 template system. The Document
Template examples show usage of the Jinja2 template system but do not provide a tutorial on the syntax itself. The
Jinja2 website offers excellent reference material to learn the syntax.
A set of objects such as draft.als_forms_set does not have to be read whole. You can choose the
objects you want, for example only the active Forms, put them in order, for example by OID, or just
count them. Choosing and Counting Objects lists every method you can use,
with examples.
Filters lists every filter a template can use, such as join,
format_datetime and xml_escape, with an example of each.
Collecting values in a loop
A {% set %} inside a loop does not change a value set outside it, so a total or a list built
that way is still empty after the loop. There are two ways round this.
Add to a list with {% do %}, which runs an expression without writing anything:
{% set oids = [] %}
{% for form in draft.als_forms_set.order_by("Ordinal") %}
{% do oids.append(form.OID) %}
{% endfor %}
Forms: {{ oids|join(", ") }}
Or keep the value in a namespace, whose attributes a {% set %} inside the loop can change:
{% set totals = namespace(fields=0) %}
{% for form in draft.als_forms_set.all() %}
{% set totals.fields = totals.fields + form.als_fields_set.count() %}
{% endfor %}
Fields: {{ totals.fields }}
Line breaks after tags
The line break that follows a {% %} tag is removed, so that lines holding only a tag, like
{% for %} and {% endfor %}, do not leave blank lines in the document. It also means a line
that ends with a tag joins the next one: a line ending {% endif %} runs into whatever comes
after it.
To choose between words at the end of a line, use an expression instead of {% if %}:
{% for form in draft.als_forms_set.order_by("Ordinal") %}
{% set count = form.als_fields_set.count() %}
{{ form.OID }}: {{ count }} {{ "field" if count == 1 else "fields" }}
{% endfor %}
Template Scope
Document templates have a Scope which determines where the document template can be accessed and what data it
receives.
Templates with a Draft Home scope can be accessed from the Draft Home Page. They receive
a draft object which represents the Draft the template is being generated for.
Draft Name : {{ draft.DraftName }}
Forms
=====
{% for form in draft.als_forms_set.all() %}
{{ form.OID }}
{% endfor %}
Templates with a Project Home scope can be accessed from the Project Home Page. They receive
a project object which represents the Project the template is being generated for.
Example:
Project Name : {{ project.ProjectName }}
Drafts
======
{% for draft in project.als_crfdraft_set.all() %}
{{ draft.DraftName }}
{% endfor %}
Templates with a Project Tickets scope can be accessed from the Project Tickets Page. They
receive a project object which represents the Project the template is being generated for as well as the page of
the tickets listing they are being run on (open, closed, all) and any filters or search active:
Example:
Project Name : {{ project.ProjectName }}
Ticket Page: {{ ticket_page.title() }}
Active Search: {{ search_text or 'None' }}
Label Filter : {{ " ,".join(filter_labels) or 'None' }}
Assignments Filter : {{ filter_assignments }} {# All, Unassigned, Assigned to me #}
Tickets
=======
{% for ticket in project.ticket_set.filter(id__in=filtered_ids).order_by('iid') %}
{{ticket.iid}} : {{ ticket.title }}
{% endfor %}
Templates with a Draft Object List scope can be accessed from the "Actions" menu of a Draft object
listing — Forms, Fields, Folders,
Edit Checks, Derivations,
Custom Functions, Data Dictionaries,
Unit Dictionaries, Matrices,
Standard Rules and Custom Objects.
A Draft Object List template must say which Object Type it reports on. The Object Type is chosen
next to the Scope when editing the Template, and the Template is then offered only on that Object
Type's listing — a report for Forms does not appear on the Fields list.
These templates receive a draft object, the objects the user had selected with the row checkboxes,
the objects matching whatever search and filters were active, and — in all_objects — every object
of the Template's Object Type in the Draft:
Example:
{{ object_type_plural }} in {{ draft.DraftName }}
Active Search: {{ search_text or 'None' }}
Selected
========
{% for form in selected_objects %}
{{ form.OID }} : {{ form.DraftFormName }}
{% endfor %}
Because the Template is tied to one Object Type, selected_objects and filtered_objects already
contain only objects of that type. See Draft Object List Template Context for the full list of
values these Templates receive.
Note
Generating with no rows selected is a normal way to use these Templates. selected_ids
and selected_objects are then empty while filtered_ids and filtered_objects hold
everything currently listed, so a Template written against filtered_objects reports on
whatever the user is looking at. all_objects is unaffected by either, so a Template can
list the whole set and mark the rows the user picked out.
Turning Text Content into other Formats (Word, PDF, Excel etc)
Templates that are set to be of type "Word" must have content that conforms to the TrialGrid Ltd DocXMarkup format.
The template text is generated and the resulting expanded text is then processed again by DocXMarkup into a Word
format document which can be downloaded by the user. See the section on Generating Word Documents for more
information.
Templates that are set to be of type "PDF" must have content that conforms to the ReportLab Markup (RML) format.
Similarly to the Word process, generating of PDF output is a two-step process that first expands the text template
and then takes the expanded template and processes it to produce PDF output. See the section on
Generating PDF Documents for more information.
Templates that are set to be of type "Excel 2003/2004" must have content that conforms to the
Microsoft Excel 2003/2004 format. See the section on Generating Excel Documents for more information.
Plain text templates can be useful for generating output for system integration. Text format templates can be used to
generate tab-delimited or Comma-separated values files and XML files.
Values are escaped in a Text template just as in the other types (see Values are escaped). That suits
XML, but output that is not markup, such as plain text, Markdown or CSV, wants each value exactly as it is stored, so
write it with |safe. Without it a quote in a value comes out as ", and escaping it again with
xml_escape gives ".
This template writes a Draft's Test Cases as Markdown, ready to give to an AI or another tool. Each Test Case is a
heading followed by its Gherkin in a fenced block, so that a # comment or a | table in the Gherkin is not
read as Markdown. Template Test Cases, which other Test Cases include, are left out:
# Test Cases in {{ draft.DraftName|safe }}
{% for tc in draft.testcase_set.filter(is_template=False).order_by("name") %}
## {{ tc.name|safe }}
ID: {{ tc.id }}
````gherkin
{{ tc.content|safe }}
````
{% endfor %}
Templates that are set to be of type "AI Summary" use a two-stage process: the template is first expanded to produce
a plain text prompt which is then sent to an AI language model for summarization. The AI response is saved as a
text file. See the section on Generating AI Summaries for more information.
Setting the name of the generated file
By default the name of the file generated is the same as the name of the Document Template with spaces and
characters that are not allowed in file names replaced with underscores (_). It is sometimes convenient to
change the name of the generated document to include the name of the Draft or some other value. This can be
achieved by setting the name inside the template with {% do SET_FILE_NAME(..) %} The value of the file name
can also be retrieved using {{ GET_FILE_NAME() }}
For example, if the name of the Draft is "Draft 1" then:
{% do SET_FILE_NAME(draft.DraftName + ' Test') %}
{{ GET_FILE_NAME() }}
returns: