diff --git a/coldfront_notifications/campaign_sender.py b/coldfront_notifications/campaign_sender.py index abf70eb..b653a67 100644 --- a/coldfront_notifications/campaign_sender.py +++ b/coldfront_notifications/campaign_sender.py @@ -13,8 +13,9 @@ import time from django.conf import settings -from django.core.mail import EmailMessage, get_connection +from django.core.mail import EmailMessage, EmailMultiAlternatives, get_connection from django.utils import timezone +from django.utils.html import strip_tags from .conf import BATCH_SIZE, BATCH_DELAY, MAX_RETRIES, RETRY_DELAY from .filters import RecipientResolver @@ -239,14 +240,15 @@ def _deliver_emails(self, resolved_emails, snapshot): if bcc: bcc = list(dev_list) - message = EmailMessage( + message = EmailMultiAlternatives( subject=rendered_subject, - body=rendered_body, + body=strip_tags(rendered_body), from_email=campaign.sender, to=to, bcc=bcc, reply_to=[campaign.reply_to] if campaign.reply_to else [], ) + message.attach_alternative(rendered_body, "text/html") send_error = self.delivery.send_with_retry(message, connection) if send_error is None: diff --git a/coldfront_notifications/static/coldfront_notifications/css/compose.css b/coldfront_notifications/static/coldfront_notifications/css/compose.css index dc5fe0c..cbda566 100644 --- a/coldfront_notifications/static/coldfront_notifications/css/compose.css +++ b/coldfront_notifications/static/coldfront_notifications/css/compose.css @@ -175,7 +175,7 @@ display: inline-flex; align-items: center; gap: 4px; margin: 0; white-space: nowrap; } .recip-page-size-select { - width: auto !important; display: inline-block; padding: 2px 6px; + width: 52px !important; display: inline-block; padding: 2px 4px; font-size: .78rem; height: auto !important; } diff --git a/coldfront_notifications/static/coldfront_notifications/js/campaign_detail.js b/coldfront_notifications/static/coldfront_notifications/js/campaign_detail.js index 2ff440c..5c6a555 100644 --- a/coldfront_notifications/static/coldfront_notifications/js/campaign_detail.js +++ b/coldfront_notifications/static/coldfront_notifications/js/campaign_detail.js @@ -3,6 +3,11 @@ // CAMPAIGN_STATUS - string ('queued', 'sending', 'sent', etc.) // CAMPAIGN_PROGRESS_URL - URL for the progress JSON endpoint +// Blur focused element before modal hides to avoid aria-hidden warning. +$('#emailViewModal').on('hide.bs.modal', function() { + if (document.activeElement) document.activeElement.blur(); +}); + var STATUS_CLASSES = { sent: 'badge-success', sending: 'badge-primary', @@ -49,7 +54,7 @@ $(function(){ // Only init DataTable when there are real data rows. var realRows = $('#logTable tbody tr td:not([colspan])').length; if (realRows > 0) { - $('#logTable').DataTable({ order:[[2,'asc']], columnDefs:[{orderable:false,targets:[3]}] }); + $('#logTable').DataTable({ order:[[2,'asc']], columnDefs:[{orderable:false,targets:[3]}], pagingType:'simple_numbers', pageLength:50, lengthMenu:[[50,100,250,-1],[50,100,250,'All']] }); } // Start polling if campaign is in-flight. if (CAMPAIGN_STATUS === 'sending' || CAMPAIGN_STATUS === 'queued') { @@ -62,6 +67,6 @@ $(document).on('click', '.view-email-btn', function() { $('#em-replyto').text($(this).attr('data-replyto') || '—'); $('#em-to').text($(this).attr('data-email')); $('#em-subject').text($(this).attr('data-subject')); - $('#em-body').text($(this).attr('data-body')); + $('#em-body').html($(this).attr('data-body')); $('#emailViewModal').modal('show'); }); diff --git a/coldfront_notifications/static/coldfront_notifications/js/compose_draft.js b/coldfront_notifications/static/coldfront_notifications/js/compose_draft.js index 4c84a3c..0bee2fc 100644 --- a/coldfront_notifications/static/coldfront_notifications/js/compose_draft.js +++ b/coldfront_notifications/static/coldfront_notifications/js/compose_draft.js @@ -31,7 +31,7 @@ function _collectDraftData() { csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(), draft_pk: DRAFT_PK || '', subject: $('#id_subject').val() || '', - body: $('#id_body').val() || '', + body: getBodyContent() || '', sender: $('#id_sender').val() || '', reply_to: $('#id_reply_to').val() || '', template_id: $('#hidden_template_id').val() || '', @@ -91,7 +91,7 @@ function restoreDraft(draftData) { // Restore form fields if (draftData.subject) $('#id_subject').val(draftData.subject); - if (draftData.body) $('#id_body').val(draftData.body); + if (draftData.body) setBodyContent(draftData.body); if (draftData.sender) $('#id_sender').val(draftData.sender); if (draftData.reply_to) $('#id_reply_to').val(draftData.reply_to); if (draftData.template_id) $('#hidden_template_id').val(draftData.template_id); diff --git a/coldfront_notifications/static/coldfront_notifications/js/compose_init.js b/coldfront_notifications/static/coldfront_notifications/js/compose_init.js index d120283..957a8ab 100644 --- a/coldfront_notifications/static/coldfront_notifications/js/compose_init.js +++ b/coldfront_notifications/static/coldfront_notifications/js/compose_init.js @@ -8,6 +8,7 @@ // ── Serialize filter hidden fields before submit ───────────────── $('#notifForm').on('submit', function() { + if (typeof tinymce !== 'undefined') tinymce.triggerSave(); $('#h_projects').val(JSON.stringify($('#f_project').val() || [])); $('#h_allocations').val(JSON.stringify($('#f_allocation').val() || [])); $('#h_depts').val(JSON.stringify($('#f_dept').val() || [])); @@ -26,6 +27,21 @@ $(document).ready(function() { width: '100%', placeholder: function() { return $(this).data('placeholder'); } }); + + // Auto-select template if ?template=ID is in the URL + var params = new URLSearchParams(window.location.search); + var templateId = params.get('template'); + if (templateId) { + // Delay slightly to ensure TinyMCE is initialized + setTimeout(function() { + var $item = $('.tmpl-item[data-id="' + templateId + '"]'); + if ($item.length) { + $('.tmpl-item').removeClass('active'); + $item.addClass('active'); + loadTemplate($item); + } + }, 500); + } }); // ── pageshow: reset validation state + refresh active template ──── diff --git a/coldfront_notifications/static/coldfront_notifications/js/compose_preview.js b/coldfront_notifications/static/coldfront_notifications/js/compose_preview.js index e0557a8..aa22aaa 100644 --- a/coldfront_notifications/static/coldfront_notifications/js/compose_preview.js +++ b/coldfront_notifications/static/coldfront_notifications/js/compose_preview.js @@ -22,14 +22,14 @@ function renderPreviewSample(idx) { // "Template" — no recipient, raw tokens $('#prev-to').text('—'); $('#prev-subject').text($('#id_subject').val() || '—'); - $('#prev-body').text($('#id_body').val() || '—'); + $('#prev-body').html(getBodyContent() || '—'); $('#prev-error').hide(); $('#prev-footer').text('Showing template with literal placeholders'); return; } $('#prev-to').text((s.recipient.name || s.recipient.username) + ' <' + s.recipient.email + '>'); $('#prev-subject').text(s.subject || '—'); - $('#prev-body').text(s.body || '—'); + $('#prev-body').html(s.body || '—'); if (s.error) { $('#prev-error').text('Resolution error: ' + s.error).show(); } else { @@ -58,7 +58,7 @@ $(document).on('click', '#previewBtn', function() { data: { csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(), subject: $('#id_subject').val() || '', - body: $('#id_body').val() || '', + body: getBodyContent() || '', filters: JSON.stringify(collectFilters()), dedupe_users: JSON.stringify(getDedupeUsers()), }, @@ -212,7 +212,7 @@ function fetchPreviewPage(page) { alloc_status: $('#f_status').val() || [], roles: $('#f_role').val() || [], subject: $('#id_subject').val() || '', - body: $('#id_body').val() || '', + body: getBodyContent() || '', dedupe_users: JSON.stringify(getDedupeUsers()), preview: 'true', page: PREVIEW_PAGE, diff --git a/coldfront_notifications/static/coldfront_notifications/js/compose_templates.js b/coldfront_notifications/static/coldfront_notifications/js/compose_templates.js index fcbb648..32e9a74 100644 --- a/coldfront_notifications/static/coldfront_notifications/js/compose_templates.js +++ b/coldfront_notifications/static/coldfront_notifications/js/compose_templates.js @@ -40,7 +40,7 @@ function renderVars(vars, filter) { // ── Extract tokens from subject + body ─────────────────────────── var TOKEN_RE = /\{\{(\w+)\}\}/g; function extractTokens() { - var text = ($('#id_subject').val() || '') + '\n' + ($('#id_body').val() || ''); + var text = ($('#id_subject').val() || '') + '\n' + (getBodyContent() || ''); var seen = {}; var out = []; var m; @@ -66,7 +66,7 @@ function loadTemplate($el) { if (!id) { ACTIVE_VARS = []; renderVars([], ''); return; } $.getJSON(URLS.templateJson.replace('{id}', id), function(t) { $('#id_subject').val(t.subject || ''); - $('#id_body').val(t.body || ''); + setBodyContent(t.body || ''); ACTIVE_VARS = t.variables || []; renderVars(ACTIVE_VARS, ''); if (HAS_VALIDATED) runValidation(); @@ -99,10 +99,16 @@ $(document).on('input', '#varSearch', function() { // ── Variable insert at cursor ──────────────────────────────────── $(document).on('click', '.var-btn', function() { - var ta = document.getElementById('id_body'); var token = $(this).attr('data-var'); - var s = ta.selectionStart, e = ta.selectionEnd; - ta.value = ta.value.substring(0, s) + token + ta.value.substring(e); - ta.selectionStart = ta.selectionEnd = s + token.length; - ta.focus(); + var editor = typeof tinymce !== 'undefined' && tinymce.get('id_body'); + if (editor) { + editor.insertContent(token); + editor.focus(); + } else { + var ta = document.getElementById('id_body'); + var s = ta.selectionStart, e = ta.selectionEnd; + ta.value = ta.value.substring(0, s) + token + ta.value.substring(e); + ta.selectionStart = ta.selectionEnd = s + token.length; + ta.focus(); + } }); diff --git a/coldfront_notifications/static/coldfront_notifications/js/compose_validate.js b/coldfront_notifications/static/coldfront_notifications/js/compose_validate.js index 48f1947..c0bac51 100644 --- a/coldfront_notifications/static/coldfront_notifications/js/compose_validate.js +++ b/coldfront_notifications/static/coldfront_notifications/js/compose_validate.js @@ -67,7 +67,7 @@ function runValidation() { data: { csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(), subject: $('#id_subject').val() || '', - body: $('#id_body').val() || '', + body: getBodyContent() || '', filters: JSON.stringify(collectFilters()), dedupe_users: JSON.stringify(getDedupeUsers()), }, diff --git a/coldfront_notifications/static/coldfront_notifications/js/datatables_init.js b/coldfront_notifications/static/coldfront_notifications/js/datatables_init.js index 269b49b..858e1f6 100644 --- a/coldfront_notifications/static/coldfront_notifications/js/datatables_init.js +++ b/coldfront_notifications/static/coldfront_notifications/js/datatables_init.js @@ -4,9 +4,26 @@ // DT_CONFIG - object with: { tableId, order, nonOrderableTargets, paging, searching, info } // All fields except tableId are optional. +// Fix DataTables "Show entries" dropdown and search layout +$('");const y=r(e),u=c(e),v=' + {% endblock %} diff --git a/coldfront_notifications/templates/coldfront_notifications/campaign_list.html b/coldfront_notifications/templates/coldfront_notifications/campaign_list.html index 7ec4ee4..2be6cd2 100644 --- a/coldfront_notifications/templates/coldfront_notifications/campaign_list.html +++ b/coldfront_notifications/templates/coldfront_notifications/campaign_list.html @@ -26,46 +26,39 @@

