{% extends "base.html" %} {% comment %} Step two: answer what the template needs, then confirm. Everything from the uploaded file — flow names, tag names, request URLs — is somebody else's text and is rendered through Django's autoescaping. There is no `|safe` and no `mark_safe` on this page, deliberately: a message body in a template carries {{ placeholders }} that apps/flows/rendering.py substitutes at send time and no template engine ever evaluates (SECURITY-BASELINE §3), and a review page that rendered one as markup would be the hole that undoes it. The external-request URLs are printed as text rather than linked. The importer did not choose them, and a clickable link on a review page is one misread away from visiting a stranger's address. {% endcomment %} {% block title %}Import a flow · DemandTrail Messaging{% endblock %} {% block page_header %} {% include "flows/_import_steps.html" with current=2 %} {% endblock %} {% block content %}
{% comment %} What will be created {% endcomment %}

Flows in this file

{% comment %} "Unpublished drafts, triggers switched off" was accurate and left the reader nowhere: it names the state the flows land in without naming the three things that get them out of it. Somebody importing their first template asked, reasonably, how they were supposed to test it. {% endcomment %}

Flows arrive as drafts with triggers off. Review each one before setting it live.

{% comment %} Outbound calls the importer did not choose {% endcomment %} {% if plan.outbound_requests %}

External requests

Check these addresses before publishing.

{% endif %} {% if plan.notes %}

Worth knowing

{% endif %} {% comment %} The mapping form {% endcomment %}
{% csrf_token %} {% for group in groups %}

{{ group.label }}

{% if group.help %}

{{ group.help }}

{% endif %}
{% for question in group.questions %} {% include "flows/_import_requirement.html" with resolution=question.resolution options=question.options kind=group.kind field_types=group.field_types needed_by=question.needed_by connect_url=question.connect_url %} {% endfor %}
{% endfor %} {% if plan.triggers %}

Triggers

Keep a trigger off for now, or skip it entirely.

{% for choice in plan.triggers %}

{{ choice.label }}

{{ choice.flow_name }}{% if choice.platform %} · {{ choice.platform }}{% endif %}

{% endfor %}
{% endif %} {% comment %} Import is a submit button on this form, carrying `then=import`, and it is never disabled. Both of those are the fix for the same bug. It used to POST straight to the confirm endpoint, so it acted on the answers the *server* last stored rather than the ones on screen — and it was disabled whenever that stored plan was incomplete. Change a dropdown and the button stayed dead from the previous render: no error, no toast, nothing. With `.btn-brand` carrying no disabled styling either, it looked perfectly clickable. So: the form saves and imports in one request, and an incomplete plan comes back to this page with the missing questions flagged rather than being refused by a control that will not depress. {% endcomment %}
Back to flows
{% comment %} Names them, and links to them. A bare count is only useful to somebody who already knows which question is blank, and the reader who needs this line is exactly the reader who does not. {% endcomment %} {% if not plan.can_apply %}

Still needed: {% for item in unanswered %}{{ item.label }}: {{ item.name }}{% if not forloop.last %}, {% endif %}{% endfor %}. Save to check again.

{% endif %}
{% endblock %}