Merge pull request #1195 from alphagov/better-template-management

Make it easier to find and preview templates
This commit is contained in:
Chris Hill-Scott
2017-03-20 15:49:16 +00:00
committed by GitHub
27 changed files with 395 additions and 349 deletions

View File

@@ -0,0 +1,44 @@
(function(Modules) {
"use strict";
let normalize = (string) => string.toLowerCase().replace(/ /g,'');
let filter = ($searchBox, $targets) => () => {
let query = normalize($searchBox.val());
$targets.each(function() {
let content = $(this).text();
$(this).toggle(
normalize(content).indexOf(normalize(query)) > -1
);
});
};
Modules.LiveSearch = function() {
this.start = function(component) {
let $component = $(component);
let $searchBox = $('input', $component);
let filterFunc = filter(
$searchBox,
$($component.data('targets'))
);
$searchBox.on('keyup input', filterFunc);
filterFunc();
};
};
})(window.GOVUK.Modules);

View File

@@ -39,6 +39,11 @@
margin-bottom: $gutter-half; margin-bottom: $gutter-half;
} }
.bottom-gutter-1-3 {
@extend %bottom-gutter;
margin-bottom: $gutter/3;
}
.bottom-gutter-3-2 { .bottom-gutter-3-2 {
@extend %bottom-gutter; @extend %bottom-gutter;
margin-bottom: $gutter * 3/2; margin-bottom: $gutter * 3/2;

View File

@@ -1,9 +1,11 @@
$white-50-opaque: rgba($white, 0.5); $white-50-opaque: rgba($white, 0.5);
$button-bottom-border-colour: rgba(0, 0, 0, 0.17); $button-bottom-border-colour: rgba(0, 0, 0, 0.17);
$email-message-gutter: $gutter * 2;
.email-message { .email-message {
margin-bottom: $gutter; margin-bottom: $gutter;
border: 1px solid $border-colour;
&-meta { &-meta {
@@ -13,35 +15,34 @@ $button-bottom-border-colour: rgba(0, 0, 0, 0.17);
td, td,
th { th {
@include core-19; @include core-19;
border-bottom: 0; border-top: 0;
border-top: 1px solid $border-colour; border-bottom: 1px solid $border-colour;
vertical-align: top; vertical-align: top;
} }
th { th {
color: $secondary-text-colour; color: $secondary-text-colour;
padding-left: $email-message-gutter;
} }
td { td {
width: 99%; width: 99%;
padding-right: $email-message-gutter;
} }
} }
&-from { &-from {
padding-top: 15px; padding-top: 15px;
border-top: 1px solid $border-colour;
} }
&-body { &-body {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: $gutter-half 0 0 0; padding: $gutter-half $email-message-gutter 0 $email-message-gutter;
margin: 0 0 $gutter * 1.5 0; margin: 0 0 0 0;
clear: both; clear: both;
border-top: 1px solid $border-colour;
border-bottom: 1px solid $border-colour;
position: relative; position: relative;
word-wrap: break-word; word-wrap: break-word;
@@ -49,11 +50,13 @@ $button-bottom-border-colour: rgba(0, 0, 0, 0.17);
.js-enabled & { .js-enabled & {
max-height: 92px; max-height: 92px;
padding: 0;
overflow: hidden; overflow: hidden;
} }
.js-enabled .expanded & { .js-enabled .expanded & {
max-height: none; max-height: none;
padding-bottom: $gutter;
} }
} }

View File

@@ -1,59 +1,34 @@
// Easing function from: http://easings.net/#easeOutBack $iso-paper-ratio: 141.42135624%;
$transition-easing: cubic-bezier(0.175, 0.885, 0.32, 1.275);
$iso-paper-ratio: 70.710678118%; @keyframes ellipsis {
to {
width: 1.25em;
}
}
.letter { .letter {
padding: 0; padding: 0;
margin: 0 0 $gutter 0; margin: 0 0 $gutter 0;
position: relative; position: relative;
background: $panel-colour;
&:after {
content: "";
display: block;
z-index: 10;
position: absolute;
top: 1px;
left: 5px;
height: 0;
padding: $iso-paper-ratio 0 0 0;
width: 100%;
background: $highlight-colour;
transform-origin: left bottom;
transform: scale(1, 0.985);
box-shadow: inset 0 0 0 1px $border-colour;
transition: all 0.2s $transition-easing;
}
&:before { &:before {
content: "";
z-index: 15;
position: absolute; position: absolute;
bottom: -3px; top: 10%;
right: -5px; left: 50%;
width: 4px; margin-left: -0.5em;
height: 7px; font-size: 96px;
background: $white; color: $white;
transform: rotate(45deg); overflow: hidden;
box-shadow: inset 1px 0 0 0 $border-colour; display: block;
} vertical-align: bottom;
animation: ellipsis steps(4,end) 1.3s infinite;
&:hover { content: "\2026"; // ellipsis
width: 0px;
&:after {
transform: skew(-1.3deg, 0deg) scale(1, 0.96);
transition: all 0.1s $transition-easing;
background: mix($highlight-colour, $white);
}
&:before {
transform: rotate(50deg);
}
} }
a { a {
display: block; display: block;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
@@ -61,19 +36,6 @@ $iso-paper-ratio: 70.710678118%;
padding: $iso-paper-ratio 0 0 0; padding: $iso-paper-ratio 0 0 0;
position: relative; position: relative;
z-index: 20; z-index: 20;
box-shadow: 0 1px 0 0 $border-colour;
&:focus {
outline: none;
box-shadow: 0 3px 0 0 $yellow;
img {
box-shadow: inset 0 0 0 3px $yellow;
}
}
} }
img { img {

View File

@@ -0,0 +1,16 @@
input[type=search] {
// overrides this nasty global from GOV.UK template
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.live-search {
display: none;
.js-enabled & {
display: block;
}
}

View File

@@ -2,9 +2,9 @@
.sms-message-wrapper { .sms-message-wrapper {
width: 100%; width: 100%;
max-width: 410px; max-width: 450px;
box-sizing: border-box; box-sizing: border-box;
padding: $gutter-half; padding: $gutter-half $gutter-half $gutter-half $gutter-half;
background: $panel-colour; background: $panel-colour;
border: 1px solid $panel-colour; border: 1px solid $panel-colour;
border-radius: 5px; border-radius: 5px;

View File

@@ -1,6 +1,6 @@
%tick-cross { %tick-cross {
@include core-16; @include core-19;
display: inline-block; display: inline-block;
background-size: 19px 19px; background-size: 19px 19px;
background-repeat: no-repeat; background-repeat: no-repeat;

View File

@@ -58,6 +58,7 @@ $path: '/static/images/';
@import 'components/tick-cross'; @import 'components/tick-cross';
@import 'components/list-entry'; @import 'components/list-entry';
@import 'components/letter'; @import 'components/letter';
@import 'components/live-search';
@import 'components/vendor/breadcrumbs'; @import 'components/vendor/breadcrumbs';
@import 'components/vendor/responsive-embed'; @import 'components/vendor/responsive-embed';
@@ -65,6 +66,7 @@ $path: '/static/images/';
@import 'views/users'; @import 'views/users';
@import 'views/api'; @import 'views/api';
@import 'views/product-page'; @import 'views/product-page';
@import 'views/template';
// TODO: break this up // TODO: break this up
@import 'app'; @import 'app';

View File

@@ -0,0 +1,41 @@
.template-container {
position: relative;
}
%edit-template-link,
.edit-template-link {
@include core-19;
position: absolute;
background: $link-colour;
color: $white;
padding: 10px $gutter-half;
z-index: 10000;
&:link, &:visited {
color: $white;
}
&:hover {
color: $light-blue-25;
}
}
.edit-template-link-letter-contact {
@extend %edit-template-link;
right: -25px;
top: 13.3%; // align to top of contact block
}
.edit-template-link-letter-address {
@extend %edit-template-link;
top: 16.5%; // align bottom edge to bottom of address
left: -5px;
}
.edit-template-link-letter-body {
@extend %edit-template-link;
top: 33.3%; // aligns to top of subject
left: -5px;
}

View File

@@ -1,6 +1,6 @@
.user-list { .user-list {
@include core-16; @include core-19;
margin-bottom: $gutter * 1.5; margin-bottom: $gutter * 1.5;
&-item { &-item {

View File

@@ -22,7 +22,7 @@ from wtforms import (
FieldList, FieldList,
DateField, DateField,
SelectField) SelectField)
from wtforms.fields.html5 import EmailField, TelField from wtforms.fields.html5 import EmailField, TelField, SearchField
from wtforms.validators import (DataRequired, Email, Length, Regexp, Optional) from wtforms.validators import (DataRequired, Email, Length, Regexp, Optional)
from app.main.validators import (Blacklist, CsvFileValidator, ValidGovEmail, NoCommasInPlaceHolders, OnlyGSMCharacters) from app.main.validators import (Blacklist, CsvFileValidator, ValidGovEmail, NoCommasInPlaceHolders, OnlyGSMCharacters)
@@ -288,7 +288,18 @@ class EmailTemplateForm(BaseTemplateForm):
class LetterTemplateForm(EmailTemplateForm): class LetterTemplateForm(EmailTemplateForm):
pass
subject = TextAreaField(
u'Title',
validators=[DataRequired(message="Cant be empty")])
template_content = TextAreaField(
u'Body',
validators=[
DataRequired(message="Cant be empty"),
NoCommasInPlaceHolders()
]
)
class ForgotPasswordForm(Form): class ForgotPasswordForm(Form):
@@ -591,3 +602,8 @@ class ChooseTemplateType(Form):
('sms', 'Text message'), ('sms', 'Text message'),
('letter', 'Letter') if include_letters else None ('letter', 'Letter') if include_letters else None
]) ])
class SearchTemplatesForm(Form):
search = SearchField('Search by name')

View File

@@ -190,19 +190,6 @@ def send_test(service_id, template_id):
) )
@main.route("/services/<service_id>/send/<template_id>/from-api", methods=['GET'])
@login_required
def send_from_api(service_id, template_id):
return render_template(
'views/send-from-api.html',
template=get_template(
service_api_client.get_service_template(service_id, template_id)['data'],
current_service,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template_id)
)
)
def _check_messages(service_id, template_type, upload_id, letters_as_pdf=False): def _check_messages(service_id, template_type, upload_id, letters_as_pdf=False):
if not session.get('upload_data'): if not session.get('upload_data'):

View File

@@ -13,7 +13,13 @@ from notifications_python_client.errors import HTTPError
from app.main import main from app.main import main
from app.utils import user_has_permissions, get_template, png_from_pdf from app.utils import user_has_permissions, get_template, png_from_pdf
from app.main.forms import ChooseTemplateType, SMSTemplateForm, EmailTemplateForm, LetterTemplateForm from app.main.forms import (
ChooseTemplateType,
SMSTemplateForm,
EmailTemplateForm,
LetterTemplateForm,
SearchTemplatesForm,
)
from app.main.views.send import get_example_csv_rows from app.main.views.send import get_example_csv_rows
from app import service_api_client, current_service, template_statistics_client from app import service_api_client, current_service, template_statistics_client
@@ -44,15 +50,8 @@ page_headings = {
def choose_template(service_id): def choose_template(service_id):
return render_template( return render_template(
'views/templates/choose.html', 'views/templates/choose.html',
templates=[ templates=service_api_client.get_service_templates(service_id)['data'],
get_template( search_form=SearchTemplatesForm(),
template,
current_service,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template['id']),
)
for template in service_api_client.get_service_templates(service_id)['data']
if should_show_template(template['template_type'])
],
) )
@@ -73,7 +72,8 @@ def view_template(service_id, template_id):
service_api_client.get_service_template(service_id, template_id)['data'], service_api_client.get_service_template(service_id, template_id)['data'],
current_service, current_service,
expand_emails=True, expand_emails=True,
letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template_id) letter_preview_url=url_for('.view_template', service_id=service_id, template_id=template_id),
show_recipient=True,
) )
) )
@@ -178,6 +178,22 @@ def add_template_by_type(service_id):
) )
if form.validate_on_submit(): if form.validate_on_submit():
if form.template_type.data == 'letter':
blank_letter = service_api_client.create_service_template(
'Untitled',
'letter',
'Content',
service_id,
'Title',
'normal',
)
return redirect(url_for(
'.view_template',
service_id=service_id,
template_id=blank_letter['data']['id'],
))
return redirect(url_for( return redirect(url_for(
'.add_service_template', '.add_service_template',
service_id=service_id, service_id=service_id,
@@ -202,7 +218,7 @@ def add_service_template(service_id, template_type):
if form.process_type.data == 'priority': if form.process_type.data == 'priority':
abort_403_if_not_admin_user() abort_403_if_not_admin_user()
try: try:
service_api_client.create_service_template( new_template = service_api_client.create_service_template(
form.name.data, form.name.data,
template_type, template_type,
form.template_content.data, form.template_content.data,
@@ -221,7 +237,7 @@ def add_service_template(service_id, template_type):
raise e raise e
else: else:
return redirect( return redirect(
url_for('.choose_template', service_id=service_id) url_for('.view_template', service_id=service_id, template_id=new_template['data']['id'])
) )
return render_template( return render_template(

View File

@@ -19,41 +19,43 @@
<a href="{{ url_for('.create_api_key', service_id=current_service.id) }}" class="button align-with-heading">Create an API key</a> <a href="{{ url_for('.create_api_key', service_id=current_service.id) }}" class="button align-with-heading">Create an API key</a>
</div> </div>
</div> </div>
{% call(item, row_number) list_table( <div class="body-copy-table">
keys, {% call(item, row_number) list_table(
empty_message="You havent created any API keys yet", keys,
caption="API keys", empty_message="You havent created any API keys yet",
caption_visible=false, caption="API keys",
field_headings=[ caption_visible=false,
'API keys', field_headings=[
'Action' 'API keys',
], 'Action'
field_headings_visible=False ],
) %} field_headings_visible=False
{% call field() %} ) %}
<div class="file-list"> {% call field() %}
{{ item.name }} <div class="file-list">
<span class="file-list-hint"> {{ item.name }}
{% if item.key_type == 'normal' %} <div class="hint">
Live sends to anyone {% if item.key_type == 'normal' %}
{% elif item.key_type == 'team' %} Live sends to anyone
Team and whitelist limits who you can send to {% elif item.key_type == 'team' %}
{% elif item.key_type == 'test' %} Team and whitelist limits who you can send to
Test pretends to send messages {% elif item.key_type == 'test' %}
{% endif %} Test pretends to send messages
</span> {% endif %}
</div> </div>
</div>
{% endcall %}
{% if item.expiry_date %}
{% call field(align='right') %}
<span class='hint'>Revoked {{ item.expiry_date|format_datetime_short }}</span>
{% endcall %}
{% else %}
{% call field(align='right', status='error') %}
<a href='{{ url_for('.revoke_api_key', service_id=current_service.id, key_id=item.id) }}'>Revoke</a>
{% endcall %}
{% endif %}
{% endcall %} {% endcall %}
{% if item.expiry_date %} </div>
{% call field(align='right') %}
<span class='hint'>Revoked {{ item.expiry_date|format_datetime_short }}</span>
{% endcall %}
{% else %}
{% call field(align='right', status='error') %}
<a href='{{ url_for('.revoke_api_key', service_id=current_service.id, key_id=item.id) }}'>Revoke</a>
{% endcall %}
{% endif %}
{% endcall %}
{{ page_footer( {{ page_footer(
secondary_link=url_for('.api_integration', service_id=current_service.id), secondary_link=url_for('.api_integration', service_id=current_service.id),

View File

@@ -175,7 +175,11 @@
wrapping_class='bottom-gutter-2-3' wrapping_class='bottom-gutter-2-3'
) }} ) }}
{% endif %} {% endif %}
{% if template.template_type != 'letter' or not request.args.from_test %}
<input type="submit" class="button" value="Send {{ count_of_recipients }} {{ message_count_label(count_of_recipients, template.template_type, suffix='') }}" /> <input type="submit" class="button" value="Send {{ count_of_recipients }} {{ message_count_label(count_of_recipients, template.template_type, suffix='') }}" />
{% else %}
<a href="#" class="heading-medium">Download as PDF</a>
{% endif %}
<a href="{{ back_link }}" class="page-footer-back-link">Back</a> <a href="{{ back_link }}" class="page-footer-back-link">Back</a>
</form> </form>
{% endif %} {% endif %}

View File

@@ -1,20 +0,0 @@
{% extends "withnav_template.html" %}
{% from "components/api-key.html" import api_key %}
{% block service_page_title %}
API info
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">
API info
</h1>
{{ template|string }}
<div class="bottom-gutter">
{{ api_key(template.id, name="Template ID", thing='template ID') }}
</div>
{% endblock %}

View File

@@ -13,11 +13,17 @@
{% block maincolumn_content %} {% block maincolumn_content %}
<h1 class="heading-large">
{% if request.args['help'] %} {% if request.args['help'] %}
<h1 class="heading-large">Example text message</h1> Example text message
{% else %} {% else %}
<h1 class="heading-large">Send yourself a test</h1> {% if template.template_type == 'letter' %}
Generate preview
{% else %}
Send yourself a test
{% endif %}
{% endif %} {% endif %}
</h1>
{{ template|string }} {{ template|string }}

View File

@@ -11,7 +11,7 @@
<h1 class="heading-large">Settings</h1> <h1 class="heading-large">Settings</h1>
<div class="bottom-gutter-3-2"> <div class="bottom-gutter-3-2 body-copy-table">
{% call mapping_table( {% call mapping_table(
caption='Settings', caption='Settings',

View File

@@ -1,25 +1,42 @@
<div class="column-two-thirds"> <div class="column-whole">
{{ template|string }}
</div>
<div class="column-one-third">
{% if template._template.archived %} {% if template._template.archived %}
<div class="message-updated-at"> <p class="hint">
This template was deleted<br/>{{ template._template.updated_at|format_date_normal }} This template was deleted {{ template._template.updated_at|format_datetime_relative }}.
</div> </p>
{% else %} {% else %}
<div class="message-use-links{% if show_title %}-with-title{% endif %}"> <div class="bottom-gutter-2-3">
{% if current_user.has_permissions(permissions=['send_texts', 'send_emails', 'send_letters']) %} <div class="grid-row">
<a href="{{ url_for(".send_messages", service_id=current_service.id, template_id=template.id) }}" class="primary"> {% if current_user.has_permissions(permissions=['send_texts', 'send_emails', 'send_letters']) %}
Upload recipients <div class="{{ 'column-half' if template.template_type == 'letter' else 'column-third' }}">
</a> <a href="{{ url_for(".send_messages", service_id=current_service.id, template_id=template.id) }}" class="pill-separate-item">
<a href="{{ url_for(".send_test", service_id=current_service.id, template_id=template.id) }}"> Upload recipients
Send yourself a test </a>
</a> </div>
{% endif %} <div class="{{ 'column-half' if template.template_type == 'letter' else 'column-third' }}">
{% if current_user.has_permissions(permissions=['manage_templates'], admin_override=True) %} <a href="{{ url_for(".send_test", service_id=current_service.id, template_id=template.id) }}" class="pill-separate-item">
<a href="{{ url_for(".edit_service_template", service_id=current_service.id, template_id=template.id) }}">Edit template</a> {{ 'Generate preview' if template.template_type == 'letter' else 'Send yourself a test' }}
{% endif %} </a>
<a href="{{ url_for(".send_from_api", service_id=current_service.id, template_id=template.id) }}">API info</a> </div>
{% endif %}
{% if
current_user.has_permissions(permissions=['manage_templates'], admin_override=True) and
template.template_type != 'letter'
%}
<div class="column-one-third">
<a href="{{ url_for(".edit_service_template", service_id=current_service.id, template_id=template.id) }}" class="pill-separate-item">
Edit template
</a>
</div>
{% endif %}
</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="column-whole template-container">
{% if current_user.has_permissions(permissions=['manage_templates'], admin_override=True) and template.template_type == 'letter' %}
<a href="{{ url_for(".edit_service_template", service_id=current_service.id, template_id=template.id) }}" class="edit-template-link-letter-body">Edit</a>
<a href="{{ url_for(".service_set_letter_contact_block", service_id=current_service.id) }}" class="edit-template-link-letter-contact">Edit</a>
<a href="#" class="edit-template-link-letter-address">Edit</a>
{% endif %}
{{ template|string }}
</div>

View File

@@ -1,18 +1,16 @@
<div class="column-whole"> <div class="column-whole">
<h2 class="message-name">{{ template.name }}</h2> <h2 class="message-name">{{ template.name }}</h2>
<p class="hint">
{% if template.get_raw('version', 1) > 1 %}
Edit made {% if template.get_raw('updated_at', None) %}{{ template.get_raw('updated_at')|format_datetime_normal }}{% endif %}
{% else %}
Created
{% if template.get_raw('created_at', None) %}{{ template.get_raw('created_at')|format_datetime_normal }}{% endif %}
{% endif %}
by {{ template.get_raw('created_by').name }}
</p>
</div> </div>
<div class="column-two-thirds"> <div class="column-whole">
{{ template|string }} {{ template|string }}
</div> </div>
<div class="column-one-third">
<div class="message-use-links">
{% if template.get_raw('version', 1) > 1 %}
{% if template.get_raw('updated_at', None) %}{{ template.get_raw('updated_at')|format_datetime_normal }}{% endif %}
{% else %}
Created<br/>{% if template.get_raw('created_at', None) %}{{ template.get_raw('created_at')|format_datetime_normal }}{% endif %}
{% endif %}
<br/>by {{ template.get_raw('created_by').name }}
</div>
</div>

View File

@@ -1,3 +1,6 @@
{% from "components/message-count-label.html" import message_count_label %}
{% from "components/textbox.html" import textbox %}
{% extends "withnav_template.html" %} {% extends "withnav_template.html" %}
{% block service_page_title %} {% block service_page_title %}
@@ -33,7 +36,7 @@
{% else %} {% else %}
<div class="grid-row"> <div class="grid-row bottom-gutter-1-2">
<div class="column-two-thirds"> <div class="column-two-thirds">
<h1 class="heading-large">Templates</h1> <h1 class="heading-large">Templates</h1>
</div> </div>
@@ -44,19 +47,29 @@
{% endif %} {% endif %}
</div> </div>
<div class="grid-row"> {% if templates|length > 7 %}
<div data-module="autofocus">
<div class="live-search" data-module="live-search" data-targets="#template-list .column-whole">
{{ textbox(
search_form.search,
width='1-1'
) }}
</div>
</div>
{% endif %}
<nav class="grid-row" id=template-list>
{% for template in templates %} {% for template in templates %}
<div class="column-whole"> <div class="column-whole">
<h2 class="message-name">{{ template.name }}</h2> <h2 class="message-name">
{% if template.get_raw('updated_at', None) %} <a href="{{ url_for('.view_template', service_id=current_service.id, template_id=template.id) }}">{{ template.name }}</a>
<p class="message-updated-at"> </h2>
Edited {{ template.get_raw('updated_at', None)|format_date_short }}&ensp;<a href="{{ url_for('.view_template_versions', service_id=current_service.id, template_id=template.id) }}">see previous versions</a> <p class="hint bottom-gutter-1-3">
</p> {{ message_count_label(1, template.template_type, suffix='')|capitalize }} template
{% endif %} </p>
</div> </div>
{% include 'views/templates/_template.html' %}
{% endfor %} {% endfor %}
</div> </nav>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@@ -1,6 +1,7 @@
{% extends "withnav_template.html" %} {% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %} {% from "components/page-footer.html" import page_footer %}
{% from "components/textbox.html" import textbox %} {% from "components/textbox.html" import textbox %}
{% from "components/api-key.html" import api_key %}
{% block service_page_title %} {% block service_page_title %}
{{ template.name }} {{ template.name }}
@@ -17,9 +18,17 @@
{% endwith %} {% endwith %}
</div> </div>
{{ page_footer( <div class="bottom-gutter">
secondary_link=url_for('.choose_template', service_id=current_service.id), {{ api_key(template.id, name="Template ID", thing='template ID') }}
secondary_link_text='All templates' </div>
) }}
{% if template._template.updated_at %}
<div class="bottom-gutter-2">
<h2 class="heading-small">Last edited {{ template._template.updated_at|format_datetime_relative }}</h2>
<p>
<a href="{{ url_for('.view_template_versions', service_id=current_service.id, template_id=template.id) }}">See previous versions</a>
</p>
</div>
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -13,10 +13,10 @@
<a href="{{ url_for('main.choose_service') }}" class="navigation-service-switch">Switch service</a> <a href="{{ url_for('main.choose_service') }}" class="navigation-service-switch">Switch service</a>
</div> </div>
<div class="grid-row"> <div class="grid-row">
<div class="column-one-third"> <div class="column-one-quarter">
{% include "main_nav.html" %} {% include "main_nav.html" %}
</div> </div>
<main role="main" class="column-two-thirds column-main"> <main role="main" class="column-three-quarters column-main">
{% include 'flash_messages.html' %} {% include 'flash_messages.html' %}
{% block maincolumn_content %}{% endblock %} {% block maincolumn_content %}{% endblock %}
</main> </main>

View File

@@ -330,7 +330,7 @@ def png_from_pdf(pdf_endpoint):
output = BytesIO() output = BytesIO()
with Image( with Image(
blob=pdf_endpoint.get_data(), blob=pdf_endpoint.get_data(),
resolution=96, resolution=150,
) as image: ) as image:
with image.convert('png') as converted: with image.convert('png') as converted:
converted.save(file=output) converted.save(file=output)

View File

@@ -64,6 +64,7 @@ gulp.task('javascripts', () => gulp
paths.src + 'javascripts/radioSelect.js', paths.src + 'javascripts/radioSelect.js',
paths.src + 'javascripts/updateContent.js', paths.src + 'javascripts/updateContent.js',
paths.src + 'javascripts/listEntry.js', paths.src + 'javascripts/listEntry.js',
paths.src + 'javascripts/liveSearch.js',
paths.src + 'javascripts/main.js' paths.src + 'javascripts/main.js'
]) ])
.pipe(plugins.prettyerror()) .pipe(plugins.prettyerror())

View File

@@ -316,25 +316,6 @@ def test_send_test_sms_message_with_placeholders(
mock_s3_upload.assert_called_with(fake_uuid, expected_data, 'eu-west-1') mock_s3_upload.assert_called_with(fake_uuid, expected_data, 'eu-west-1')
def test_api_info_page(
logged_in_client,
mocker,
api_user_active,
mock_login,
mock_get_service,
mock_get_service_email_template,
mock_s3_upload,
mock_has_permissions,
fake_uuid
):
response = logged_in_client.get(
url_for('main.send_from_api', service_id=fake_uuid, template_id=fake_uuid),
follow_redirects=True
)
assert response.status_code == 200
assert 'API info' in response.get_data(as_text=True)
def test_download_example_csv( def test_download_example_csv(
logged_in_client, logged_in_client,
mocker, mocker,
@@ -664,132 +645,6 @@ def test_route_invalid_permissions(
service_one) service_one)
def test_route_choose_template_manage_service_permissions(
mocker,
app_,
client,
api_user_active,
service_one,
mock_login,
mock_get_user,
mock_get_service,
mock_check_verify_code,
mock_get_service_templates,
mock_get_jobs,
):
template_id = mock_get_service_templates(service_one['id'])['data'][0]['id']
resp = validate_route_permission(
mocker,
app_,
"GET",
200,
url_for(
'main.choose_template',
service_id=service_one['id'],
),
['manage_users', 'manage_templates', 'manage_settings'],
api_user_active,
service_one)
page = resp.get_data(as_text=True)
assert url_for(
"main.send_messages",
service_id=service_one['id'],
template_id=template_id) not in page
assert url_for(
"main.send_test",
service_id=service_one['id'],
template_id=template_id) not in page
assert url_for(
"main.edit_service_template",
service_id=service_one['id'],
template_id=template_id) in page
def test_route_choose_template_send_messages_permissions(
mocker,
app_,
client,
active_user_with_permissions,
service_one,
mock_get_service,
mock_check_verify_code,
mock_get_service_templates,
mock_get_jobs,
):
template_id = None
for temp in mock_get_service_templates(service_one['id'])['data']:
if temp['template_type'] == 'sms':
template_id = temp['id']
assert template_id
resp = validate_route_permission(
mocker,
app_,
"GET",
200,
url_for(
'main.choose_template',
service_id=service_one['id'],
template_type='sms'),
['send_texts', 'send_emails', 'send_letters'],
active_user_with_permissions,
service_one)
page = resp.get_data(as_text=True)
assert url_for(
"main.send_messages",
service_id=service_one['id'],
template_id=template_id) in page
assert url_for(
"main.edit_service_template",
service_id=service_one['id'],
template_id=template_id) not in page
def test_route_choose_template_manage_api_keys_permissions(
mocker,
app_,
client,
api_user_active,
service_one,
mock_get_user,
mock_get_service,
mock_check_verify_code,
mock_get_service_templates,
mock_get_jobs,
):
template_id = None
for temp in mock_get_service_templates(service_one['id'])['data']:
if temp['template_type'] == 'sms':
template_id = temp['id']
assert template_id
resp = validate_route_permission(
mocker,
app_,
"GET",
200,
url_for(
'main.choose_template',
service_id=service_one['id'],
template_type='sms'),
['manage_api_keys'],
api_user_active,
service_one)
page = resp.get_data(as_text=True)
assert url_for(
"main.send_test",
service_id=service_one['id'],
template_id=template_id) not in page
assert url_for(
"main.edit_service_template",
service_id=service_one['id'],
template_id=template_id) not in page
page = BeautifulSoup(resp.data.decode('utf-8'), 'html.parser')
links = page.findAll('a', href=re.compile('^' + url_for(
"main.send_from_api",
service_id=service_one['id'],
template_id=template_id)))
assert len(links) == 1
@pytest.mark.parametrize( @pytest.mark.parametrize(
'extra_args,expected_url', 'extra_args,expected_url',
[ [

View File

@@ -33,6 +33,75 @@ def test_should_show_page_for_one_template(
mock_get_service_template.assert_called_with(service_one['id'], template_id) mock_get_service_template.assert_called_with(service_one['id'], template_id)
@pytest.mark.parametrize('permissions, links_to_be_shown', [
(
['view_activity'],
[]
),
(
['manage_templates'],
['.edit_service_template']
),
(
['send_texts', 'send_emails', 'send_letters'],
['.send_messages', '.send_test']
),
(
['send_texts', 'send_emails', 'send_letters', 'manage_templates'],
['.send_messages', '.send_test', '.edit_service_template']
),
])
def test_should_be_able_to_view_a_template_with_links(
client,
mock_get_service_template,
active_user_with_permissions,
mocker,
service_one,
fake_uuid,
permissions,
links_to_be_shown,
):
active_user_with_permissions._permissions[service_one['id']] = permissions
client.login(active_user_with_permissions, mocker, service_one)
response = client.get(url_for(
'.view_template',
service_id=service_one['id'],
template_id=fake_uuid
))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
links_in_page = page.select('.pill-separate-item')
assert len(links_in_page) == len(links_to_be_shown)
for index, link_to_be_shown in enumerate(links_to_be_shown):
assert links_in_page[index]['href'] == url_for(
link_to_be_shown,
service_id=service_one['id'],
template_id=fake_uuid,
)
def test_should_show_template_id_on_template_page(
logged_in_client,
mock_get_service_template,
service_one,
fake_uuid,
):
response = logged_in_client.get(url_for(
'.view_template',
service_id=service_one['id'],
template_id=fake_uuid))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.select('.api-key-key')[0].text == fake_uuid
def test_should_show_sms_template_with_downgraded_unicode_characters( def test_should_show_sms_template_with_downgraded_unicode_characters(
logged_in_client, logged_in_client,
mocker, mocker,
@@ -513,10 +582,10 @@ def test_should_show_page_for_a_deleted_template(
assert response.status_code == 200 assert response.status_code == 200
content = response.get_data(as_text=True) content = response.get_data(as_text=True)
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert url_for("main.edit_service_template", service_id=fake_uuid, template_id=fake_uuid) not in content assert url_for("main.edit_service_template", service_id=fake_uuid, template_id=fake_uuid) not in content
assert url_for("main.send_from_api", service_id=fake_uuid, template_id=fake_uuid) not in content
assert url_for("main.send_test", service_id=fake_uuid, template_id=fake_uuid) not in content assert url_for("main.send_test", service_id=fake_uuid, template_id=fake_uuid) not in content
assert "This template was deleted<br/>1 January 2016" in content assert page.select('p.hint')[0].text.strip() == 'This template was deleted today at 3:00pm.'
mock_get_deleted_template.assert_called_with(service_id, template_id) mock_get_deleted_template.assert_called_with(service_id, template_id)