All Notifications

-
- - - - - - - - - - {% for c in campaigns %} - - - - - - - - - - - - {% empty %} - - {% endfor %} - -
#SubjectTemplateRecipientsDeliveredFailedSent AtStatus
{{ c.pk|stringformat:"03d" }}{{ c.display_subject }}{{ c.template.slug|default:"—" }}{{ c.recipient_count }}{{ c.delivered_count }}{{ c.failed_count|default:"—" }}{{ c.sent_at|date:"Y-m-d H:i"|default:"—" }} - {% if c.status == 'sent' %}Sent - {% elif c.status == 'sending' %}Sending… - {% elif c.status == 'partial' %}Partial - {% elif c.status == 'failed' %}Failed - {% elif c.status == 'draft' %}Draft - {% else %}{{ c.status }}{% endif %} - - {% if c.status == 'draft' %} - - - {% endif %} -
No notifications yet. Send one →
-
+ +
+ + + + + + + + + + {% for c in campaigns %} + + + + + + + + + {% include "coldfront_notifications/_status_badge_td.html" with campaign=c %} + + + {% empty %} + + {% endfor %} + +
#SubjectTemplateRecipientsDeliveredFailedSent AtStatus
{{ c.pk|stringformat:"03d" }}{{ c.display_subject }}{{ c.template.slug|default:"—" }}{{ c.recipient_count }}{{ c.delivered_count }}{{ c.failed_count|default:"—" }}{{ c.sent_at|date:"Y-m-d H:i"|default:"—" }} + {% if c.status == 'draft' %} + + + {% endif %} +
No notifications yet. Send one →
{% endblock %} {% block javascript %} diff --git a/coldfront_notifications/templates/coldfront_notifications/compose.html b/coldfront_notifications/templates/coldfront_notifications/compose.html index 033c091..4ee502c 100644 --- a/coldfront_notifications/templates/coldfront_notifications/compose.html +++ b/coldfront_notifications/templates/coldfront_notifications/compose.html @@ -282,124 +282,8 @@

