Merge branch 'master' into pyup-update-whitenoise-4.1.1-to-4.1.2

This commit is contained in:
Chris Hill-Scott
2018-11-27 10:09:31 +00:00
committed by GitHub
31 changed files with 601 additions and 214 deletions
+11 -10
View File
@@ -35,6 +35,7 @@ from notifications_utils.recipients import (
) )
from notifications_utils.formatters import formatted_list from notifications_utils.formatters import formatted_list
from notifications_utils.sanitise_text import SanitiseASCII from notifications_utils.sanitise_text import SanitiseASCII
from notifications_utils.timezones import utc_string_to_aware_gmt_datetime
from werkzeug.exceptions import abort, HTTPException as WerkzeugHTTPException from werkzeug.exceptions import abort, HTTPException as WerkzeugHTTPException
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
@@ -69,7 +70,7 @@ from app.notify_client.complaint_api_client import complaint_api_client
from app.notify_client.platform_stats_api_client import platform_stats_api_client from app.notify_client.platform_stats_api_client import platform_stats_api_client
from app.notify_client.template_folder_api_client import template_folder_api_client from app.notify_client.template_folder_api_client import template_folder_api_client
from app.commands import setup_commands from app.commands import setup_commands
from app.utils import get_cdn_domain, gmt_timezones, id_safe from app.utils import get_cdn_domain, id_safe
login_manager = LoginManager() login_manager = LoginManager()
csrf = CSRFProtect() csrf = CSRFProtect()
@@ -256,17 +257,17 @@ def format_datetime_numeric(date):
def format_date_numeric(date): def format_date_numeric(date):
return gmt_timezones(date).strftime('%Y-%m-%d') return utc_string_to_aware_gmt_datetime(date).strftime('%Y-%m-%d')
def format_time_24h(date): def format_time_24h(date):
return gmt_timezones(date).strftime('%H:%M') return utc_string_to_aware_gmt_datetime(date).strftime('%H:%M')
def get_human_day(time): def get_human_day(time):
# Add 1 minute to transform 00:00 into midnight today instead of midnight tomorrow # Add 1 minute to transform 00:00 into midnight today instead of midnight tomorrow
date = (gmt_timezones(time) - timedelta(minutes=1)).date() date = (utc_string_to_aware_gmt_datetime(time) - timedelta(minutes=1)).date()
if date == (datetime.utcnow() + timedelta(days=1)).date(): if date == (datetime.utcnow() + timedelta(days=1)).date():
return 'tomorrow' return 'tomorrow'
if date == datetime.utcnow().date(): if date == datetime.utcnow().date():
@@ -281,21 +282,21 @@ def format_time(date):
'12:00AM': 'Midnight', '12:00AM': 'Midnight',
'12:00PM': 'Midday' '12:00PM': 'Midday'
}.get( }.get(
gmt_timezones(date).strftime('%-I:%M%p'), utc_string_to_aware_gmt_datetime(date).strftime('%-I:%M%p'),
gmt_timezones(date).strftime('%-I:%M%p') utc_string_to_aware_gmt_datetime(date).strftime('%-I:%M%p')
).lower() ).lower()
def format_date(date): def format_date(date):
return gmt_timezones(date).strftime('%A %d %B %Y') return utc_string_to_aware_gmt_datetime(date).strftime('%A %d %B %Y')
def format_date_normal(date): def format_date_normal(date):
return gmt_timezones(date).strftime('%d %B %Y').lstrip('0') return utc_string_to_aware_gmt_datetime(date).strftime('%d %B %Y').lstrip('0')
def format_date_short(date): def format_date_short(date):
return _format_datetime_short(gmt_timezones(date)) return _format_datetime_short(utc_string_to_aware_gmt_datetime(date))
def _format_datetime_short(datetime): def _format_datetime_short(datetime):
@@ -306,7 +307,7 @@ def format_delta(date):
delta = ( delta = (
datetime.now(timezone.utc) datetime.now(timezone.utc)
) - ( ) - (
gmt_timezones(date) utc_string_to_aware_gmt_datetime(date)
) )
if delta < timedelta(seconds=30): if delta < timedelta(seconds=30):
return "just now" return "just now"
+6 -1
View File
@@ -9,7 +9,12 @@
$targets.each(function() { $targets.each(function() {
let content = $(this).text(); let content = $('.live-search-relevant', this).text() || $(this).text();
if (query == '') {
$(this).css('display', '');
return;
}
$(this).toggle( $(this).toggle(
normalize(content).indexOf(normalize(query)) > -1 normalize(content).indexOf(normalize(query)) > -1
+45 -2
View File
@@ -5,9 +5,31 @@
margin: 0; margin: 0;
a { a {
display: block; display: inline;
margin-bottom: -$gutter; margin-bottom: -$gutter;
padding-bottom: $gutter; padding-bottom: $gutter;
&:focus {
// Use box shadow instead of outline to avoid buggy outline
// rendering in Firefox
outline: none;
box-shadow: 0 0 0 3px $yellow;
@include ie-lte(8) {
// Box shadow is not supported in IE8
outline: 3px solid $yellow;
}
}
}
&-separator {
display: inline-block;
vertical-align: top;
color: $secondary-text-colour;
padding: 0 4px 0 5px;
font-weight: normal;
} }
} }
@@ -40,11 +62,32 @@
} }
&-hidden-by-default {
display: none;
}
&-without-ancestors {
.message-name {
a {
display: block;
&:first-child {
display: block;
}
}
}
}
} }
&-folder { &-folder {
a { a:first-child {
display: inline-block;
text-indent: 40px; text-indent: 40px;
background-image: file-url('folder-blue-bold.svg'); background-image: file-url('folder-blue-bold.svg');
background-repeat: no-repeat; background-repeat: no-repeat;
@@ -48,6 +48,11 @@
.page-footer { .page-footer {
margin-bottom: 0; margin-bottom: 0;
min-height: 50px;
}
.notification-status {
margin: 0;
} }
} }
+13
View File
@@ -11,6 +11,19 @@ $item-top-padding: $gutter-half;
border-top: 1px solid $border-colour; border-top: 1px solid $border-colour;
position: relative; position: relative;
h3 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: $secondary-text-colour; // So the ellipsis is grey
.heading-small {
color: $black;
}
}
&:last-child { &:last-child {
border-bottom: 1px solid $border-colour; border-bottom: 1px solid $border-colour;
} }
+1 -1
View File
@@ -124,7 +124,7 @@ class Staging(Config):
HTTP_PROTOCOL = 'https' HTTP_PROTOCOL = 'https'
HEADER_COLOUR = '#6F72AF' # $mauve HEADER_COLOUR = '#6F72AF' # $mauve
STATSD_ENABLED = True STATSD_ENABLED = True
CSV_UPLOAD_BUCKET_NAME = 'staging-notify-csv-upload' CSV_UPLOAD_BUCKET_NAME = 'staging-notifications-csv-upload'
LOGO_UPLOAD_BUCKET_NAME = 'public-logos-staging' LOGO_UPLOAD_BUCKET_NAME = 'public-logos-staging'
MOU_BUCKET_NAME = 'staging-notify.works-mou' MOU_BUCKET_NAME = 'staging-notify.works-mou'
NOTIFY_ENVIRONMENT = 'staging' NOTIFY_ENVIRONMENT = 'staging'
+10 -9
View File
@@ -2374,7 +2374,7 @@ internalauditscotland.gov.uk:
inverclyde.gov.uk: inverclyde.gov.uk:
owner: Inverclyde Council owner: Inverclyde Council
crown: false crown: false
agreement_signed: false agreement_signed: true
iow.gov.uk: iow.gov.uk:
owner: Isle of Wight Council owner: Isle of Wight Council
crown: false crown: false
@@ -2629,7 +2629,7 @@ lincoln.gov.uk:
lincolnshire.gov.uk: lincolnshire.gov.uk:
owner: Lincolnshire County Council owner: Lincolnshire County Council
crown: false crown: false
agreement_signed: false agreement_signed: true
lincsbc.gov.uk: lincsbc.gov.uk:
owner: East Lindsey District Council owner: East Lindsey District Council
crown: false crown: false
@@ -3246,7 +3246,7 @@ northtyneside.gov.uk:
northumberland.gov.uk: northumberland.gov.uk:
owner: Northumberland County Council owner: Northumberland County Council
crown: false crown: false
agreement_signed: false agreement_signed: true
northwalesadoption.gov.uk: northwalesadoption.gov.uk:
owner: Wrexham County Borough Council owner: Wrexham County Borough Council
crown: false crown: false
@@ -3494,11 +3494,8 @@ pkc.gov.uk:
plymouth.gov.uk: plymouth.gov.uk:
owner: Plymouth City Council owner: Plymouth City Council
crown: false crown: false
agreement_signed: false agreement_signed: true
plymouthmuseum.gov.uk: plymouthmuseum.gov.uk: plymouth.gov.uk
owner: Plymouth City Council
crown: false
agreement_signed: false
pocklington.gov.uk: pocklington.gov.uk:
owner: Pocklington Town Council owner: Pocklington Town Council
crown: false crown: false
@@ -4659,7 +4656,7 @@ towcester-tc.gov.uk:
towerhamlets.gov.uk: towerhamlets.gov.uk:
owner: Tower Hamlets London Borough Council owner: Tower Hamlets London Borough Council
crown: false crown: false
agreement_signed: false agreement_signed: true
towynkinmelbay-tc.gov.uk: towynkinmelbay-tc.gov.uk:
owner: Towyn and Kinmel Bay Town Council owner: Towyn and Kinmel Bay Town Council
crown: false crown: false
@@ -5070,6 +5067,10 @@ wixford-pc.gov.uk:
owner: Stratford-on-Avon District Council owner: Stratford-on-Avon District Council
crown: false crown: false
agreement_signed: false agreement_signed: false
wmfs.net:
owner: West Midlands Fire Service
crown: false
agreement_signed: true
woking.gov.uk: woking.gov.uk:
owner: Woking Borough Council owner: Woking Borough Council
crown: false crown: false
+1
View File
@@ -40,3 +40,4 @@
- hscni.net - hscni.net
- bi.team - bi.team
- networkrail.co.uk - networkrail.co.uk
- uksbs.co.uk
+7 -15
View File
@@ -1143,8 +1143,8 @@ class TemplateAndFoldersSelectionForm(Form):
def __init__( def __init__(
self, self,
service, all_template_folders,
template_type, template_list,
current_folder_id, current_folder_id,
*args, *args,
**kwargs **kwargs
@@ -1152,20 +1152,12 @@ class TemplateAndFoldersSelectionForm(Form):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.templates_and_folders.choices = self.ids_and_names( self.templates_and_folders.choices = template_list.as_id_and_name
service.get_template_folders_and_templates(template_type, current_folder_id)
)
self.move_to.choices = self.ids_and_names( self.move_to.choices = [
[self.ALL_TEMPLATES_FOLDER] + service.all_template_folders, (item['id'], item['name'])
exclude=current_folder_id, for item in ([self.ALL_TEMPLATES_FOLDER] + all_template_folders)
) if item['id'] != str(current_folder_id)
@staticmethod
def ids_and_names(items, exclude=None):
return [
(item['id'], item['name']) for item in items
if item['id'] != str(exclude)
] ]
templates_and_folders = MultiCheckboxField('Choose templates or folders') templates_and_folders = MultiCheckboxField('Choose templates or folders')
+1
View File
@@ -65,6 +65,7 @@ def view_notification(service_id, notification_id):
notification_id=notification_id, notification_id=notification_id,
filetype='png', filetype='png',
), ),
expand_emails=True,
page_count=page_count, page_count=page_count,
show_recipient=True, show_recipient=True,
redact_missing_personalisation=True, redact_missing_personalisation=True,
+5 -1
View File
@@ -552,8 +552,12 @@ def service_set_letters(service_id):
@main.route("/services/<service_id>/service-settings/set-postage", methods=['GET', 'POST']) @main.route("/services/<service_id>/service-settings/set-postage", methods=['GET', 'POST'])
@login_required @login_required
@user_is_platform_admin @user_has_permissions('manage_service')
def service_set_postage(service_id): def service_set_postage(service_id):
if not current_service.has_permission('letter'):
abort(404)
form = ServicePostageForm(postage=current_service.postage) form = ServicePostageForm(postage=current_service.postage)
if form.validate_on_submit(): if form.validate_on_submit():
+31 -14
View File
@@ -29,6 +29,7 @@ from app.main.forms import (
) )
from app.main.views.send import get_example_csv_rows, get_sender_details from app.main.views.send import get_example_csv_rows, get_sender_details
from app.models.service import Service from app.models.service import Service
from app.models.template_list import TemplateList
from app.template_previews import TemplatePreview, get_page_count_for_letter from app.template_previews import TemplatePreview, get_page_count_for_letter
from app.utils import ( from app.utils import (
email_or_sms_not_enabled, email_or_sms_not_enabled,
@@ -108,8 +109,11 @@ def start_tour(service_id, template_id):
@user_has_permissions() @user_has_permissions()
def choose_template(service_id, template_type='all', template_folder_id=None): def choose_template(service_id, template_type='all', template_folder_id=None):
template_list = TemplateList(current_service, template_type, template_folder_id)
templates_and_folders_form = TemplateAndFoldersSelectionForm( templates_and_folders_form = TemplateAndFoldersSelectionForm(
service=current_service, all_template_folders=current_service.all_template_folders,
template_list=template_list,
template_type=template_type, template_type=template_type,
current_folder_id=template_folder_id, current_folder_id=template_folder_id,
) )
@@ -126,9 +130,7 @@ def choose_template(service_id, template_type='all', template_folder_id=None):
current_template_folder_id=template_folder_id, current_template_folder_id=template_folder_id,
can_manage_folders=can_manage_folders(), can_manage_folders=can_manage_folders(),
template_folder_path=current_service.get_template_folder_path(template_folder_id), template_folder_path=current_service.get_template_folder_path(template_folder_id),
template_folder_has_contents=current_service.get_template_folders_and_templates('all', template_folder_id), template_list=template_list,
template_folders=current_service.get_template_folders(template_type, template_folder_id),
templates=current_service.get_templates(template_type, template_folder_id),
show_search_box=current_service.count_of_templates_and_folders > 7, show_search_box=current_service.count_of_templates_and_folders > 7,
show_template_nav=( show_template_nav=(
current_service.has_multiple_template_types current_service.has_multiple_template_types
@@ -318,7 +320,7 @@ def copy_template(service_id, template_id):
return add_service_template(service_id, template['template_type']) return add_service_template(service_id, template['template_type'])
template['template_content'] = template['content'] template['template_content'] = template['content']
template['name'] = 'Copy of {}'.format(template['name']) template['name'] = _get_template_copy_name(template, current_service.all_templates)
form = form_objects[template['template_type']](**template) form = form_objects[template['template_type']](**template)
return render_template( return render_template(
@@ -330,6 +332,20 @@ def copy_template(service_id, template_id):
) )
def _get_template_copy_name(template, existing_templates):
template_names = [existing['name'] for existing in existing_templates]
for index in reversed(range(1, 10)):
if '{} (copy {})'.format(template['name'], index) in template_names:
return '{} (copy {})'.format(template['name'], index + 1)
if '{} (copy)'.format(template['name']) in template_names:
return '{} (copy 2)'.format(template['name'])
return '{} (copy)'.format(template['name'])
@main.route("/services/<service_id>/templates/action-blocked/<notification_type>/<return_to>/<template_id>") @main.route("/services/<service_id>/templates/action-blocked/<notification_type>/<return_to>/<template_id>")
@login_required @login_required
@user_has_permissions('manage_templates') @user_has_permissions('manage_templates')
@@ -405,16 +421,18 @@ def manage_template_folder(service_id, template_folder_id):
@login_required @login_required
@user_has_permissions('manage_templates') @user_has_permissions('manage_templates')
def delete_template_folder(service_id, template_folder_id): def delete_template_folder(service_id, template_folder_id):
if not current_service.has_permission('edit_folders'): if not current_service.has_permission('edit_folders'):
abort(403) abort(403)
form = TemplateFolderForm()
template_folder_path = current_service.get_template_folder_path(template_folder_id) template_folder = current_service.get_template_folder(template_folder_id)
template_folder_name = template_folder_path[-1]["name"]
form = TemplateFolderForm(name=template_folder['name'])
if len(current_service.get_template_folders_and_templates( if len(current_service.get_template_folders_and_templates(
template_type="all", template_folder_id=template_folder_id template_type="all", template_folder_id=template_folder_id
)) > 0: )) > 0:
flash("You must empty this folder before you can delete it".format(template_folder_name), 'info') flash("You must empty this folder before you can delete it".format(template_folder['name']), 'info')
return redirect( return redirect(
url_for( url_for(
'.choose_template', service_id=service_id, template_type="all", template_folder_id=template_folder_id '.choose_template', service_id=service_id, template_type="all", template_folder_id=template_folder_id
@@ -426,12 +444,12 @@ def delete_template_folder(service_id, template_folder_id):
template_folder_api_client.delete_template_folder(current_service.id, template_folder_id) template_folder_api_client.delete_template_folder(current_service.id, template_folder_id)
return redirect( return redirect(
url_for('.choose_template', service_id=service_id) url_for('.choose_template', service_id=service_id, template_folder_id=template_folder['parent_id'])
) )
except HTTPError as e: except HTTPError as e:
msg = "Folder is not empty" msg = "Folder is not empty"
if e.status_code == 400 and msg in e.message: if e.status_code == 400 and msg in e.message:
flash("You must empty this folder before you can delete it".format(template_folder_name), 'info') flash("You must empty this folder before you can delete it", 'info')
return redirect( return redirect(
url_for( url_for(
'.choose_template', '.choose_template',
@@ -443,15 +461,14 @@ def delete_template_folder(service_id, template_folder_id):
else: else:
abort(500, e) abort(500, e)
flash("Are you sure you want to delete the {} folder?".format(template_folder_name), 'delete') flash("Are you sure you want to delete the {} folder?".format(template_folder['name']), 'delete')
return render_template( return render_template(
'views/templates/manage-template-folder.html', 'views/templates/manage-template-folder.html',
form=form, form=form,
template_folder_path=template_folder_path, template_folder_path=current_service.get_template_folder_path(template_folder_id),
current_service_id=current_service.id, current_service_id=current_service.id,
template_folder_id=template_folder_id, template_folder_id=template_folder_id,
template_type="all", template_type="all",
delete_folder=True
) )
+1 -1
View File
@@ -369,7 +369,7 @@ class Service():
@property @property
def count_of_templates_and_folders(self): def count_of_templates_and_folders(self):
return len(self.get_template_folders_and_templates('all', None)) return len(self.all_templates + self.all_template_folders)
def move_to_folder(self, ids_to_move, move_to): def move_to_folder(self, ids_to_move, move_to):
+124
View File
@@ -0,0 +1,124 @@
class TemplateList():
def __init__(
self,
service,
template_type='all',
template_folder_id=None,
):
self.service = service
self.template_type = template_type
self.template_folder_id = template_folder_id
def __iter__(self):
for item in self.get_templates_and_folders(
self.template_type, self.template_folder_id, ancestors=[]
):
yield item
def get_templates_and_folders(self, template_type, template_folder_id, ancestors):
for item in self.service.get_template_folders(
template_type, template_folder_id
):
yield TemplateListFolder(
item,
folders=self.service.get_template_folders(
self.template_type, item['id']
),
templates=self.service.get_templates(
self.template_type, item['id']
),
ancestors=ancestors,
)
for sub_item in self.get_templates_and_folders(
template_type, item['id'], ancestors + [item]
):
yield sub_item
for item in self.service.get_templates(
self.template_type, template_folder_id
):
yield TemplateListTemplate(
item,
ancestors=ancestors,
)
@property
def as_id_and_name(self):
return [(item.id, item.name) for item in self]
@property
def templates_to_show(self):
return any(self)
@property
def folder_is_empty(self):
return not any(self.get_templates_and_folders(
'all', self.template_folder_id, []
))
class TemplateListItem():
def __init__(
self,
template_or_folder,
ancestors,
):
self.id = template_or_folder['id']
self.name = template_or_folder['name']
self.ancestors = ancestors
class TemplateListTemplate(TemplateListItem):
is_folder = False
def __init__(
self,
template,
ancestors,
):
super().__init__(template, ancestors)
self.hint = {
'email': 'Email template',
'sms': 'Text message template',
'letter': 'Letter template',
}.get(template['template_type'])
class TemplateListFolder(TemplateListItem):
is_folder = True
def __init__(
self,
folder,
templates,
folders,
ancestors,
):
super().__init__(folder, ancestors)
self.number_of_templates = len(templates)
self.number_of_folders = len(folders)
@property
def _hint_parts(self):
if self.number_of_folders == self.number_of_templates == 0:
yield 'Empty'
if self.number_of_templates == 1:
yield '1 template'
elif self.number_of_templates > 1:
yield '{} templates'.format(self.number_of_templates)
if self.number_of_folders == 1:
yield '1 folder'
elif self.number_of_folders > 1:
yield '{} folders'.format(self.number_of_folders)
@property
def hint(self):
return ', '.join(self._hint_parts)
@@ -55,26 +55,3 @@
{%- endif -%} {%- endif -%}
{%- endif %} {%- endif %}
{%- endmacro %} {%- endmacro %}
{% macro folder_contents_count(number_of_folders, number_of_templates) %}
{% if number_of_folders == number_of_templates == 0 %}
Empty
{% endif %}
{% if number_of_templates == 1 %}
{{ number_of_templates }} template
{%- elif number_of_templates > 1 -%}
{{ number_of_templates }} templates
{%- endif -%}
{%- if number_of_folders and number_of_templates %}, {% endif -%}
{%- if number_of_folders == 1 -%}
{{ number_of_folders }} folder
{%- elif number_of_folders > 1 -%}
{{ number_of_folders }} folders
{% endif %}
{%- endmacro %}
+1 -1
View File
@@ -39,7 +39,7 @@
{% if template.template_type != 'letter' or not request.args.from_test %} {% if template.template_type != 'letter' or not request.args.from_test %}
<button type="submit" class="button">Send {{ count_of_recipients }} {{ message_count_label(count_of_recipients, template.template_type, suffix='') }}</button> <button type="submit" class="button">Send {{ count_of_recipients }} {{ message_count_label(count_of_recipients, template.template_type, suffix='') }}</button>
{% else %} {% else %}
<a href="{{ url_for('main.check_messages_preview', service_id=current_service.id, template_id=template.id, upload_id=upload_id, filetype='pdf') }}" download class="button">Download as a printable PDF</a> <a href="{{ url_for('main.check_messages_preview', service_id=current_service.id, template_id=template.id, upload_id=upload_id, filetype='pdf') }}" download class="button">Download as a PDF</a>
{% endif %} {% 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>
+1 -1
View File
@@ -33,7 +33,7 @@
<div class="user-list"> <div class="user-list">
{% for user in users %} {% for user in users %}
<div class="user-list-item"> <div class="user-list-item">
<h3> <h3 title="{{ user.email_address }}">
{%- if user.name -%} {%- if user.name -%}
<span class="heading-small">{{ user.name }}</span>&ensp; <span class="heading-small">{{ user.name }}</span>&ensp;
{%- endif -%} {%- endif -%}
+1 -1
View File
@@ -65,7 +65,7 @@
{% endif %} {% endif %}
<a href="{{ back_link }}" class="page-footer-back-link">Back</a> <a href="{{ back_link }}" class="page-footer-back-link">Back</a>
{% if template.template_type == 'letter' %} {% if template.template_type == 'letter' %}
<a href="{{ url_for('main.check_notification_preview', service_id=current_service.id, template_id=template.id, filetype='pdf') }}" download class="page-footer-right-aligned-link">Download as a printable PDF</a> <a href="{{ url_for('main.check_notification_preview', service_id=current_service.id, template_id=template.id, filetype='pdf') }}" download class="page-footer-right-aligned-link">Download as a PDF</a>
{% endif %} {% endif %}
</form> </form>
</div> </div>
@@ -51,15 +51,23 @@
<p> <p>
Estimated delivery date: {{ estimated_letter_delivery_date|string|format_date_short }} Estimated delivery date: {{ estimated_letter_delivery_date|string|format_date_short }}
</p> </p>
<p class="bottom-gutter">
<a href="{{ url_for('main.view_letter_notification_as_preview', service_id=current_service.id, notification_id=notification_id, filetype='pdf') }}" download>Download as a PDF</a>
</p>
{% endif %} {% endif %}
{% endif %} {% endif %}
{{ template|string }} {{ template|string }}
{% if template.template_type != 'letter' %} {% if template.template_type == 'letter' %}
<div class="js-stick-at-bottom-when-scrolling">
<div class="page-footer">
<div>&nbsp;</div>
<a class="page-footer-right-aligned-link" href="{{ url_for('main.view_letter_notification_as_preview', service_id=current_service.id, notification_id=notification_id, filetype='pdf') }}" download>Download as a PDF</a>
</div>
</div>
{% elif template.template_type == 'email' %}
<div class="js-stick-at-bottom-when-scrolling">
{{ ajax_block(partials, updates_url, 'status', finished=finished) }}
</div>
{% elif template.template_type == 'sms' %}
{{ ajax_block(partials, updates_url, 'status', finished=finished) }} {{ ajax_block(partials, updates_url, 'status', finished=finished) }}
{% endif %} {% endif %}
+12 -14
View File
@@ -234,20 +234,18 @@
)}} )}}
{% endcall %} {% endcall %}
{% if current_user.platform_admin %} {% call settings_row(if_has_permission='letter') %}
{% call settings_row(if_has_permission='letter') %} {{ text_field('Postage') }}
{{ text_field('Postage') }} {% set postage = {'first': 'First class only', 'second': 'Second class only'} %}
{% set postage = {'first': 'First class only', 'second': 'Second class only'} %} {{ text_field(postage[current_service.postage]) }}
{{ text_field(postage[current_service.postage]) }} {{ edit_field(
{{ edit_field( 'Change',
'Change', url_for('.service_set_postage',
url_for('.service_set_postage', service_id=current_service.id),
service_id=current_service.id), permissions=['manage_service']
permissions=['manage_service'] )
) }}
}} {% endcall %}
{% endcall %}
{% endif %}
{% endcall %} {% endcall %}
</div> </div>
+1 -1
View File
@@ -1,7 +1,7 @@
{% from "components/radios.html" import radios %} {% from "components/radios.html" import radios %}
{% from "components/page-footer.html" import page_footer %} {% from "components/page-footer.html" import page_footer %}
{% if templates_and_folders_form.move_to.choices and (templates or template_folders) %} {% if templates_and_folders_form.move_to.choices and template_list.templates_to_show %}
{{ radios(templates_and_folders_form.move_to) }} {{ radios(templates_and_folders_form.move_to) }}
{{ page_footer('Move selected', button_name='operation', button_value='move') }} {{ page_footer('Move selected', button_name='operation', button_value='move') }}
{% endif %} {% endif %}
@@ -1,52 +1,43 @@
{% from "components/checkbox.html" import unlabelled_checkbox %} {% from "components/checkbox.html" import unlabelled_checkbox %}
{% from "components/message-count-label.html" import folder_contents_count, message_count_label %} {% from "components/message-count-label.html" import folder_contents_count, message_count_label %}
{% if service_has_templates_or_folders and not templates and not template_folders %} {% if not template_list.templates_to_show %}
<p class="template-list-empty"> <p class="template-list-empty">
{% if template_folder_has_contents %} {% if template_list.folder_is_empty %}
There are no {{ message_count_label(1, template_type, suffix='') }} templates in this folder
{% else %}
This folder is empty This folder is empty
{% else %}
There are no {{ message_count_label(1, template_type, suffix='') }} templates in this folder
{% endif %} {% endif %}
</p> </p>
{% else %} {% else %}
<nav id=template-list> <nav id=template-list>
{% for template_folder in template_folders %} {% for item in template_list %}
<div class="template-list-item {% if can_manage_folders %}template-list-item-with-checkbox{% endif %}"> <div class="template-list-item {% if can_manage_folders %}template-list-item-with-checkbox{% endif %} {% if item.ancestors %}template-list-item-hidden-by-default{% endif %} {% if not item.ancestors %}template-list-item-without-ancestors{% endif %}">
{% if can_manage_folders %} {% if can_manage_folders %}
{{ unlabelled_checkbox( {{ unlabelled_checkbox(
id='templates-or-folder-{}'.format(template_folder.id), id='templates-or-folder-{}'.format(item.id),
name='templates_and_folders', name='templates_and_folders',
value=template_folder.id, value=item.id,
) }} ) }}
{% endif %} {% endif %}
<h2 class="message-name template-list-folder"> <h2 class="message-name {% if item.is_folder or (item.ancestors and not item.is_folder) %}template-list-folder{% endif %}">
<a href="{{ url_for('.choose_template', service_id=current_service.id, template_type=template_type, template_folder_id=template_folder.id) }}"> {% for ancestor in item.ancestors %}
{{ template_folder.name }} <a href="{{ url_for('.choose_template', service_id=current_service.id, template_type=template_type, template_folder_id=ancestor.id) }}">
</a> {{ ancestor.name }}
</a> <span class="message-name-separator">/</span>
{% endfor %}
{% if item.is_folder %}
<a href="{{ url_for('.choose_template', service_id=current_service.id, template_type=template_type, template_folder_id=item.id) }}">
<span class="live-search-relevant">{{ item.name }}</span>
</a>
{% else %}
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.id) }}">
<span class="live-search-relevant">{{ item.name }}</span>
</a>
{% endif %}
</h2> </h2>
<p class="message-type"> <p class="message-type">
{{ folder_contents_count( {{ item.hint }}
current_service.get_template_folders(template_type, template_folder.id)|length,
current_service.get_templates(template_type, template_folder.id)|length,
) }}
</p>
</div>
{% endfor %}
{% for template in templates %}
<div class="template-list-item {% if can_manage_folders %}template-list-item-with-checkbox{% endif %}">
{% if can_manage_folders %}
{{ unlabelled_checkbox(
id='templates-or-folder-{}'.format(template.id),
name='templates_and_folders',
value=template.id,
) }}
{% endif %}
<h2 class="message-name">
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=template.id) }}">{{ template.name }}</a>
</h2>
<p class="message-type">
{{ message_count_label(1, template.template_type, suffix='')|capitalize }} template
</p> </p>
</div> </div>
{% endfor %} {% endfor %}
+3 -21
View File
@@ -77,29 +77,11 @@
{% if can_manage_folders %} {% if can_manage_folders %}
<form method="post"> <form method="post">
{% with {% include 'views/templates/_template_list.html' %}
templates=templates, {% include 'views/templates/_move_to.html' %}
template_folders=template_folders,
service_has_templates_or_folders=(current_service.has_templates or current_service.has_folders),
template_type=template_type,
template_folder_has_contents=template_folder_has_contents
%}
{% include 'views/templates/_template_list.html' %}
{% endwith %}
{% with templates=templates, template_folders=template_folders, templates_and_folders_form=templates_and_folders_form %}
{% include 'views/templates/_move_to.html' %}
{% endwith %}
</form> </form>
{% else %} {% else %}
{% with {% include 'views/templates/_template_list.html' %}
templates=templates,
template_folders=template_folders,
service_has_templates_or_folders=(current_service.has_templates or current_service.has_folders),
template_type=template_type,
template_folder_has_contents=template_folder_has_contents
%}
{% include 'views/templates/_template_list.html' %}
{% endwith %}
{% endif %} {% endif %}
{% endif %} {% endif %}
@@ -21,20 +21,16 @@
</div> </div>
</div> </div>
{% if not delete_folder %} {% call form_wrapper(action=url_for('main.manage_template_folder', service_id=current_service.id, template_folder_id=template_folder_id)) %}
{% call form_wrapper() %} {{ textbox(form.name) }}
{{ textbox(form.name) }} {{ page_footer(
{{ page_footer( 'Save',
'Save', delete_link=url_for(
delete_link=url_for( '.delete_template_folder',
'.delete_template_folder', service_id=current_service_id,
service_id=current_service_id, template_folder_id=template_folder_id
template_folder_id=template_folder_id ),
), delete_link_text="Delete this folder") }}
delete_link_text="Delete this folder") }} {% endcall %}
{% endcall %}
{% else %}
<a href="{{url_for('.manage_template_folder', service_id=current_service.id, template_folder_id=template_folder_id)}}">Back to manage folder page</a>
{% endif %}
{% endblock %} {% endblock %}
-7
View File
@@ -12,7 +12,6 @@ from urllib.parse import urlparse
import ago import ago
import dateutil import dateutil
import pyexcel import pyexcel
import pytz
import yaml import yaml
from flask import ( from flask import (
Markup, Markup,
@@ -355,12 +354,6 @@ def email_or_sms_not_enabled(template_type, permissions):
return (template_type in ['email', 'sms']) and (template_type not in permissions) return (template_type in ['email', 'sms']) and (template_type not in permissions)
def gmt_timezones(date):
date = dateutil.parser.parse(date)
forced_utc = date.replace(tzinfo=pytz.utc)
return forced_utc.astimezone(pytz.timezone('Europe/London'))
def get_cdn_domain(): def get_cdn_domain():
parsed_uri = urlparse(current_app.config['ADMIN_BASE_URL']) parsed_uri = urlparse(current_app.config['ADMIN_BASE_URL'])
+1 -1
View File
@@ -24,4 +24,4 @@ awscli-cwlogs>=1.4,<1.5
# Putting upgrade on hold due to v1.0.0 using sha512 instead of sha1 by default # Putting upgrade on hold due to v1.0.0 using sha512 instead of sha1 by default
itsdangerous==0.24 # pyup: <1.0.0 itsdangerous==0.24 # pyup: <1.0.0
git+https://github.com/alphagov/notifications-utils.git@30.6.0#egg=notifications-utils==30.6.0 git+https://github.com/alphagov/notifications-utils.git@30.7.0#egg=notifications-utils==30.7.0
+4 -4
View File
@@ -26,13 +26,13 @@ awscli-cwlogs>=1.4,<1.5
# Putting upgrade on hold due to v1.0.0 using sha512 instead of sha1 by default # Putting upgrade on hold due to v1.0.0 using sha512 instead of sha1 by default
itsdangerous==0.24 # pyup: <1.0.0 itsdangerous==0.24 # pyup: <1.0.0
git+https://github.com/alphagov/notifications-utils.git@30.6.0#egg=notifications-utils==30.6.0 git+https://github.com/alphagov/notifications-utils.git@30.7.0#egg=notifications-utils==30.7.0
## The following requirements were added by pip freeze: ## The following requirements were added by pip freeze:
awscli==1.16.58 awscli==1.16.61
bleach==2.1.3 bleach==2.1.3
boto3==1.6.16 boto3==1.6.16
botocore==1.12.48 botocore==1.12.51
certifi==2018.10.15 certifi==2018.10.15
chardet==3.0.4 chardet==3.0.4
Click==7.0 Click==7.0
@@ -54,7 +54,7 @@ lxml==4.2.5
MarkupSafe==1.1.0 MarkupSafe==1.1.0
mistune==0.8.3 mistune==0.8.3
monotonic==1.5 monotonic==1.5
openpyxl==2.5.10 openpyxl==2.5.11
orderedset==2.0.1 orderedset==2.0.1
phonenumbers==8.9.4 phonenumbers==8.9.4
pyasn1==0.4.4 pyasn1==0.4.4
+2 -2
View File
@@ -2803,7 +2803,7 @@ def test_one_off_letters_have_download_link(
template_id=fake_uuid, template_id=fake_uuid,
filetype='pdf', filetype='pdf',
) )
assert page.select_one('a[download]').text == 'Download as a printable PDF' assert page.select_one('a[download]').text == 'Download as a PDF'
def test_send_one_off_letter_errors_in_trial_mode( def test_send_one_off_letter_errors_in_trial_mode(
@@ -2848,7 +2848,7 @@ def test_send_one_off_letter_errors_in_trial_mode(
assert not page.select('[type=submit]') assert not page.select('[type=submit]')
assert page.select_one('.page-footer-back-link').text == 'Back' assert page.select_one('.page-footer-back-link').text == 'Back'
assert page.select_one('a[download]').text == 'Download as a printable PDF' assert page.select_one('a[download]').text == 'Download as a PDF'
def test_check_messages_shows_over_max_row_error( def test_check_messages_shows_over_max_row_error(
+24 -18
View File
@@ -216,7 +216,7 @@ def test_should_show_overview_for_service_with_more_things_set(
def test_if_cant_send_letters_then_cant_see_postage( def test_if_cant_send_letters_then_cant_see_postage(
logged_in_platform_admin_client, client_request,
service_one, service_one,
single_reply_to_email_address, single_reply_to_email_address,
single_letter_contact_block, single_letter_contact_block,
@@ -224,8 +224,7 @@ def test_if_cant_send_letters_then_cant_see_postage(
single_sms_sender, single_sms_sender,
mock_get_service_settings_page_common, mock_get_service_settings_page_common,
): ):
response = logged_in_platform_admin_client.get(url_for('main.service_settings', service_id=SERVICE_ONE_ID)) page = client_request.get('main.service_settings', service_id=SERVICE_ONE_ID)
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
letter_table = page.find_all('table')[3] letter_table = page.find_all('table')[3]
rows = letter_table.find_all('tr') rows = letter_table.find_all('tr')
@@ -2148,35 +2147,42 @@ def test_set_letter_branding_saves(
mock_update_service.assert_called_once_with(service_one['id'], dvla_organisation='500') mock_update_service.assert_called_once_with(service_one['id'], dvla_organisation='500')
def test_set_postage_platform_admin_only( def test_set_postage_only_for_letter_services(
logged_in_client, client_request,
service_one,
): ):
response = logged_in_client.get(url_for('main.service_set_postage', service_id=SERVICE_ONE_ID)) client_request.get(
assert response.status_code == 403 'main.service_set_postage',
service_id=SERVICE_ONE_ID,
_expected_status=404,
)
def test_set_postage_prepopulates( def test_set_postage_prepopulates(
logged_in_platform_admin_client, client_request,
service_one, service_one,
): ):
response = logged_in_platform_admin_client.get(url_for('main.service_set_postage', service_id=SERVICE_ONE_ID)) service_one['permissions'] += ['letter']
assert response.status_code == 200 page = client_request.get('main.service_set_postage', service_id=SERVICE_ONE_ID)
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.select('input[checked]')[0]['value'] == 'second' assert page.select('input[checked]')[0]['value'] == 'second'
def test_set_postage_saves( def test_set_postage_saves(
logged_in_platform_admin_client, client_request,
service_one, service_one,
mock_update_service, mock_update_service,
): ):
response = logged_in_platform_admin_client.post( service_one['permissions'] += ['letter']
url_for('main.service_set_postage', service_id=SERVICE_ONE_ID), client_request.post(
data={'postage': 'first'} 'main.service_set_postage',
service_id=SERVICE_ONE_ID,
_data={'postage': 'first'},
_expected_status=302,
expected_redirect=url_for(
'main.service_settings',
service_id=SERVICE_ONE_ID,
_external=True,
)
) )
assert response.status_code == 302
assert response.location == url_for('main.service_settings', service_id=SERVICE_ONE_ID, _external=True)
mock_update_service.assert_called_once_with(SERVICE_ONE_ID, postage='first') mock_update_service.assert_called_once_with(SERVICE_ONE_ID, postage='first')
+147 -13
View File
@@ -107,6 +107,8 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
'extra_args,' 'extra_args,'
'expected_nav_links,' 'expected_nav_links,'
'expected_items, ' 'expected_items, '
'expected_displayed_items, '
'expected_searchable_text, '
'expected_empty_message ' 'expected_empty_message '
), ),
[ [
@@ -118,6 +120,11 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
['Text message', 'Email', 'Letter'], ['Text message', 'Email', 'Letter'],
[ [
'folder_one 2 folders', 'folder_one 2 folders',
'folder_one / folder_one_one 1 template, 1 folder',
'folder_one / folder_one_one / folder_one_one_one 1 template',
'folder_one / folder_one_one / folder_one_one_one / sms_template_nested Text message template',
'folder_one / folder_one_one / letter_template_nested Letter template',
'folder_one / folder_one_two Empty',
'folder_two Empty', 'folder_two Empty',
'sms_template_one Text message template', 'sms_template_one Text message template',
'sms_template_two Text message template', 'sms_template_two Text message template',
@@ -126,6 +133,31 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
'letter_template_one Letter template', 'letter_template_one Letter template',
'letter_template_two Letter template', 'letter_template_two Letter template',
], ],
[
'folder_one 2 folders',
'folder_two Empty',
'sms_template_one Text message template',
'sms_template_two Text message template',
'email_template_one Email template',
'email_template_two Email template',
'letter_template_one Letter template',
'letter_template_two Letter template',
],
[
'folder_one',
'folder_one_one',
'folder_one_one_one',
'sms_template_nested',
'letter_template_nested',
'folder_one_two',
'folder_two',
'sms_template_one',
'sms_template_two',
'email_template_one',
'email_template_two',
'letter_template_one',
'letter_template_two',
],
None, None,
), ),
( (
@@ -136,9 +168,25 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
['All', 'Email', 'Letter'], ['All', 'Email', 'Letter'],
[ [
'folder_one 1 folder', 'folder_one 1 folder',
'folder_one / folder_one_one 1 folder',
'folder_one / folder_one_one / folder_one_one_one 1 template',
'folder_one / folder_one_one / folder_one_one_one / sms_template_nested Text message template',
'sms_template_one Text message template', 'sms_template_one Text message template',
'sms_template_two Text message template', 'sms_template_two Text message template',
], ],
[
'folder_one 1 folder',
'sms_template_one Text message template',
'sms_template_two Text message template',
],
[
'folder_one',
'folder_one_one',
'folder_one_one_one',
'sms_template_nested',
'sms_template_one',
'sms_template_two',
],
None, None,
), ),
( (
@@ -149,8 +197,22 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
['Text message', 'Email', 'Letter'], ['Text message', 'Email', 'Letter'],
[ [
'folder_one_one 1 template, 1 folder', 'folder_one_one 1 template, 1 folder',
'folder_one_one / folder_one_one_one 1 template',
'folder_one_one / folder_one_one_one / sms_template_nested Text message template',
'folder_one_one / letter_template_nested Letter template',
'folder_one_two Empty', 'folder_one_two Empty',
], ],
[
'folder_one_one 1 template, 1 folder',
'folder_one_two Empty',
],
[
'folder_one_one',
'folder_one_one_one',
'sms_template_nested',
'letter_template_nested',
'folder_one_two',
],
None, None,
), ),
( (
@@ -161,6 +223,16 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
['All', 'Email', 'Letter'], ['All', 'Email', 'Letter'],
[ [
'folder_one_one 1 folder', 'folder_one_one 1 folder',
'folder_one_one / folder_one_one_one 1 template',
'folder_one_one / folder_one_one_one / sms_template_nested Text message template',
],
[
'folder_one_one 1 folder',
],
[
'folder_one_one',
'folder_one_one_one',
'sms_template_nested',
], ],
None, None,
), ),
@@ -171,6 +243,8 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
{'template_type': 'email', 'template_folder_id': PARENT_FOLDER_ID}, {'template_type': 'email', 'template_folder_id': PARENT_FOLDER_ID},
['All', 'Text message', 'Letter'], ['All', 'Text message', 'Letter'],
[], [],
[],
[],
'There are no email templates in this folder', 'There are no email templates in this folder',
), ),
( (
@@ -181,8 +255,18 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
['Text message', 'Email', 'Letter'], ['Text message', 'Email', 'Letter'],
[ [
'folder_one_one_one 1 template', 'folder_one_one_one 1 template',
'folder_one_one_one / sms_template_nested Text message template',
'letter_template_nested Letter template', 'letter_template_nested Letter template',
], ],
[
'folder_one_one_one 1 template',
'letter_template_nested Letter template',
],
[
'folder_one_one_one',
'sms_template_nested',
'letter_template_nested',
],
None, None,
), ),
( (
@@ -194,6 +278,12 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
[ [
'sms_template_nested Text message template', 'sms_template_nested Text message template',
], ],
[
'sms_template_nested Text message template',
],
[
'sms_template_nested',
],
None, None,
), ),
( (
@@ -203,6 +293,8 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
{'template_folder_id': FOLDER_TWO_ID}, {'template_folder_id': FOLDER_TWO_ID},
['Text message', 'Email', 'Letter'], ['Text message', 'Email', 'Letter'],
[], [],
[],
[],
'This folder is empty', 'This folder is empty',
), ),
( (
@@ -212,6 +304,8 @@ def test_post_add_template_folder_page(client_request, service_one, mocker, pare
{'template_folder_id': FOLDER_TWO_ID, 'template_type': 'sms'}, {'template_folder_id': FOLDER_TWO_ID, 'template_type': 'sms'},
['All', 'Email', 'Letter'], ['All', 'Email', 'Letter'],
[], [],
[],
[],
'This folder is empty', 'This folder is empty',
), ),
] ]
@@ -229,6 +323,8 @@ def test_should_show_templates_folder_page(
extra_args, extra_args,
expected_nav_links, expected_nav_links,
expected_items, expected_items,
expected_displayed_items,
expected_searchable_text,
expected_empty_message, expected_empty_message,
): ):
mock_get_template_folders.return_value = [ mock_get_template_folders.return_value = [
@@ -281,12 +377,32 @@ def test_should_show_templates_folder_page(
for index, expected_link in enumerate(expected_nav_links): for index, expected_link in enumerate(expected_nav_links):
assert links_in_page[index].text.strip() == expected_link assert links_in_page[index].text.strip() == expected_link
page_items = page.select('.template-list-item') all_page_items = page.select('.template-list-item')
checkboxes = page.select('input[name=templates_and_folders]')
assert len(page_items) == len(expected_items) unique_checkbox_values = set(item['value'] for item in checkboxes)
assert len(all_page_items) == len(expected_items)
assert len(checkboxes) == len(expected_items)
assert len(unique_checkbox_values) == len(expected_items)
for index, expected_item in enumerate(expected_items): for index, expected_item in enumerate(expected_items):
assert normalize_spaces(page_items[index].text) == expected_item assert normalize_spaces(all_page_items[index].text) == expected_item
displayed_page_items = page.find_all(lambda tag: (
tag.has_attr('class')
and 'template-list-item' in tag['class']
and 'template-list-item-hidden-by-default' not in tag['class']
))
assert len(displayed_page_items) == len(expected_displayed_items)
for index, expected_item in enumerate(expected_displayed_items):
assert '/' not in expected_item # Yo dawg I heard you like tests…
assert normalize_spaces(displayed_page_items[index].text) == expected_item
all_searchable_text = page.select('#template-list .template-list-item .live-search-relevant')
assert len(all_searchable_text) == len(expected_searchable_text)
for index, expected_item in enumerate(expected_searchable_text):
assert normalize_spaces(all_searchable_text[index].text) == expected_item
if expected_empty_message: if expected_empty_message:
assert normalize_spaces(page.select_one('.template-list-empty').text) == ( assert normalize_spaces(page.select_one('.template-list-empty').text) == (
@@ -402,7 +518,7 @@ def test_get_manage_folder_page(
assert normalize_spaces(page.select_one('title').text) == ( assert normalize_spaces(page.select_one('title').text) == (
'folder_two Templates service one GOV.UK Notify' 'folder_two Templates service one GOV.UK Notify'
) )
assert page.select_one('input[name=name]') is not None assert page.select_one('input[name=name]')['value'] == 'folder_two'
delete_link = page.find('a', string="Delete this folder") delete_link = page.find('a', string="Delete this folder")
expected_delete_url = "/services/{}/templates/folders/{}/delete".format(service_one['id'], folder_id) expected_delete_url = "/services/{}/templates/folders/{}/delete".format(service_one['id'], folder_id)
@@ -478,9 +594,20 @@ def test_delete_template_folder_should_request_confirmation(
'Yes, delete' 'Yes, delete'
) )
assert len(page.select('label')) == 0 assert page.select_one('input[name=name]')['value'] == 'sacrifice'
assert len(page.select('button')) == 1
assert "Back to manage folder page" in page.text assert len(page.select('form')) == 2
assert len(page.select('button')) == 2
assert 'action' not in page.select('form')[0]
assert page.select('form button')[0].text == 'Yes, delete'
assert page.select('form')[1]['action'] == url_for(
'main.manage_template_folder',
service_id=service_one['id'],
template_folder_id=folder_id,
)
assert page.select('form button')[1].text == 'Save'
def test_delete_template_folder_should_detect_non_empty_folder_on_get( def test_delete_template_folder_should_detect_non_empty_folder_on_get(
@@ -511,11 +638,15 @@ def test_delete_template_folder_should_detect_non_empty_folder_on_get(
) )
def test_delete_folder(client_request, service_one, mock_get_template_folders, mocker): @pytest.mark.parametrize('parent_folder_id', (
None,
PARENT_FOLDER_ID,
))
def test_delete_folder(client_request, service_one, mock_get_template_folders, mocker, parent_folder_id):
mock_delete = mocker.patch('app.template_folder_api_client.delete_template_folder') mock_delete = mocker.patch('app.template_folder_api_client.delete_template_folder')
folder_id = str(uuid.uuid4()) folder_id = str(uuid.uuid4())
mock_get_template_folders.side_effect = [[ mock_get_template_folders.side_effect = [[
{'id': folder_id, 'name': 'sacrifice', 'parent_id': None}, {'id': folder_id, 'name': 'sacrifice', 'parent_id': parent_folder_id},
], []] ], []]
mocker.patch( mocker.patch(
'app.models.service.Service.get_templates', 'app.models.service.Service.get_templates',
@@ -527,9 +658,12 @@ def test_delete_folder(client_request, service_one, mock_get_template_folders, m
'main.delete_template_folder', 'main.delete_template_folder',
service_id=service_one['id'], service_id=service_one['id'],
template_folder_id=folder_id, template_folder_id=folder_id,
_expected_redirect=url_for("main.choose_template", _expected_redirect=url_for(
service_id=service_one['id'], "main.choose_template",
_external=True) service_id=service_one['id'],
template_folder_id=parent_folder_id,
_external=True,
)
) )
mock_delete.assert_called_once_with(service_one['id'], folder_id) mock_delete.assert_called_once_with(service_one['id'], folder_id)
+96 -1
View File
@@ -20,6 +20,7 @@ from tests import (
from tests.app.main.views.test_template_folders import ( from tests.app.main.views.test_template_folders import (
CHILD_FOLDER_ID, CHILD_FOLDER_ID,
PARENT_FOLDER_ID, PARENT_FOLDER_ID,
_folder,
) )
from tests.conftest import ( from tests.conftest import (
SERVICE_ONE_ID, SERVICE_ONE_ID,
@@ -364,6 +365,40 @@ def test_should_post_move_to_api(
) )
def test_should_be_able_to_move_a_sub_item(
client_request,
service_one,
fake_uuid,
mock_get_service_templates,
mock_get_template_folders,
mock_move_to_template_folder,
):
service_one['permissions'] += ['edit_folders']
GRANDCHILD_FOLDER_ID = str(uuid.uuid4())
mock_get_template_folders.return_value = [
{'id': PARENT_FOLDER_ID, 'name': 'folder_one', 'parent_id': None},
{'id': CHILD_FOLDER_ID, 'name': 'folder_one_one', 'parent_id': PARENT_FOLDER_ID},
{'id': GRANDCHILD_FOLDER_ID, 'name': 'folder_one_one_one', 'parent_id': CHILD_FOLDER_ID},
]
client_request.post(
'main.choose_template',
service_id=SERVICE_ONE_ID,
template_folder_id=PARENT_FOLDER_ID,
_data={
'operation': 'move',
'move_to': 'None',
'templates_and_folders': [GRANDCHILD_FOLDER_ID],
},
_expected_status=302,
)
mock_move_to_template_folder.assert_called_once_with(
service_id=SERVICE_ONE_ID,
folder_id=None,
folder_ids={GRANDCHILD_FOLDER_ID},
template_ids=set(),
)
@pytest.mark.parametrize('thing_to_move', [ @pytest.mark.parametrize('thing_to_move', [
PARENT_FOLDER_ID, # Cant move a folder inside itself PARENT_FOLDER_ID, # Cant move a folder inside itself
CHILD_FOLDER_ID, # Cant move a folder which doesnt belong to the service CHILD_FOLDER_ID, # Cant move a folder which doesnt belong to the service
@@ -446,6 +481,33 @@ def test_should_show_live_search_if_list_of_templates_taller_than_screen(
assert len(page.select(search['data-targets'])) == len(page.select('.message-name')) == 14 assert len(page.select(search['data-targets'])) == len(page.select('.message-name')) == 14
def test_should_show_live_search_if_service_has_lots_of_folders(
client_request,
mock_get_template_folders,
mock_get_service_templates, # returns 4 templates
):
mock_get_template_folders.return_value = [
_folder('one', PARENT_FOLDER_ID),
_folder('two', None, parent=PARENT_FOLDER_ID),
_folder('three', None, parent=PARENT_FOLDER_ID),
_folder('four', None, parent=PARENT_FOLDER_ID),
]
page = client_request.get(
'main.choose_template',
service_id=SERVICE_ONE_ID,
)
count_of_templates_and_folders = len(page.select('.message-name'))
count_of_folders = len(page.select('.template-list-folder'))
count_of_templates = count_of_templates_and_folders - count_of_folders
assert len(page.select('.live-search')) == 1
assert count_of_folders == 4
assert count_of_templates == 4
def test_should_show_page_for_one_template( def test_should_show_page_for_one_template(
logged_in_client, logged_in_client,
mock_get_service_template, mock_get_service_template,
@@ -823,11 +885,44 @@ def test_choose_a_template_to_copy(
) )
@pytest.mark.parametrize('existing_template_names, expected_name', (
(
['Two week reminder'],
'Two week reminder (copy)'
),
(
['Two week reminder (copy)'],
'Two week reminder (copy 2)'
),
(
['Two week reminder', 'Two week reminder (copy)'],
'Two week reminder (copy 2)'
),
(
['Two week reminder (copy 8)', 'Two week reminder (copy 9)'],
'Two week reminder (copy 10)'
),
(
['Two week reminder (copy)', 'Two week reminder (copy 9)'],
'Two week reminder (copy 10)'
),
(
['Two week reminder (copy)', 'Two week reminder (copy 10)'],
'Two week reminder (copy 2)'
),
))
def test_load_edit_template_with_copy_of_template( def test_load_edit_template_with_copy_of_template(
client_request, client_request,
mock_get_service_templates,
mock_get_service_email_template, mock_get_service_email_template,
mock_get_non_empty_organisations_and_services_for_user, mock_get_non_empty_organisations_and_services_for_user,
existing_template_names,
expected_name,
): ):
mock_get_service_templates.side_effect = lambda service_id: {'data': [
{'name': existing_template_name, 'template_type': 'sms'}
for existing_template_name in existing_template_names
]}
page = client_request.get( page = client_request.get(
'main.copy_template', 'main.copy_template',
service_id=SERVICE_ONE_ID, service_id=SERVICE_ONE_ID,
@@ -838,7 +933,7 @@ def test_load_edit_template_with_copy_of_template(
assert page.select_one('form')['method'] == 'post' assert page.select_one('form')['method'] == 'post'
assert page.select_one('input')['value'] == ( assert page.select_one('input')['value'] == (
'Copy of Two week reminder' expected_name
) )
assert page.select_one('textarea').text == ( assert page.select_one('textarea').text == (
'Your ((thing)) is due soon' 'Your ((thing)) is due soon'