mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-17 04:59:37 -04:00
Merge remote-tracking branch 'origin/main' into 1169-display-confirmation-summary-on-the-preview-page
This commit is contained in:
@@ -21,7 +21,6 @@ from flask_login import LoginManager, current_user
|
||||
from flask_talisman import Talisman
|
||||
from flask_wtf import CSRFProtect
|
||||
from flask_wtf.csrf import CSRFError
|
||||
from govuk_frontend_jinja.flask_ext import init_govuk_frontend
|
||||
from itsdangerous import BadSignature
|
||||
from notifications_python_client.errors import HTTPError
|
||||
from notifications_utils import logging, request_helper
|
||||
@@ -114,6 +113,7 @@ from app.notify_client.template_statistics_api_client import template_statistics
|
||||
from app.notify_client.upload_api_client import upload_api_client
|
||||
from app.notify_client.user_api_client import user_api_client
|
||||
from app.url_converters import SimpleDateTypeConverter, TemplateTypeConverter
|
||||
from app.utils.govuk_frontend_jinja.flask_ext import init_govuk_frontend
|
||||
|
||||
login_manager = LoginManager()
|
||||
csrf = CSRFProtect()
|
||||
|
||||
BIN
app/assets/images/product/security-review-message.png
Normal file
BIN
app/assets/images/product/security-review-message.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -1,43 +1,21 @@
|
||||
# Get started
|
||||
|
||||
|
||||
1. ## Check if Notify.gov is right for you
|
||||
<p>Read about our <a class="usa-link" href="{{ url_for('main.features') }}">features</a>, <a class="usa-link" href="{{ url_for('.pricing') }}">pricing</a> and <a class="usa-link" href="{{ url_for('main.roadmap') }}">roadmap</a>.</p>
|
||||
1. ## Everyone starts in [Trial Mode](/using-notify/trial-mode)
|
||||
Explore Notify, add team members, and practice [sending messages to teammates](/using-notify/trial-mode).
|
||||
|
||||
2. ## Personalize content
|
||||
Learn how to [personalize messages](/using-notify/guidance) to increase response.
|
||||
|
||||
2. ## Create an account
|
||||
{% if not current_user.is_authenticated %}
|
||||
<p><a class="usa-link" href="{{ url_for('.register') }}">Create an account</a> for free and add your first Notify service. When you add a new service it will start in <a class="usa-link" href="{{ url_for('main.trial_mode_new') }}">trial mode</a>.</p>
|
||||
{% else %}
|
||||
<p>Create an account for free and add your first Notify service. When you add a new service, it will start in <a class="usa-link" href="{{ url_for('main.trial_mode_new') }}">trial mode</a>.</p>
|
||||
{% endif %}
|
||||
|
||||
3. ## Write some messages
|
||||
{% if True %}
|
||||
<p>Add message templates with examples of the content you plan to send. You can use our <a class="usa-link" href="{{ url_for('main.guidance_index') }}">guidance</a> to help you.</p>
|
||||
{% endif %}
|
||||
|
||||
4. ## Set up your service
|
||||
{% if not current_user.is_authenticated or not current_service %}
|
||||
<p>Review your settings to add message branding and sender information.</p>
|
||||
<p>Add team members and check their permissions.</p>
|
||||
{% else %}
|
||||
<p>Review your <a class="usa-link" href="{{ url_for('.service_settings', service_id=current_service.id) }}">settings</a> to add message branding and sender information.</p>
|
||||
<p>Add <a class="usa-link" href="{{ url_for('.manage_users', service_id=current_service.id) }}">team members</a> and check their permissions.</p>
|
||||
{% endif %}
|
||||
3. ## Check delivery status
|
||||
[Analyze the delivery](/using-notify/delivery-status) of your messages and download reports
|
||||
|
||||
4. ## View messaging limits
|
||||
Learn about message _parts_ and [how limits are calculated](/using-notify/pricing).
|
||||
|
||||
5. ## Start sending messages
|
||||
{% if not current_user.is_authenticated or not current_service %}
|
||||
<p>When you’re ready to send messages to people outside your team, go to the <b class="bold">Settings</b> page and select <b class="bold">Request to go live</b>. We’ll approve your request within one working day.</p>
|
||||
{% else %}
|
||||
<p>You should <a class="usa-link" href="{{ url_for('.support') }}">request to go live</a> when you’re ready to send messages to people outside your team. We’ll approve your request within one working day.</p>
|
||||
{% endif %}
|
||||
To remove the restrictions of Trial Mode and begin sending messages to clients complete the [Live Campaign Form](https://airtable.com/appe4n7jYOALPLcyU/shrIPWnLTw9U1fclL).
|
||||
We’ll respond within one business day.
|
||||
|
||||
<!-- <li class="get-started-list__item">
|
||||
<h2 class="usa-process-list__heading line-height-sans-1">Set up an API integration (optional)</h2>
|
||||
<p>You can use the Notify API to send messages automatically.</p>
|
||||
<p>Our <a class="usa-link" href="{{ url_for('main.documentation') }}">documentation</a> explains how to integrate the API with a web application or back office system.</p>
|
||||
</li> -->
|
||||
|
||||
<!-- <p>Check <a class="usa-link" href="{{ url_for('main.how_to_pay') }}">how to pay</a> if you’re planning to exceed the <a class="usa-link" href="{{ url_for('.pricing', _anchor='text-messages') }}">free text message allowance</a>.</p> -->
|
||||
### Questions?
|
||||
[Contact the Notify team](/support)
|
||||
|
||||
@@ -90,7 +90,7 @@ def view_notification(service_id, notification_id, error_message=None):
|
||||
partials=get_single_notification_partials(notification),
|
||||
created_by=notification.get("created_by"),
|
||||
created_at=notification["created_at"],
|
||||
updated_at=notification["updated_at"],
|
||||
updated_at=notification["sent_at"],
|
||||
help=get_help_argument(),
|
||||
notification_id=notification["id"],
|
||||
can_receive_inbound=(current_service.has_permission("inbound_sms")),
|
||||
|
||||
@@ -953,14 +953,20 @@ def send_notification(service_id, template_id):
|
||||
service_id, job_id=upload_id, include_one_off=True
|
||||
)
|
||||
attempts = 0
|
||||
while notifications["total"] == 0 and attempts < 5:
|
||||
|
||||
# The response can come back in different forms of incompleteness
|
||||
while (
|
||||
notifications["total"] == 0
|
||||
and notifications["notifications"] == []
|
||||
and attempts < 50
|
||||
):
|
||||
notifications = notification_api_client.get_notifications_for_service(
|
||||
service_id, job_id=upload_id, include_one_off=True
|
||||
)
|
||||
time.sleep(0.1)
|
||||
attempts = attempts + 1
|
||||
|
||||
if notifications["total"] == 0 and attempts == 5:
|
||||
if notifications["total"] == 0 and attempts == 50:
|
||||
# This shows the job we auto-generated for the user
|
||||
return redirect(
|
||||
url_for(
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
<p class="status-hint margin-0 width-card ">
|
||||
{{ notification.status|format_notification_status_as_time(
|
||||
notification.created_at|format_datetime_short,
|
||||
(notification.updated_at or notification.created_at)|format_datetime_short
|
||||
(notification.sent_at or notification.created_at)|format_datetime_short
|
||||
) }}
|
||||
</p>
|
||||
{% if displayed_on_single_line %}</span>{% endif %}
|
||||
|
||||
@@ -11,115 +11,229 @@
|
||||
|
||||
<h1 class="font-body-2xl margin-bottom-3">Guidance</h1>
|
||||
|
||||
<p>This guidance is for teams using Notify.gov to send text messages.</p>
|
||||
|
||||
<p>It explains how to:</p>
|
||||
<p>Notify allows you easily to create templates for messages for your recipients. You can customize messages to encourage
|
||||
your recipient to manage their benefits and increase follow through.</p>
|
||||
<p>Below we explain how to:</p>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
<li><a class="usa-link" href="#editandformat">edit and format messages</a></li>
|
||||
<li><a class="usa-link" href="#customization">add customization</a></li>
|
||||
<!-- <li><a class="usa-link" href="{{ url_for('.send_files_by_email') }}">send files by email</a></li> -->
|
||||
<li><a class="usa-link" href="#format-content">Format your content</a></li>
|
||||
<li><a class="usa-link" href="#add-links">Add links</a></li>
|
||||
<li><a class="usa-link" href="#personalize-content">Personalize your content</a></li>
|
||||
<li><a class="usa-link" href="#conditional-content">Add conditional content</a></li>
|
||||
<li><a class="usa-link" href="#indentify-program">Identify your program</a></li>
|
||||
<li><a class="usa-link" href="#prepare-data">Prepare your data</a></li>
|
||||
<li><a class="usa-link" href="#prevent-fraud">Prevent fraud</a></li>
|
||||
</ul>
|
||||
|
||||
<h2 class="font-body-lg" id="#formatting">Edit and format messages</h2>
|
||||
{# Format content #}
|
||||
<h2 class="padding-top-1" id="format-content">Format your content</h2>
|
||||
<p>Effective texts will help your message recipients take the steps needed to secure and keep the benefits and services
|
||||
they depend on. To craft an effective text:</p>
|
||||
|
||||
<p>This section explains how to:</p>
|
||||
<ul class="list list-bullet">
|
||||
<li><a class="usa-link" href="#formatting">format your content</a></li>
|
||||
<li><a class="usa-link" href="#links">add links</a></li>
|
||||
<li><a class="usa-link" href="#personalised-content">personalize your content</a></li>
|
||||
<li><a class="usa-link" href="#optional-content">add optional content</a></li>
|
||||
<li>Choose your messages thoughtfully. Text messages are best to remind/nudge someone to take a specific action or
|
||||
communicate information at a particular time rather than pushing out broad or overly general information.</li>
|
||||
<li>Clearly state the issue and expected response. Tell your recipient what you expect from them.</li>
|
||||
<li>Say only one important thing per message.</li>
|
||||
<li>Frame the message to encourage action and explain the consequences of not completing the desired action.</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="font-body-lg" id="formatting">Format your content</h3>
|
||||
|
||||
<p>You can see a list of formatting instructions on the edit template page:</p>
|
||||
|
||||
<ol class="list list-number">
|
||||
<li>Go to the {{ service_link(current_service, 'main.choose_template', 'templates') }} page.</li>
|
||||
<li>Add a new template or choose an existing template and select <b class="bold">Edit</b>.</li>
|
||||
<h3>To create and format your message</h3>
|
||||
<ol class="list">
|
||||
<li>All messages start from a template</li>
|
||||
<li>Click “Send Messages”. You’ll see existing templates.</li>
|
||||
<li>Add a new template or choose an existing template and select Edit.</li>
|
||||
</ol>
|
||||
|
||||
<h3 class="font-body-lg" id="links">Add links</h3>
|
||||
|
||||
<p>When composing a text message, write URLs in full and Notify will convert them into links for you.
|
||||
</p>
|
||||
|
||||
<p>You cannot convert text into a link.
|
||||
<!-- Research shows that people like to check a URL looks genuine before clicking the link in an email. This is hard to do if the URL is hidden behind clickable link text.-->
|
||||
</p>
|
||||
|
||||
<p>We do not recommend using a third-party link shortening service because:</p>
|
||||
{# Add links #}
|
||||
<h2 class="padding-top-1" id="add-links">Add links</h2>
|
||||
<p>When composing a text message, links to websites or online applications can help your recipient respond quickly.</p>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
<li>your users cannot see where the link will take them</li>
|
||||
<li>your link might stop working if there’s a service outage</li>
|
||||
<li>you can no longer control where the redirect goes</li>
|
||||
<li>Write URLs in full and Notify will convert them into links for you. Note that you cannot hyperlink text in Notify
|
||||
messages.</li>
|
||||
<li>For link click tracking, you can consider adding campaign parameters to URLs.</li>
|
||||
<li>All links should point to a government domain.</li>
|
||||
<li>Link directly to where your recipient needs to take action, not to more information.</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="font-body-lg" id="personalised-content">Personalize your content</h3>
|
||||
<h3>About link-shortening services</h3>
|
||||
<p>We do not recommend using a third-party link-shortening service because:</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>Your recipient cannot see where the link will take them, which could make them suspect the link is spam/scam.</li>
|
||||
<li>Your link might stop working if there’s a service outage.</li>
|
||||
<li>You can no longer control where the redirect goes.</li>
|
||||
</ul>
|
||||
|
||||
<p>To personalize the content of your messages, add a placeholder to the template.</p>
|
||||
{# Personalize content #}
|
||||
<h2 class="padding-top-1" id="personalize-content">Personalize your content</h2>
|
||||
<p>Personalizing your content can increase response rates and help the recipient know the text is legitimate.</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>Including a person’s first name increases response rates.</li>
|
||||
<li>Specific details such as time and location of an appointment or where suspected fraud use occurred encourages action.</li>
|
||||
</ul>
|
||||
|
||||
<p>Placeholders are filled in with details, like a name or reference number, each time you send a
|
||||
message.</p>
|
||||
|
||||
<p>To add a placeholder to the template:</p>
|
||||
|
||||
<ol class="list list-number">
|
||||
<li>Go to the {{ service_link(current_service, 'main.choose_template', 'templates') }} page.</li>
|
||||
<li>Add a new template or choose an existing template and select <b class="bold">Edit</b>.</li>
|
||||
<li>Add a placeholder using double brackets. For example: Hello ((first name)), your reference is
|
||||
((ref number)).</li>
|
||||
<li>Select <b class="bold">Save</b>.</li>
|
||||
<h3>To personalize your content</h3>
|
||||
<ol class="list">
|
||||
<li>Add a placeholder to your content by placing two brackets around the personalized elements.</li>
|
||||
<li>You can manually enter the personalized content or you can upload a spreadsheet with the details and let Notify do the
|
||||
work for you. See <a href="#prepare-data">data preparation</a>.</li>
|
||||
</ol>
|
||||
|
||||
<p>When you send a message you can either:</p>
|
||||
<h4>Example</h4>
|
||||
<p>To personalize with the recipient's first name and include a reference number:</p>
|
||||
<p class="padding-2 bg-base-lightest">State WIC: Hello ((first name)), your reference is ((ref number)). Please provide this number when you call 123-123-1234 to make an appointment.</p>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
<li>manually fill in the placeholders yourself</li>
|
||||
<li>upload a list of personal details and let Notify do it for you</li>
|
||||
</ul>
|
||||
{# Add conditional content #}
|
||||
<h2 class="padding-top-1" id="conditional-content">Add conditional content</h2>
|
||||
<p>Conditional (or optional) content appears only when a recipient meets certain criteria. This feature allows you to make
|
||||
all or part of the message contingent upon specific criteria associated with the recipient.</p>
|
||||
|
||||
<p>If you upload a list, the column names need to match the placeholders in your template.</p>
|
||||
|
||||
<h3 class="font-body-lg" id="optional-content">Add optional content</h3>
|
||||
|
||||
<p>To add optional content to your messages:</p>
|
||||
|
||||
<ol class="list list-number">
|
||||
<li>Go to the {{ service_link(current_service, 'main.choose_template', 'templates') }} page.</li>
|
||||
<li>Add a new template or choose an existing template and select <b class="bold">Edit</b>.</li>
|
||||
<li>Use double brackets and ?? to define optional content. For example, if you only want to show something to people
|
||||
who are under 18: ((under18??Please get your application signed by a parent or guardian.))</li>
|
||||
<li>Select <b class="bold">Save</b>.</li>
|
||||
<h3>To add conditional content</h3>
|
||||
<ol class="list">
|
||||
<li>Use two brackets and ?? to define the conditional content.</li>
|
||||
<li>You can manually enter the conditional content or you can upload a spreadsheet with the personal details and let Notify
|
||||
do the work for you. See <a href="#prepare-data">data preparation</a>.</li>
|
||||
</ol>
|
||||
|
||||
<p>For each person you send this message to, specify ‘yes’ or ‘no’ to show or hide this content. You
|
||||
can either:</p>
|
||||
<h4>Examples</h4>
|
||||
<ol class="list">
|
||||
<li>If you only want to show something to people who are under 18:
|
||||
</br>
|
||||
<p class="padding-2 bg-base-lightest">State SNAP: Renewal applications are due by March 15. ((under18??Please get your application signed by a parent or
|
||||
guardian.))</p>
|
||||
</li>
|
||||
<li>
|
||||
If you want to make people who are homebound aware of the option of virtual visits (but not other message recipients):
|
||||
</br>
|
||||
<p class="padding-2 bg-base-lightest">State Medicaid: Please call 123-123-1234 to schedule an appointment. ((homebound??Virtual visits are available.))</p>
|
||||
</li>
|
||||
<li>
|
||||
If you want to send a messages in different languages to different recipients:
|
||||
</br>
|
||||
<p class="padding-2 bg-base-lightest">((English??We’ve identified unauthorized use on your EBT account. Call the phone number on the back of your card to
|
||||
cancel or go to your local CSO for immediate replacement.))((Spanish??Hemos identificado un uso no autorizado en su
|
||||
cuenta EBT. Llame al número de teléfono que aparece en el reverso de su tarjeta para cancelarla o diríjase a su CSO
|
||||
local para que se la sustituyan inmediatamente.))</p>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
<li>do this yourself</li>
|
||||
<li>upload a list of personal details and let Notify do it for you</li>
|
||||
|
||||
{# Identify your program #}
|
||||
<h2 class="padding-top-1" id="identify-program">Identify your program</h2>
|
||||
<p>You can help your recipients identify your texts as legitimate by customizing your messages to clearly state who they
|
||||
are from. Consider using the program or benefit name that is most familiar to your recipients.</p>
|
||||
|
||||
<h3>To customize your program name</h3>
|
||||
<p>To change the text message sender from the default service name:</p>
|
||||
<ol class="list">
|
||||
<li>Go to the <a href="{{ url_for('main.service_settings', service_id=current_service.id) }}">Settings page</a></li>
|
||||
<li>Select “Start text messages with service name.”</li>
|
||||
<li>Change the service name to a familiar program or benefit.</li>
|
||||
</ol>
|
||||
|
||||
{# Prepare your data #}
|
||||
<h2 class="padding-top-1" id="prepare-data">Prepare your data</h2>
|
||||
<p>The easiest and most efficient way to personalize your content is by uploading a spreadsheet. Notify can accommodate
|
||||
many file formats and structures.</p>
|
||||
|
||||
<h3>File format</h3>
|
||||
<p>Notify can accept files in the following formats: CSV, TSV, ODS, and Microsoft Excel.</p>
|
||||
|
||||
<h3>File structure</h3>
|
||||
<ul class="list">
|
||||
<li>The phone number must be in the first column (Column A) and must be labeled <strong>Phone number</strong>.</li>
|
||||
<li>Each column must have a unique name.</li>
|
||||
<li>If you are using Excel, you must either disable the”auto-date/time format” for time and date columns or convert the file
|
||||
to a CSV prior to loading. (If you do not, Excel will display date/time data in a confusing format.)</li>
|
||||
</ul>
|
||||
|
||||
<p>If you upload a list, the column names need to match the optional content in your template.</p>
|
||||
<h3>Formatting personalized content</h3>
|
||||
<p>If you are sending a message with personalized content, such as the first name of the recipient or the appointment time
|
||||
and location, the names of the column headings have to match the indicator included in the message template.</p>
|
||||
<p>For example, if the personalized content is the first name of the recipient, and we are using the spreadsheet below, the
|
||||
indicator in the message needs to be ((First name)), not ((firstname)) or ((name)).</p>
|
||||
|
||||
<h2 class="font-body-lg" id="customization">Message customization</h2>
|
||||
<h3>Formatting conditional content</h3>
|
||||
<p>If you are sending messages with conditional content, such as content based on the recipient’s preferred language or
|
||||
location, the flag to receive the content needs to be captured in its own column with a Yes or No (Y/N) flag.</p>
|
||||
|
||||
<h3 class="font-body-lg">Change the text message sender</h3>
|
||||
<h3>Example</h3>
|
||||
<table class="usa-table">
|
||||
<caption class="usa-sr-only">
|
||||
Example
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Phone number</th>
|
||||
<th scope="col">First name</th>
|
||||
<th scope="col">Last name</th>
|
||||
<th scope="col">Spanish</th>
|
||||
<th scope="col">English</th>
|
||||
<th scope="col">Date</th>
|
||||
<th scope="col">Time</th>
|
||||
<th scope="col">Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>123-456-7890</td>
|
||||
<td>Lulu</td>
|
||||
<td>Praether</td>
|
||||
<td>N</td>
|
||||
<td>Y</td>
|
||||
<td>November 2, 2023</td>
|
||||
<td>3:25</td>
|
||||
<td>123 Ford Rd.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>234-567-8901</td>
|
||||
<td>Sela</td>
|
||||
<td>Appel</td>
|
||||
<td>Y</td>
|
||||
<td>N</td>
|
||||
<td>November 2, 2023</td>
|
||||
<td>4:00</td>
|
||||
<td>123 Ford Rd.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>123-456-7890</td>
|
||||
<td>Dexter</td>
|
||||
<td>Moseley</td>
|
||||
<td>N</td>
|
||||
<td>Y</td>
|
||||
<td>November 2, 2023</td>
|
||||
<td>2:00</td>
|
||||
<td>123 Ford Rd.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>The text message sender tells your users who the message is from.</p>
|
||||
{# Preventing fraud #}
|
||||
<h2 class="padding-top-1" id="prevent-fraud">Preventing fraud</h2>
|
||||
<p>Texting fraud is ever prevalent and while we can’t eliminate the risk, we can reduce the chances that recipients will
|
||||
fall victim to fraud.</p>
|
||||
<ol class="list">
|
||||
<li>Never send unnecessary or protected private information.</li>
|
||||
<li>Remind your recipients that text messaging is not a secure means of communication and they should not send you private
|
||||
information via text.</li>
|
||||
<li>Only include links to websites and online applications that are secure.</li>
|
||||
<li>Send an introductory text letting recipients know you will be texting them and to save the number in their phone.</li>
|
||||
<li>Include an auto-response with the name of your organization and a contact phone number.</li>
|
||||
</ol>
|
||||
|
||||
<p>To change the text message sender from the default of ‘Notify.gov’:</p>
|
||||
|
||||
<ol class="list list-number">
|
||||
<li>Go to the <b class="bold">Text message settings</b> section of the {{
|
||||
service_link(current_service, 'main.service_settings', 'settings') }} page.</li>
|
||||
<li>Select <b class="bold">Manage</b> on the <b class="bold">Text message
|
||||
senders</b> row.</li>
|
||||
<li>Select <b class="bold">Change</b> or <b class="bold">Add text message
|
||||
sender</b>.</li>
|
||||
<h3>Examples</h3>
|
||||
<ol class="list">
|
||||
<li>Introductory text:
|
||||
</br>
|
||||
<p class="padding-2 bg-base-lightest">State Transit Dept: We're piloting a new way to get important reminders to our staff. Save this number to your phone,
|
||||
you'll receive updates from us here.</p>
|
||||
</li>
|
||||
<li>Auto-response text:
|
||||
</br>
|
||||
<p class="padding-2 bg-base-lightest">State Agency: This number is unmonitored. To contact us, call us at 123-123-1234. We will never ask for personal details
|
||||
in a text. If you have questions about how to protect your privacy, see statename.gov/privacy.</p>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -22,18 +22,22 @@
|
||||
<p>This roadmap is only a guide. It does not cover everything we do, and some things may change.</p>
|
||||
<p>You can <a class="usa-link" href="{{url_for('.support')}}">contact us</a> if you have any questions about the roadmap or suggestions for new features.</p>
|
||||
|
||||
<h2 class="font-body-lg" id="things-we-are-working-on">What we’re working on</h2>
|
||||
<h2 id="things-we-are-working-on">What we’re working on</h2>
|
||||
|
||||
<h3 class="font-body-lg" id="now">Now</h3>
|
||||
<h3 id="now">Now</h3>
|
||||
|
||||
<p>We are investigating the Notify concept, building on the notifications tool pioneered by the UK.</p>
|
||||
<p>To do this, we are convening a pilot with a small set of partners.</p>
|
||||
<p>Goals during this stage:</p>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
<li>Achieve compliance to begin piloting, such as ATO and privacy standards</li>
|
||||
<li>Demonstrate that a government-run notifications tool provides a unique value</li>
|
||||
<li>Gather data from the pilot to improve the product</li>
|
||||
<li>Achieve compliance to begin piloting, such as ATO and privacy standards.
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
use xlink:href="/assets/img/sprite.svg#check"></use>
|
||||
</svg>
|
||||
</li>
|
||||
<li>Demonstrate that a government-run notifications tool provides a unique value.</li>
|
||||
<li>Gather data from the pilot to improve the product.</li>
|
||||
</ul>
|
||||
|
||||
<p>Features prioritized during this stage:</p>
|
||||
@@ -46,53 +50,38 @@
|
||||
<li>Message send/failure analytics</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="font-body-lg" id="next">Next</h3>
|
||||
<h3id="next">Next</h3>
|
||||
|
||||
<p>If the pilot is successful, we hope to recruit additional high-impact partners to improve outcomes for low-income individuals and families.</p>
|
||||
|
||||
<p>Goals during this stage:</p>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
<li>Complement Notify with practical guidance and support services</li>
|
||||
<li>Iterate on existing features and implement new features based on what we've learned so far</li>
|
||||
<li>Hone our measurement approaches to better quantify impact</li>
|
||||
<li>Complement Notify with practical guidance and support services.</li>
|
||||
<li>Iterate on existing features and implement new features based on what we've learned so far.</li>
|
||||
<li>Hone our measurement approaches to better quantify impact.</li>
|
||||
</ul>
|
||||
|
||||
<p>Features prioritized during this stage:</p>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
<ul class="list">
|
||||
<li>SMS sending via API integration</li>
|
||||
<li>Single-level decision two-way messaging (e.g. reply “YES” if, or “NO” if…)</li>
|
||||
<li>Self-service account creation</li>
|
||||
<li>Application status page</li>
|
||||
<li>Scheduled send option</li>
|
||||
<li>Improved scheduled send option</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="font-body-lg" id="later">Later</h3>
|
||||
<h3 id="later">Later</h3>
|
||||
|
||||
<p>In the future, we may decide to expand beyond SMS, or to offer the service government-wide.</p>
|
||||
|
||||
<p>Features to be considered during this stage:</p>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
<ul class="list">
|
||||
|
||||
<li>Email sending via UI and API</li>
|
||||
<li>Multiple-level decision two-way messaging (greater than one layer decision-tree)</li>
|
||||
<li>Open-text reply two-way messaging (rather than reply yes or no, 1 or 2, etc.)</li>
|
||||
<li>Two-way messaging</li>
|
||||
<li>Multilingual interface and content library options</li>
|
||||
<li>Recurring scheduled send (such as “Send each Monday for 3 weeks”)</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<!-- <h2 class="font-body-lg" id="things-we-have-done">Things we’ve done</h2>
|
||||
|
||||
<p>Recently we have:</p>
|
||||
|
||||
<ul class="list list-bullet">
|
||||
|
||||
<li></li>
|
||||
<li></li>
|
||||
|
||||
</ul> -->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -15,22 +15,52 @@
|
||||
<li>manage risks around information</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="font-body-lg" id="data">Data</h2>
|
||||
<p>On Notify, data is encrypted:</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>when it passes through the service</li>
|
||||
<li>when it’s stored on the service</li>
|
||||
</ul>
|
||||
<p>Any recipient data you upload is only held for seven days.</p>
|
||||
<!-- <p>If you <a class="usa-link" href="/features/email#send-files">send a file by email</a>, the file will be available for the recipient to download for 18 months.</p> -->
|
||||
<h2 id="infrastructure">Infrastructure</h2>
|
||||
|
||||
<h2 class="font-body-lg" id="technical-security">Technical security</h2>
|
||||
<!-- <p>Other technical security controls on Notify include:</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>compliance with National Cyber Security Centre (NCSC) Cloud Security Principles</li>
|
||||
<li>protective monitoring to record activity, and raise alerts about any suspicious activity</li>
|
||||
<li>using JSON Web Tokens, to avoid sending API keys when your service talks to Notify</li>
|
||||
</ul> -->
|
||||
<p>Notify.gov is comprised of two applications both running on <a href="https://www.cloud.gov">cloud.gov</a>:</p>
|
||||
<ul class="list">
|
||||
<li>Admin, a Flask website running on the python_buildpack which hosts agency user-facing UI</li>
|
||||
<li>API, a Flask application running on the python_buildpack hosting the Notify API</li>
|
||||
</ul>
|
||||
|
||||
<p>Notify.gov utilizes several cloud.gov-provided services through Amazon Web Services (AWS):</p>
|
||||
<ul class="list">
|
||||
<li>AWS S3 buckets for temporary file storage</li>
|
||||
<li>AWS Elasticache (Redis) for caching data and enqueueing background tasks</li>
|
||||
<li>AWS RDS (PostgreSQL) for system data storage</li>
|
||||
</ul>
|
||||
|
||||
<p>Notify.gov also provisions and uses two AWS services via a <a href="https://github.com/GSA/datagov-ssb">supplemental service broker</a>:</p>
|
||||
<ul class="list">
|
||||
<li><a href="https://aws.amazon.com/sns/">AWS SNS</a> for sending SMS messages</li>
|
||||
<li><a href="https://aws.amazon.com/ses/">AWS SES</a> for sending email messages (note: email function is currently unavailable to users for pilot)</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="current-security-review">Current security review</h2>
|
||||
<p>Currently, Notify.gov operates under a GSA <a href="https://www.gsa.gov/system/files/Lightweight-Security-Authorization-Process-%28LATO%29%20%5BCIO-IT-Security-14-68-Rev-7%5D%2009-17-2021docx%20%281%29.pdf">Lightweight Authority to Operate (LATO)</a>. This federal security authorization
|
||||
process leverages security controls provided by National Institute of Standards and Technology (NIST). The process is
|
||||
focused on operational security from both a functional and assurance perspective.</p>
|
||||
<p>We are pursuing a full Authority to Operate (ATO)</p>
|
||||
|
||||
<h2 id="data">Data</h2>
|
||||
<p>To send a message, agencies upload a spreadsheet of phone numbers and other necessary data from their existing data
|
||||
management system. On Notify.gov, data is encrypted when it passes through the service and when it’s stored on the
|
||||
service.</p>
|
||||
<p>Notify.gov is not a system of record and as a result does not have a SORN. Agencies are responsible for managing their
|
||||
data outside of Notify.gov.</p>
|
||||
<ul class="list">
|
||||
<li>Notify stores data within the cloud.gov-managed PostgreSQL database and <a href="https://cloud.gov/docs/services/s3/#encryption">S3 buckets</a>. The entire database is <a href="https://cloud.gov/docs/services/relational-database/#encryption">encrypted at
|
||||
the disk level</a>.</li>
|
||||
<li>Additionally, data is encrypted at the field level when it is stored in the database, using OpenSSL 3.0.8 via the Python
|
||||
cryptography library. The implementation is available <a href="https://github.com/GSA/notifications-utils/blob/main/notifications_utils/clients/encryption/encryption_client.py">on GitHub</a>.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="data-retention">Data retention</h2>
|
||||
<p>Any recipient data uploaded is only held for seven days; all personally identifiable information (PII) is deleted for
|
||||
successful messages, so data is retained only for unsuccessful messages.
|
||||
</p>
|
||||
|
||||
<h2 id="technical-security">Technical security</h2>
|
||||
|
||||
<h3 class="font-body-lg">Protect sensitive information</h3>
|
||||
<p>Some messages include sensitive information like security codes or password reset links.</p>
|
||||
@@ -46,6 +76,23 @@
|
||||
</ul>
|
||||
<p>If signing in with a text message is a problem for your team, <a class="usa-link" href="{{ url_for('main.support') }}">contact us</a> to find out about using an email link instead.</p>
|
||||
|
||||
<img src="{{ asset_url('images/product/security-review-message.png') }}"
|
||||
alt="Screenshot of a teat message in review with the link to 'hide personalization after sending' circled.">
|
||||
|
||||
<h4>How to hide PII after sending a message</h4>
|
||||
|
||||
<h3>User permissions and signing in</h3>
|
||||
<p>You can set different user permissions in Notify. This lets you control who in your team has access to certain parts of
|
||||
the service.</p>
|
||||
|
||||
<h3>Multi-factor authentication (MFA)</h3>
|
||||
<p>Notify.gov uses <a href="https://login.gov/">Login.gov</a> to authenticate users.</p>
|
||||
<ul class="list">
|
||||
<li>Your email address and password</li>
|
||||
<li>A code from an authenticator app or sent by Login.gov in the form of a text message</li>
|
||||
</ul>
|
||||
<p>If signing in with a text message is a problem for your team, <a href="https://beta.notify.gov/support">contact us</a> to find out about using an email link instead.</p>
|
||||
|
||||
<!-- <h2 class="font-body-lg" id="information-risk-management">Information risk management</h2>
|
||||
<p>Our approach to information risk management follows NCSC guidance. It assesses:</p>
|
||||
<ul class="list list-bullet">
|
||||
|
||||
@@ -8,43 +8,20 @@
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="usa-prose margin-bottom-10">
|
||||
|
||||
<h1 class="font-body-2xl margin-bottom-3">Contact us</h1>
|
||||
|
||||
<p>We are available at <a class="usa-link" href="mailto:notify-support@gsa.gov">notify-support@gsa.gov</a>.</p>
|
||||
<p>Notify is designed to be easy to use.</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>For information on personalization and data preparation, see <a href="/using-notify/guidance">Guidance</a>.</li>
|
||||
<li>For help interpreting delivery reports, see <a href="/using-notify/delivery-status">Delivery Status</a>.</li>
|
||||
<li>For details on pricing and what counts as a message part, see <a href="/using-notify/pricing"></a>Pricing.</li>
|
||||
</ul>
|
||||
|
||||
<p>If you have other questions, we are available at <a class="usa-link" href="mailto:notify-support@gsa.gov">notify-support@gsa.gov</a>.</p>
|
||||
|
||||
<p>You can expect a response within one business day.</p>
|
||||
|
||||
<!-- <p>Notify.gov provides 24-hour online support.</p> -->
|
||||
|
||||
<!-- {% call form_wrapper() %}
|
||||
{% if current_user.is_authenticated %}
|
||||
{{ form.support_type }}
|
||||
{% else %}
|
||||
<p>
|
||||
What do you need help with?
|
||||
</p>
|
||||
{{ form.who }}
|
||||
{% endif %}
|
||||
{{ page_footer('Continue') }}
|
||||
{% endcall %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-8">
|
||||
<h2 class="font-body-lg">Office hours</h2>
|
||||
<p>Our office hours are 9:30am to 5:30pm, Monday to Friday.</p>
|
||||
<p>When you report a problem in office hours, we’ll aim to read it within 30 minutes and reply within one working day.</p>
|
||||
|
||||
<h2 class="font-body-lg">Out-of-hours</h2>
|
||||
<p>Outside office hours, response times depend on whether you’re reporting an emergency.</p>
|
||||
<p>If it’s an emergency, we’ll reply within 30 minutes and update you every hour until the problem’s fixed.</p>
|
||||
<p>If your problem is not an emergency, we’ll reply within one working day.</p>
|
||||
<p>A problem is only classed as an emergency if you have a live service and:</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>nobody on your team can sign in</li>
|
||||
<li>a ‘technical difficulties’ error appears when you try to upload a file</li>
|
||||
<li>a 500 response code appears when you try to send messages using the API</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
1
app/utils/govuk_frontend_jinja/__init__.py
Normal file
1
app/utils/govuk_frontend_jinja/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .templates import Environment # noqa
|
||||
30
app/utils/govuk_frontend_jinja/flask_ext.py
Normal file
30
app/utils/govuk_frontend_jinja/flask_ext.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from flask.templating import Environment as FlaskEnvironment
|
||||
from jinja2 import select_autoescape
|
||||
|
||||
from app.utils.govuk_frontend_jinja.templates import Environment as NunjucksEnvironment
|
||||
from app.utils.govuk_frontend_jinja.templates import (
|
||||
NunjucksExtension,
|
||||
NunjucksUndefined,
|
||||
)
|
||||
|
||||
|
||||
class Environment(NunjucksEnvironment, FlaskEnvironment):
|
||||
pass
|
||||
|
||||
|
||||
def init_govuk_frontend(app):
|
||||
"""Use the govuk_frontend_jinja Jinja environment in a Flask app
|
||||
|
||||
>>> from flask import Flask
|
||||
>>> app = Flask("cheeseshop_service")
|
||||
>>> init_govuk_frontend(app)
|
||||
"""
|
||||
app.jinja_environment = Environment
|
||||
app.select_jinja_autoescape = select_autoescape(
|
||||
("html", "htm", "xml", "xhtml", "njk")
|
||||
)
|
||||
jinja_options = app.jinja_options.copy()
|
||||
jinja_options["extensions"].append(NunjucksExtension)
|
||||
jinja_options["undefined"] = NunjucksUndefined
|
||||
app.jinja_options = jinja_options
|
||||
return app
|
||||
292
app/utils/govuk_frontend_jinja/templates.py
Normal file
292
app/utils/govuk_frontend_jinja/templates.py
Normal file
@@ -0,0 +1,292 @@
|
||||
import builtins
|
||||
import os.path as path
|
||||
import re
|
||||
from collections.abc import Sized
|
||||
|
||||
import jinja2
|
||||
import jinja2.ext
|
||||
from jinja2.lexer import Token
|
||||
from markupsafe import Markup
|
||||
|
||||
|
||||
def njk_to_j2(template):
|
||||
# Some component templates (such as radios) use `items` as the key of
|
||||
# an object element. However `items` is also the name of a dictionary
|
||||
# method in Python, and Jinja2 will prefer to return this attribute
|
||||
# over the dict item. Handle specially.
|
||||
template = re.sub(r"\.items\b", ".items__njk", template)
|
||||
|
||||
# Some component templates (such as radios) append the loop index to a
|
||||
# string. As the loop index is an integer this causes a TypeError in
|
||||
# Python. Jinja2 has an operator `~` for string concatenation that
|
||||
# converts integers to strings.
|
||||
template = template.replace("+ loop.index", "~ loop.index")
|
||||
|
||||
# The Character Count component in version 3 concatenates the word count
|
||||
# with the hint text. As the word count is an integer this causes a
|
||||
# TypeError in Python. Jinja2 has an operator `~` for string
|
||||
# concatenation that converts integers to strings.
|
||||
template = template.replace(
|
||||
"+ (params.maxlength or params.maxwords) +",
|
||||
"~ (params.maxlength or params.maxwords) ~",
|
||||
)
|
||||
|
||||
# Nunjucks uses elseif, Jinja uses elif
|
||||
template = template.replace("elseif", "elif")
|
||||
|
||||
# Some component templates (such as input) call macros with params as
|
||||
# an object which has unqoted keys. This causes Jinja to silently
|
||||
# ignore the values.
|
||||
template = re.sub(
|
||||
r"""^([ ]*)([^ '"#\r\n:]+?)\s*:""", r"\1'\2':", template, flags=re.M
|
||||
)
|
||||
|
||||
# govukFieldset can accept a call block argument, however the Jinja
|
||||
# compiler does not detect this as the macro body is included from
|
||||
# the template file. A workaround is to patch the declaration of the
|
||||
# macro to include an explicit caller argument.
|
||||
template = template.replace(
|
||||
"macro govukFieldset(params)", "macro govukFieldset(params, caller=none)"
|
||||
)
|
||||
|
||||
# Many components feature an attributes field, which is supposed to be
|
||||
# a dictionary. In the template for these components, the keys and values
|
||||
# are iterated. In Python, the default iterator for a dict is .keys(), but
|
||||
# we want .items().
|
||||
# This only works because our undefined implements .items()
|
||||
# We've tested this explicitly with: govukInput, govukCheckbox, govukTable,
|
||||
# govukSummaryList
|
||||
template = re.sub(
|
||||
r"for attribute, value in (params|item|cell|action).attributes",
|
||||
r"for attribute, value in \1.attributes.items()",
|
||||
template,
|
||||
flags=re.M,
|
||||
)
|
||||
|
||||
# Some templates try to set a variable in an outer block, which is not
|
||||
# supported in Jinja. We create a namespace in those templates to get
|
||||
# around this.
|
||||
template = re.sub(
|
||||
r"""^([ ]*)({% set describedBy =( params.*describedBy if params.*describedBy else)? "" %})""",
|
||||
r"\1{%- set nonlocal = namespace() -%}\n\1\2",
|
||||
template,
|
||||
flags=re.M,
|
||||
)
|
||||
# Change any references to describedBy to be nonlocal.describedBy,
|
||||
# unless describedBy is a dictionary key (i.e. quoted or dotted).
|
||||
template = re.sub(r"""(?<!['".])describedBy""", r"nonlocal.describedBy", template)
|
||||
# govukSummaryList
|
||||
template = re.sub(
|
||||
r"""^([ ]*)({% set anyRowHasActions = false %})""",
|
||||
r"\1{%- set nonlocal = namespace() -%}\n\1\2",
|
||||
template,
|
||||
flags=re.M,
|
||||
)
|
||||
template = re.sub(
|
||||
r"""(?<!['".])anyRowHasActions""", r"nonlocal.anyRowHasActions", template
|
||||
)
|
||||
# govukRadios and govukCheckboxes
|
||||
# Since both of these templates set describedBy before isConditional, we can use
|
||||
# the existing nonlocal.
|
||||
template = re.sub(
|
||||
r"""(?<!['".])isConditional""", r"nonlocal.isConditional", template
|
||||
)
|
||||
|
||||
# Issue#16: some component templates test the length of an array by trying
|
||||
# to get an attribute `.length`. We need to handle this specially because
|
||||
# .length isn't a thing in python
|
||||
template = re.sub(r"\.length\b", ".length__njk", template)
|
||||
|
||||
# see `indent_njk`
|
||||
template = re.sub(re.escape("| indent") + r"\b", "| indent_njk", template)
|
||||
|
||||
return template
|
||||
|
||||
|
||||
def indent_njk(s, width=4, first=False, blank=False, indentfirst=None):
|
||||
"""Return a copy of the string with each line indented by 4 spaces."""
|
||||
|
||||
# Copied from
|
||||
# https://github.com/pallets/jinja/blob/a2f5e2c7972c4d5148c1c75c724e24950d8605bc/jinja2/filters.py#L536-L580
|
||||
# to include an unreleased fix for https://github.com/pallets/jinja/pull/826
|
||||
# which causes the file upload component to escape HTML markup.
|
||||
# TODO: Remove once jinja2 2.11 is released and in use.
|
||||
|
||||
if indentfirst is not None:
|
||||
first = indentfirst
|
||||
|
||||
indention = " " * width
|
||||
newline = "\n"
|
||||
|
||||
if isinstance(s, Markup):
|
||||
indention = Markup(indention)
|
||||
newline = Markup(newline)
|
||||
|
||||
s += newline # this quirk is necessary for splitlines method
|
||||
|
||||
if blank:
|
||||
rv = (newline + indention).join(s.splitlines())
|
||||
else:
|
||||
lines = s.splitlines()
|
||||
rv = lines.pop(0)
|
||||
|
||||
if lines:
|
||||
rv += newline + newline.join(
|
||||
indention + line if line else line for line in lines
|
||||
)
|
||||
|
||||
if first:
|
||||
rv = indention + rv
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class NunjucksExtension(jinja2.ext.Extension):
|
||||
def filter_stream(self, stream):
|
||||
if stream.filename and stream.filename.endswith(".njk"):
|
||||
return self.filter_njk_stream(stream)
|
||||
else:
|
||||
return stream
|
||||
|
||||
def filter_njk_stream(self, stream):
|
||||
for token in stream:
|
||||
# patch strict equality operator `===`
|
||||
if token.test("eq:==") and stream.current.test("assign:="):
|
||||
yield Token(token.lineno, "name", "is")
|
||||
yield Token(token.lineno, "name", "sameas")
|
||||
stream.skip(1)
|
||||
else:
|
||||
yield token
|
||||
|
||||
def preprocess(self, source, name, filename=None):
|
||||
if filename and filename.endswith(".njk"):
|
||||
return njk_to_j2(source)
|
||||
else:
|
||||
return source
|
||||
|
||||
|
||||
class NunjucksUndefined(jinja2.runtime.Undefined):
|
||||
__slots__ = ()
|
||||
|
||||
# copied from https://github.com/pallets/jinja/commit/19133d40593ced72eb28e230588abcc70d8b9f82
|
||||
def __getattr__(self, _):
|
||||
"""Make undefined that is chainable, where both
|
||||
__getattr__ and __getitem__ return itself rather than
|
||||
raising an :exc:`UndefinedError`:
|
||||
>>> foo = ChainableUndefined(name='foo')
|
||||
>>> str(foo.bar['baz'])
|
||||
''
|
||||
>>> foo.bar['baz'] + 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
"""
|
||||
return self
|
||||
|
||||
__getitem__ = __getattr__
|
||||
|
||||
# Allow treating undefined as an (empty) dictionary.
|
||||
# This works because Undefined is an iterable.
|
||||
def items(self):
|
||||
return self
|
||||
|
||||
# Allow escaping with Markup. This is required when
|
||||
# autoescape is enabled. Debugging this issue was
|
||||
# annoying; the error messages were not clear as to
|
||||
# the cause of the issue (see upstream pull request
|
||||
# for info https://github.com/pallets/jinja/pull/1047)
|
||||
def __html__(self):
|
||||
return str(self)
|
||||
|
||||
# attempt to behave a bit like js's `undefined` when concatenation is attempted
|
||||
def __add__(self, other):
|
||||
if isinstance(other, str):
|
||||
return "undefined" + other
|
||||
return super().__add__(other)
|
||||
|
||||
def __radd__(self, other):
|
||||
if isinstance(other, str):
|
||||
return other + "undefined"
|
||||
return super().__radd__(other)
|
||||
|
||||
|
||||
class NunjucksCodeGenerator(jinja2.compiler.CodeGenerator):
|
||||
def visit_CondExpr(self, node, frame):
|
||||
if not (self.filename or "").endswith(".njk"):
|
||||
return super().visit_CondExpr(node, frame)
|
||||
|
||||
# else our replacement, which is based on that in
|
||||
# https://github.com/pallets/jinja/blob/c4c4088945a2c12535f539be7f5453b9ca94666c/jinja2/compiler.py#L1613
|
||||
def write_expr2():
|
||||
if node.expr2 is not None:
|
||||
return self.visit(node.expr2, frame)
|
||||
# rather than complaining about a missing else
|
||||
# clause we just assume it to be the empty
|
||||
# string for nunjucks compatibility
|
||||
return self.write('""')
|
||||
|
||||
self.write("(")
|
||||
self.visit(node.expr1, frame)
|
||||
self.write(" if ")
|
||||
self.visit(node.test, frame)
|
||||
self.write(" else ")
|
||||
write_expr2()
|
||||
self.write(")")
|
||||
|
||||
|
||||
_njk_signature = "__njk"
|
||||
_builtin_function_or_method_type = type({}.keys)
|
||||
|
||||
|
||||
class Environment(jinja2.Environment):
|
||||
code_generator_class = NunjucksCodeGenerator
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("extensions", [NunjucksExtension])
|
||||
kwargs.setdefault("undefined", NunjucksUndefined)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.filters["indent_njk"] = indent_njk
|
||||
|
||||
def join_path(self, template, parent):
|
||||
"""Enable the use of relative paths in template import statements"""
|
||||
if template.startswith(("./", "../")):
|
||||
return path.normpath(path.join(path.dirname(parent), template))
|
||||
else:
|
||||
return template
|
||||
|
||||
def _handle_njk(method_name):
|
||||
def inner(self, obj, argument):
|
||||
if isinstance(argument, str) and argument.endswith(_njk_signature):
|
||||
# a njk-originated access will always be assuming a dict lookup before an attr
|
||||
final_method_name = "getitem"
|
||||
final_argument = argument[: -len(_njk_signature)]
|
||||
else:
|
||||
final_argument = argument
|
||||
final_method_name = method_name
|
||||
|
||||
# pleasantly surprised that super() works in this context
|
||||
retval = builtins.getattr(super(), final_method_name)(obj, final_argument)
|
||||
|
||||
if (
|
||||
argument == f"length{_njk_signature}"
|
||||
and isinstance(retval, jinja2.runtime.Undefined)
|
||||
and isinstance(obj, Sized)
|
||||
):
|
||||
return len(obj)
|
||||
if (
|
||||
isinstance(argument, str)
|
||||
and argument.endswith(_njk_signature)
|
||||
and isinstance(retval, _builtin_function_or_method_type)
|
||||
):
|
||||
# the lookup has probably gone looking for attributes and found a builtin method. because
|
||||
# any njk-originated lookup will have been made to prefer dict lookups over attributes, we
|
||||
# can be fairly sure there isn't a dict key matching this - so we should just call this a
|
||||
# failure.
|
||||
return self.undefined(obj=obj, name=final_argument)
|
||||
return retval
|
||||
|
||||
return inner
|
||||
|
||||
getitem = _handle_njk("getitem")
|
||||
|
||||
getattr = _handle_njk("getattr")
|
||||
Reference in New Issue
Block a user