- - - - - +{% include "coldfront_notifications/_email_preview_modal.html" %} +{% include "coldfront_notifications/_recipient_preview_modal.html" with show_dedupe=True info_text="Users with multiple emails show a dedupe checkbox — tick it to send them only their first email." %} {% endblock %} @@ -419,6 +303,8 @@

draftSave: '{% url "notifications:api-draft-save" %}' }; + + diff --git a/coldfront_notifications/templates/coldfront_notifications/dashboard.html b/coldfront_notifications/templates/coldfront_notifications/dashboard.html index 13afc83..89f9e3c 100644 --- a/coldfront_notifications/templates/coldfront_notifications/dashboard.html +++ b/coldfront_notifications/templates/coldfront_notifications/dashboard.html @@ -11,38 +11,10 @@

Notifications Dashboard

-
-
-
-
Total Delivered
-
{{ total_sent|default:"0" }}
-
-
-
-
-
-
-
Sending Now
-
{{ sending_now|default:"0" }}
-
-
-
-
-
-
-
Failed (30d)
-
{{ total_failed_30|default:"0" }}
-
-
-
-
-
-
-
Total Notifications
-
{{ total_campaigns|default:"0" }}
-
-
-
+ {% include "coldfront_notifications/_stat_card.html" with label="Total Delivered" value=total_sent|default:"0" color="#A51C30" %} + {% include "coldfront_notifications/_stat_card.html" with label="Sending Now" value=sending_now|default:"0" color="#28a745" value_color="#28a745" %} + {% include "coldfront_notifications/_stat_card.html" with label="Failed (30d)" value=total_failed_30|default:"0" color="#ffc107" value_color="#e67e22" %} + {% include "coldfront_notifications/_stat_card.html" with label="Total Notifications" value=total_campaigns|default:"0" color="#17a2b8" %}
@@ -92,21 +64,7 @@

{{ campaign.sender }} {{ campaign.recipient_count }} {{ campaign.sent_at|date:"Y-m-d H:i"|default:"—" }} - - {% if campaign.status == 'sent' %} - Sent - {% elif campaign.status == 'sending' %} - Sending… - {% elif campaign.status == 'partial' %} - Partial - {% elif campaign.status == 'failed' %} - Failed - {% elif campaign.status == 'draft' %} - Draft - {% else %} - {{ campaign.status }} - {% endif %} - + {% include "coldfront_notifications/_status_badge_td.html" with campaign=campaign %} {% empty %} diff --git a/coldfront_notifications/templates/coldfront_notifications/draft_delete_confirm.html b/coldfront_notifications/templates/coldfront_notifications/draft_delete_confirm.html index 01aa3da..5c754b0 100644 --- a/coldfront_notifications/templates/coldfront_notifications/draft_delete_confirm.html +++ b/coldfront_notifications/templates/coldfront_notifications/draft_delete_confirm.html @@ -1,52 +1,32 @@ -{% extends "common/base.html" %} -{% block title %}Delete Draft | FAS Research Computing{% endblock %} -{% block content %} -
-

Delete Draft

-
-
+{% extends "coldfront_notifications/_delete_confirm_base.html" %} -
-
-
-
-
Confirm Deletion
-
-
-

Are you sure you want to delete this draft notification?

+{% block delete_title %}Delete Draft{% endblock %} +{% block delete_heading %}Delete Draft{% endblock %} - - - {% if campaign.template %} - - {% endif %} - - {% if campaign.filter_summary %} - - {% endif %} -
Subject{{ campaign.subject|default:"(no subject)" }}
Template{{ campaign.template.slug }}
Created{{ campaign.created_at|date:"Y-m-d H:i" }}
Filters - {% for badge in campaign.filter_summary %} - {{ badge }} - {% endfor %} -
+{% block confirm_message %} +

Are you sure you want to delete this draft notification?

+{% endblock %} -

- This draft has never been sent. Deleting it is permanent and cannot be undone. -

+{% block details_rows %} +Subject{{ campaign.subject|default:"(no subject)" }} +{% if campaign.template %} +Template{{ campaign.template.slug }} +{% endif %} +Created{{ campaign.created_at|date:"Y-m-d H:i" }} +{% if campaign.filter_summary %} +Filters + {% for badge in campaign.filter_summary %} + {{ badge }} + {% endfor %} + +{% endif %} +{% endblock %} -
- {% csrf_token %} -
- - Go Back - - -
-
-
-
-
-
+{% block explanation %} +

+ This draft has never been sent. Deleting it is permanent and cannot be undone. +

{% endblock %} + +{% block cancel_url %}{% url 'notifications:campaign-list' %}{% endblock %} +{% block delete_button_label %}Delete Draft{% endblock %} diff --git a/coldfront_notifications/templates/coldfront_notifications/resend_compose.html b/coldfront_notifications/templates/coldfront_notifications/resend_compose.html index dd21cd2..eda9dbd 100644 --- a/coldfront_notifications/templates/coldfront_notifications/resend_compose.html +++ b/coldfront_notifications/templates/coldfront_notifications/resend_compose.html @@ -160,114 +160,14 @@

Review & Edit

- - - - - +{% include "coldfront_notifications/_email_preview_modal.html" %} +{% include "coldfront_notifications/_recipient_preview_modal.html" with show_dedupe=False info_text="Recipients that will receive this resend." %} {% endblock %} {% block javascript %} + + + + {% endblock %} \ No newline at end of file diff --git a/coldfront_notifications/templates/coldfront_notifications/template_list.html b/coldfront_notifications/templates/coldfront_notifications/template_list.html index 8a882cc..d096f1c 100644 --- a/coldfront_notifications/templates/coldfront_notifications/template_list.html +++ b/coldfront_notifications/templates/coldfront_notifications/template_list.html @@ -13,32 +13,30 @@

Templates


-
-
- - - - - - {% for t in templates %} - - - - - - - - - {% empty %} - - {% endfor %} - -
NameSlugSubjectUpdatedUsed
{{ t.name }}{{ t.slug }}{{ t.subject }}{{ t.updated_at|date:"Y-m-d" }}{{ t.use_count }}× - - - -
No templates yet.
-
+
+ + + + + + {% for t in templates %} + + + + + + + + + {% empty %} + + {% endfor %} + +
NameSlugSubjectUpdatedUsed
{{ t.name }}{{ t.slug }}{{ t.subject }}{{ t.updated_at|date:"Y-m-d" }}{{ t.use_count }}× + + + +
No templates yet.
{% endblock %} {% block javascript %} diff --git a/coldfront_notifications/templates/coldfront_notifications/variable_delete_confirm.html b/coldfront_notifications/templates/coldfront_notifications/variable_delete_confirm.html index de6b77b..3c7df82 100644 --- a/coldfront_notifications/templates/coldfront_notifications/variable_delete_confirm.html +++ b/coldfront_notifications/templates/coldfront_notifications/variable_delete_confirm.html @@ -1,63 +1,48 @@ -{% extends "common/base.html" %} -{% block title %}Delete Variable | FAS Research Computing{% endblock %} -{% block content %} -
-

Delete Variable

-
-
+{% extends "coldfront_notifications/_delete_confirm_base.html" %} -
-
-
-
-
Confirm Deletion
-
-
-

Are you sure you want to delete the variable {% templatetag openvariable %}{{ variable.key }}{% templatetag closevariable %}?

+{% block delete_title %}Delete Variable{% endblock %} +{% block delete_heading %}Delete Variable{% endblock %} - - - - {% if variable.resolver_key %} - - {% endif %} -
Label{{ variable.label }}
Source{{ variable.get_source_display }}
Query{{ variable.resolver_key }}
+{% block confirm_message %} +

Are you sure you want to delete the variable {% templatetag openvariable %}{{ variable.key }}{% templatetag closevariable %}?

+{% endblock %} - {% if affected_templates %} -
- - {{ affected_templates|length }} template{{ affected_templates|length|pluralize }} use{{ affected_templates|length|pluralize:"s," }} this variable: -
    - {% for t in affected_templates %} -
  • - {{ t.name }} - ({{ t.slug }}) -
  • - {% endfor %} -
-
-

- These templates will fail validation until the {% templatetag openvariable %}{{ variable.key }}{% templatetag closevariable %} token is removed from their subject/body. -

- {% else %} -

- No active templates reference this variable. -

- {% endif %} +{% block details_rows %} +Label{{ variable.label }} +Source{{ variable.get_source_display }} +{% if variable.resolver_key %} +Query{{ variable.resolver_key }} +{% endif %} +{% endblock %} -
- {% csrf_token %} -
- - Go Back - - -
-
-
-
-
+{% block warning %} +{% if affected_templates %} +
+ + {{ affected_templates|length }} template{{ affected_templates|length|pluralize }} use{{ affected_templates|length|pluralize:"s," }} this variable: +
    + {% for t in affected_templates %} +
  • + {{ t.name }} + ({{ t.slug }}) +
  • + {% endfor %} +
+{% endif %} {% endblock %} + +{% block explanation %} +{% if affected_templates %} +

+ These templates will fail validation until the {% templatetag openvariable %}{{ variable.key }}{% templatetag closevariable %} token is removed from their subject/body. +

+{% else %} +

+ No active templates reference this variable. +

+{% endif %} +{% endblock %} + +{% block cancel_url %}{% url 'notifications:variable-list' %}{% endblock %} +{% block delete_button_label %}Delete Variable{% endblock %} diff --git a/coldfront_notifications/templates/coldfront_notifications/variable_form.html b/coldfront_notifications/templates/coldfront_notifications/variable_form.html index b810933..5cc4351 100644 --- a/coldfront_notifications/templates/coldfront_notifications/variable_form.html +++ b/coldfront_notifications/templates/coldfront_notifications/variable_form.html @@ -163,24 +163,8 @@

{% if variable %} -
-
-
Danger Zone
-
-
-

- Deleting {% templatetag openvariable %}{{ variable.key }}{% templatetag closevariable %} will cause templates that use it - to fail pre-flight validation. -

-
- {% csrf_token %} - -
-
-
+ {% url 'notifications:variable-delete' variable.pk as dz_url %} + {% include "coldfront_notifications/_danger_zone_card.html" with message="Deleting this variable will cause templates that use it to fail pre-flight validation." delete_url=dz_url delete_label="Delete Variable" %} {% endif %}
diff --git a/coldfront_notifications/templates/coldfront_notifications/variable_list.html b/coldfront_notifications/templates/coldfront_notifications/variable_list.html index 5897519..bd32d34 100644 --- a/coldfront_notifications/templates/coldfront_notifications/variable_list.html +++ b/coldfront_notifications/templates/coldfront_notifications/variable_list.html @@ -19,62 +19,60 @@

Template Variables

-
-
- - - - - - - - - - - - - - {% for v in variables %} - - - - - - - - - - {% empty %} - - {% endfor %} - -
TokenLabelSourceQuery / WidgetExampleRequired
{% templatetag openvariable %}{{ v.key }}{% templatetag closevariable %}{{ v.label }} - {% if v.description %}
{{ v.description }}{% endif %} -
- {% if v.source == "query" %} - Query - {% else %} - Manual - {% endif %} - - {% if v.source == "query" %} - {{ v.resolver_key }} - {% else %} - {{ v.get_input_widget_display }} - {% endif %} - {{ v.example|default:"—" }} - {% if v.is_required %}{% else %}—{% endif %} - - - - - - - -
No variables defined yet. - Create the first one → -
-
+
+ + + + + + + + + + + + + + {% for v in variables %} + + + + + + + + + + {% empty %} + + {% endfor %} + +
TokenLabelSourceQuery / WidgetExampleRequired
{% templatetag openvariable %}{{ v.key }}{% templatetag closevariable %}{{ v.label }} + {% if v.description %}
{{ v.description }}{% endif %} +
+ {% if v.source == "query" %} + Query + {% else %} + Manual + {% endif %} + + {% if v.source == "query" %} + {{ v.resolver_key }} + {% else %} + {{ v.get_input_widget_display }} + {% endif %} + {{ v.example|default:"—" }} + {% if v.is_required %}{% else %}—{% endif %} + + + + + + + +
No variables defined yet. + Create the first one → +
{% endblock %} {% block javascript %}