mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 18:37:33 -04:00
Merge branch 'master' of github.com:alphagov/notifications-admin
This commit is contained in:
@@ -15,6 +15,8 @@ from app.notify_client.api_key_api_client import ApiKeyApiClient
|
|||||||
from app.notify_client.user_api_client import UserApiClient
|
from app.notify_client.user_api_client import UserApiClient
|
||||||
from app.notify_client.job_api_client import JobApiClient
|
from app.notify_client.job_api_client import JobApiClient
|
||||||
from app.notify_client.status_api_client import StatusApiClient
|
from app.notify_client.status_api_client import StatusApiClient
|
||||||
|
from app.notify_client.permission_api_client import PermissionApiClient
|
||||||
|
from app.notify_client.invite_api_client import InviteApiClient
|
||||||
from app.its_dangerous_session import ItsdangerousSessionInterface
|
from app.its_dangerous_session import ItsdangerousSessionInterface
|
||||||
from app.asset_fingerprinter import AssetFingerprinter
|
from app.asset_fingerprinter import AssetFingerprinter
|
||||||
from app.utils import validate_phone_number, InvalidPhoneError
|
from app.utils import validate_phone_number, InvalidPhoneError
|
||||||
@@ -30,7 +32,9 @@ user_api_client = UserApiClient()
|
|||||||
api_key_api_client = ApiKeyApiClient()
|
api_key_api_client = ApiKeyApiClient()
|
||||||
job_api_client = JobApiClient()
|
job_api_client = JobApiClient()
|
||||||
status_api_client = StatusApiClient()
|
status_api_client = StatusApiClient()
|
||||||
|
invite_api_client = InviteApiClient()
|
||||||
asset_fingerprinter = AssetFingerprinter()
|
asset_fingerprinter = AssetFingerprinter()
|
||||||
|
permission_api_client = PermissionApiClient()
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_name, config_overrides=None):
|
def create_app(config_name, config_overrides=None):
|
||||||
@@ -47,6 +51,8 @@ def create_app(config_name, config_overrides=None):
|
|||||||
api_key_api_client.init_app(application)
|
api_key_api_client.init_app(application)
|
||||||
job_api_client.init_app(application)
|
job_api_client.init_app(application)
|
||||||
status_api_client.init_app(application)
|
status_api_client.init_app(application)
|
||||||
|
permission_api_client.init_app(application)
|
||||||
|
invite_api_client.init_app(application)
|
||||||
|
|
||||||
login_manager.init_app(application)
|
login_manager.init_app(application)
|
||||||
login_manager.login_view = 'main.sign_in'
|
login_manager.login_view = 'main.sign_in'
|
||||||
|
|||||||
@@ -15,3 +15,7 @@
|
|||||||
margin-bottom: $gutter;
|
margin-bottom: $gutter;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-gutter-2-3 {
|
||||||
|
margin-bottom: $gutter * 2/3;
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -164,13 +164,13 @@ class AddServiceForm(Form):
|
|||||||
name = StringField(
|
name = StringField(
|
||||||
'Service name',
|
'Service name',
|
||||||
validators=[
|
validators=[
|
||||||
DataRequired(message='Service name can not be empty')
|
DataRequired(message='Service name can’t be empty')
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_name(self, a):
|
def validate_name(self, a):
|
||||||
if a.data in self._names_func():
|
if a.data in self._names_func():
|
||||||
raise ValidationError('Service name already exists')
|
raise ValidationError('This service name is already in use')
|
||||||
|
|
||||||
|
|
||||||
class ServiceNameForm(Form):
|
class ServiceNameForm(Form):
|
||||||
|
|||||||
@@ -11,39 +11,15 @@ from app.main.forms import AddServiceForm
|
|||||||
@login_required
|
@login_required
|
||||||
def add_service():
|
def add_service():
|
||||||
form = AddServiceForm(services_dao.find_all_service_names)
|
form = AddServiceForm(services_dao.find_all_service_names)
|
||||||
services = services_dao.get_services(current_user.id)
|
heading = 'Which service do you want to set up notifications for?'
|
||||||
if len(services['data']) == 0:
|
|
||||||
heading = 'Which service do you want to set up notifications for?'
|
|
||||||
else:
|
|
||||||
heading = 'Add a new service'
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
session['service_name'] = form.name.data
|
session['service_name'] = form.name.data
|
||||||
return redirect(url_for('main.add_from_address'))
|
user = users_dao.get_user_by_id(session['user_id'])
|
||||||
|
service_id = services_dao.insert_new_service(session['service_name'], user.id)
|
||||||
|
return redirect(url_for('main.service_dashboard', service_id=service_id))
|
||||||
else:
|
else:
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/add-service.html',
|
'views/add-service.html',
|
||||||
form=form,
|
form=form,
|
||||||
heading=heading
|
heading=heading
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/confirm-add-service", methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def add_from_address():
|
|
||||||
if request.method == 'POST':
|
|
||||||
user = users_dao.get_user_by_id(session['user_id'])
|
|
||||||
service_id = services_dao.insert_new_service(session['service_name'], user.id)
|
|
||||||
return redirect(url_for('main.service_dashboard', service_id=service_id))
|
|
||||||
else:
|
|
||||||
return render_template(
|
|
||||||
'views/add-from-address.html',
|
|
||||||
service_name=session['service_name'],
|
|
||||||
from_address="{}@notifications.service.gov.uk".format(_email_safe(session['service_name']))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _email_safe(string):
|
|
||||||
return "".join([
|
|
||||||
character.lower() if character.isalnum() or character == "." else ""
|
|
||||||
for character in re.sub("\s+", ".", string.strip())
|
|
||||||
])
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from flask_login import login_required
|
|||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.forms import CreateKeyForm
|
from app.main.forms import CreateKeyForm
|
||||||
from app import api_key_api_client
|
from app import api_key_api_client
|
||||||
|
from app.utils import user_has_permissions
|
||||||
|
|
||||||
|
|
||||||
@main.route("/services/<service_id>/documentation")
|
@main.route("/services/<service_id>/documentation")
|
||||||
@@ -13,6 +14,7 @@ def documentation(service_id):
|
|||||||
|
|
||||||
@main.route("/services/<service_id>/api-keys")
|
@main.route("/services/<service_id>/api-keys")
|
||||||
@login_required
|
@login_required
|
||||||
|
@user_has_permissions('manage_api_keys')
|
||||||
def api_keys(service_id):
|
def api_keys(service_id):
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/api-keys.html',
|
'views/api-keys.html',
|
||||||
@@ -23,6 +25,7 @@ def api_keys(service_id):
|
|||||||
|
|
||||||
@main.route("/services/<service_id>/api-keys/create", methods=['GET', 'POST'])
|
@main.route("/services/<service_id>/api-keys/create", methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@user_has_permissions('manage_api_keys')
|
||||||
def create_api_key(service_id):
|
def create_api_key(service_id):
|
||||||
key_names = [
|
key_names = [
|
||||||
key['name'] for key in api_key_api_client.get_api_keys(service_id=service_id)['apiKeys']
|
key['name'] for key in api_key_api_client.get_api_keys(service_id=service_id)['apiKeys']
|
||||||
@@ -41,6 +44,7 @@ def create_api_key(service_id):
|
|||||||
|
|
||||||
@main.route("/services/<service_id>/api-keys/revoke/<int:key_id>", methods=['GET', 'POST'])
|
@main.route("/services/<service_id>/api-keys/revoke/<int:key_id>", methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@user_has_permissions('manage_api_keys')
|
||||||
def revoke_api_key(service_id, key_id):
|
def revoke_api_key(service_id, key_id):
|
||||||
key_name = api_key_api_client.get_api_keys(service_id=service_id, key_id=key_id)['apiKeys'][0]['name']
|
key_name = api_key_api_client.get_api_keys(service_id=service_id, key_id=key_id)['apiKeys'][0]['name']
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ def service_dashboard(service_id):
|
|||||||
try:
|
try:
|
||||||
service = get_service_by_id(service_id)
|
service = get_service_by_id(service_id)
|
||||||
session['service_name'] = service['data']['name']
|
session['service_name'] = service['data']['name']
|
||||||
|
session['service_id'] = service['data']['id']
|
||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
if e.status_code == 404:
|
if e.status_code == 404:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from notification_utils.template import Template
|
|||||||
from app import job_api_client
|
from app import job_api_client
|
||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.dao import templates_dao
|
from app.main.dao import templates_dao
|
||||||
|
from app.main.dao import services_dao
|
||||||
|
|
||||||
now = time.strftime('%H:%M')
|
now = time.strftime('%H:%M')
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ def view_jobs(service_id):
|
|||||||
@main.route("/services/<service_id>/jobs/<job_id>")
|
@main.route("/services/<service_id>/jobs/<job_id>")
|
||||||
@login_required
|
@login_required
|
||||||
def view_job(service_id, job_id):
|
def view_job(service_id, job_id):
|
||||||
|
service = services_dao.get_service_by_id_or_404(service_id)
|
||||||
try:
|
try:
|
||||||
job = job_api_client.get_job(service_id, job_id)['data']
|
job = job_api_client.get_job(service_id, job_id)['data']
|
||||||
messages = []
|
messages = []
|
||||||
@@ -56,9 +58,11 @@ def view_job(service_id, job_id):
|
|||||||
uploaded_file_name=job['original_file_name'],
|
uploaded_file_name=job['original_file_name'],
|
||||||
uploaded_file_time=job['created_at'],
|
uploaded_file_time=job['created_at'],
|
||||||
template=Template(
|
template=Template(
|
||||||
templates_dao.get_service_template_or_404(service_id, job['template'])['data']
|
templates_dao.get_service_template_or_404(service_id, job['template'])['data'],
|
||||||
|
prefix=service['name']
|
||||||
),
|
),
|
||||||
service_id=service_id
|
service_id=service_id,
|
||||||
|
service=service
|
||||||
)
|
)
|
||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
if e.status_code == 404:
|
if e.status_code == 404:
|
||||||
|
|||||||
@@ -7,13 +7,18 @@ from flask import (
|
|||||||
flash
|
flash
|
||||||
)
|
)
|
||||||
|
|
||||||
from flask_login import login_required, current_user
|
from flask_login import (
|
||||||
|
login_required,
|
||||||
|
current_user
|
||||||
|
)
|
||||||
|
|
||||||
|
from notifications_python_client.errors import HTTPError
|
||||||
|
|
||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.dao import users_dao
|
|
||||||
from app.main.forms import InviteUserForm
|
from app.main.forms import InviteUserForm
|
||||||
from app.main.dao.services_dao import get_service_by_id_or_404
|
from app.main.dao.services_dao import get_service_by_id_or_404
|
||||||
from app import user_api_client
|
from app import user_api_client
|
||||||
|
from app import invite_api_client
|
||||||
|
|
||||||
fake_users = [
|
fake_users = [
|
||||||
{
|
{
|
||||||
@@ -29,14 +34,19 @@ fake_users = [
|
|||||||
@main.route("/services/<service_id>/users")
|
@main.route("/services/<service_id>/users")
|
||||||
@login_required
|
@login_required
|
||||||
def manage_users(service_id):
|
def manage_users(service_id):
|
||||||
users = user_api_client.get_users_for_service(service_id=service_id)
|
try:
|
||||||
return render_template(
|
users = user_api_client.get_users_for_service(service_id=service_id)
|
||||||
'views/manage-users.html',
|
invited_users = invite_api_client.get_invites_for_service(service_id=service_id)
|
||||||
service_id=service_id,
|
return render_template('views/manage-users.html',
|
||||||
users=users,
|
service_id=service_id,
|
||||||
current_user=current_user,
|
users=users,
|
||||||
invited_users=[]
|
current_user=current_user,
|
||||||
)
|
invited_users=invited_users)
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.status_code == 404:
|
||||||
|
abort(404)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
@main.route("/services/<service_id>/users/invite", methods=['GET', 'POST'])
|
@main.route("/services/<service_id>/users/invite", methods=['GET', 'POST'])
|
||||||
@@ -44,10 +54,19 @@ def manage_users(service_id):
|
|||||||
def invite_user(service_id):
|
def invite_user(service_id):
|
||||||
|
|
||||||
form = InviteUserForm()
|
form = InviteUserForm()
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
flash('Invite sent to {}'.format(form.email_address.data), 'default_with_tick')
|
email_address = form.email_address.data
|
||||||
return redirect(url_for('.manage_users', service_id=service_id))
|
permissions = _get_permissions(request.form)
|
||||||
|
try:
|
||||||
|
resp = invite_api_client.create_invite(current_user.id, service_id, email_address, permissions)
|
||||||
|
flash('Invite sent to {}'.format(resp['email_address']), 'default_with_tick')
|
||||||
|
return redirect(url_for('.manage_users', service_id=service_id))
|
||||||
|
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.status_code == 404:
|
||||||
|
abort(404)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/invite-user.html',
|
'views/invite-user.html',
|
||||||
@@ -95,3 +114,14 @@ def delete_user(service_id, user_id):
|
|||||||
service=get_service_by_id_or_404(service_id),
|
service=get_service_by_id_or_404(service_id),
|
||||||
service_id=service_id
|
service_id=service_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_permissions(form):
|
||||||
|
permissions = []
|
||||||
|
if form.get('send_messages') and form['send_messages'] == 'yes':
|
||||||
|
permissions.append('send_messages')
|
||||||
|
if form.get('manage_service') and form['manage_service'] == 'yes':
|
||||||
|
permissions.append('manage_service')
|
||||||
|
if form.get('manage_api_keys') and form['manage_api_keys'] == 'yes':
|
||||||
|
permissions.append('manage_api_keys')
|
||||||
|
return ','.join(permissions)
|
||||||
|
|||||||
+64
-33
@@ -1,9 +1,6 @@
|
|||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
import uuid
|
import uuid
|
||||||
import botocore
|
|
||||||
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
request,
|
request,
|
||||||
@@ -17,7 +14,6 @@ from flask import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from werkzeug import secure_filename
|
|
||||||
from notifications_python_client.errors import HTTPError
|
from notifications_python_client.errors import HTTPError
|
||||||
from utils.template import Template, NeededByTemplateError, NoPlaceholderForDataError
|
from utils.template import Template, NeededByTemplateError, NoPlaceholderForDataError
|
||||||
|
|
||||||
@@ -32,16 +28,23 @@ from app.main.dao import services_dao
|
|||||||
from app import job_api_client
|
from app import job_api_client
|
||||||
from app.utils import validate_recipient, InvalidPhoneError, InvalidEmailError
|
from app.utils import validate_recipient, InvalidPhoneError, InvalidEmailError
|
||||||
|
|
||||||
first_column_header = {
|
page_headings = {
|
||||||
'email': 'email',
|
'email': 'Send emails',
|
||||||
'sms': 'phone'
|
'sms': 'Send text messages'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/services/<service_id>/send/letters", methods=['GET'])
|
||||||
|
def letters_stub(service_id):
|
||||||
|
return render_template(
|
||||||
|
'views/letters.html', service_id=service_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/services/<service_id>/send/<template_type>", methods=['GET'])
|
@main.route("/services/<service_id>/send/<template_type>", methods=['GET'])
|
||||||
def choose_template(service_id, template_type):
|
def choose_template(service_id, template_type):
|
||||||
|
|
||||||
services_dao.get_service_by_id_or_404(service_id)
|
service = services_dao.get_service_by_id_or_404(service_id)
|
||||||
|
|
||||||
if template_type not in ['email', 'sms']:
|
if template_type not in ['email', 'sms']:
|
||||||
abort(404)
|
abort(404)
|
||||||
@@ -53,11 +56,17 @@ def choose_template(service_id, template_type):
|
|||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/choose-{}-template.html'.format(template_type),
|
'views/choose-template.html',
|
||||||
templates=[
|
templates=[
|
||||||
Template(template) for template in templates_dao.get_service_templates(service_id)['data']
|
Template(
|
||||||
|
template,
|
||||||
|
prefix=service['name']
|
||||||
|
) for template in templates_dao.get_service_templates(service_id)['data']
|
||||||
if template['template_type'] == template_type
|
if template['template_type'] == template_type
|
||||||
],
|
],
|
||||||
|
template_type=template_type,
|
||||||
|
page_heading=page_headings[template_type],
|
||||||
|
service=service,
|
||||||
has_jobs=len(jobs),
|
has_jobs=len(jobs),
|
||||||
service_id=service_id
|
service_id=service_id
|
||||||
)
|
)
|
||||||
@@ -70,7 +79,7 @@ def send_messages(service_id, template_id):
|
|||||||
form = CsvUploadForm()
|
form = CsvUploadForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
try:
|
try:
|
||||||
csv_file = form.file.data
|
csv_file = form.file
|
||||||
filedata = _get_filedata(csv_file)
|
filedata = _get_filedata(csv_file)
|
||||||
upload_id = str(uuid.uuid4())
|
upload_id = str(uuid.uuid4())
|
||||||
s3upload(upload_id, service_id, filedata, current_app.config['AWS_REGION'])
|
s3upload(upload_id, service_id, filedata, current_app.config['AWS_REGION'])
|
||||||
@@ -79,19 +88,20 @@ def send_messages(service_id, template_id):
|
|||||||
service_id=service_id,
|
service_id=service_id,
|
||||||
upload_id=upload_id))
|
upload_id=upload_id))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
flash('There was a problem uploading: {}'.format(csv_file.filename))
|
flash('There was a problem uploading: {}'.format(csv_file.data.filename))
|
||||||
flash(str(e))
|
flash(str(e))
|
||||||
return redirect(url_for('.send_messages', service_id=service_id, template_id=template_id))
|
return redirect(url_for('.send_messages', service_id=service_id, template_id=template_id))
|
||||||
|
|
||||||
service = services_dao.get_service_by_id_or_404(service_id)
|
service = services_dao.get_service_by_id_or_404(service_id)
|
||||||
template = Template(
|
template = Template(
|
||||||
templates_dao.get_service_template_or_404(service_id, template_id)['data']
|
templates_dao.get_service_template_or_404(service_id, template_id)['data'],
|
||||||
|
prefix=service['name']
|
||||||
)
|
)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/send.html',
|
'views/send.html',
|
||||||
template=template,
|
template=template,
|
||||||
column_headers=[first_column_header[template.template_type]] + template.placeholders_as_markup,
|
column_headers=['to'] + template.placeholders_as_markup,
|
||||||
form=form,
|
form=form,
|
||||||
service=service,
|
service=service,
|
||||||
service_id=service_id
|
service_id=service_id
|
||||||
@@ -105,10 +115,14 @@ def get_example_csv(service_id, template_id):
|
|||||||
placeholders = list(Template(template).placeholders)
|
placeholders = list(Template(template).placeholders)
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
writer = csv.writer(output)
|
writer = csv.writer(output)
|
||||||
writer.writerow([first_column_header[template['template_type']]] + placeholders)
|
writer.writerow(['to'] + placeholders)
|
||||||
writer.writerow([current_user.mobile_number] + ["test {}".format(header) for header in placeholders])
|
writer.writerow([
|
||||||
|
{
|
||||||
return(output.getvalue(), 200, {'Content-Type': 'text/csv; charset=utf-8'})
|
'email': current_user.email_address,
|
||||||
|
'sms': current_user.mobile_number
|
||||||
|
}[template['template_type']]
|
||||||
|
] + ["test {}".format(header) for header in placeholders])
|
||||||
|
return output.getvalue(), 200, {'Content-Type': 'text/csv; charset=utf-8'}
|
||||||
|
|
||||||
|
|
||||||
@main.route("/services/<service_id>/send/<template_id>/to-self", methods=['GET'])
|
@main.route("/services/<service_id>/send/<template_id>/to-self", methods=['GET'])
|
||||||
@@ -118,7 +132,7 @@ def send_message_to_self(service_id, template_id):
|
|||||||
placeholders = list(Template(template).placeholders)
|
placeholders = list(Template(template).placeholders)
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
writer = csv.writer(output)
|
writer = csv.writer(output)
|
||||||
writer.writerow([first_column_header[template['template_type']]] + placeholders)
|
writer.writerow(['to'] + placeholders)
|
||||||
writer.writerow([current_user.mobile_number] + ["test {}".format(header) for header in placeholders])
|
writer.writerow([current_user.mobile_number] + ["test {}".format(header) for header in placeholders])
|
||||||
filedata = {
|
filedata = {
|
||||||
'file_name': 'Test run',
|
'file_name': 'Test run',
|
||||||
@@ -140,29 +154,30 @@ def check_messages(service_id, upload_id):
|
|||||||
|
|
||||||
upload_data = session['upload_data']
|
upload_data = session['upload_data']
|
||||||
template_id = upload_data.get('template_id')
|
template_id = upload_data.get('template_id')
|
||||||
|
service = services_dao.get_service_by_id_or_404(service_id)
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
contents = s3download(service_id, upload_id)
|
contents = s3download(service_id, upload_id)
|
||||||
if not contents:
|
if not contents:
|
||||||
flash('There was a problem reading your upload file')
|
flash('There was a problem reading your upload file')
|
||||||
raw_template = templates_dao.get_service_template_or_404(service_id, template_id)['data']
|
raw_template = templates_dao.get_service_template_or_404(service_id, template_id)['data']
|
||||||
recipient_type = first_column_header[raw_template['template_type']]
|
|
||||||
upload_result = _get_rows(contents, raw_template)
|
upload_result = _get_rows(contents, raw_template)
|
||||||
session['upload_data']['notification_count'] = len(upload_result['rows'])
|
session['upload_data']['notification_count'] = len(upload_result['rows'])
|
||||||
template = Template(
|
template = Template(
|
||||||
raw_template,
|
raw_template,
|
||||||
values=upload_result['rows'][0] if upload_result['valid'] else {},
|
values=upload_result['rows'][0] if upload_result['valid'] else {},
|
||||||
drop_values={recipient_type}
|
drop_values={'to'},
|
||||||
|
prefix=service['name']
|
||||||
)
|
)
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/check-sms.html',
|
'views/check.html',
|
||||||
upload_result=upload_result,
|
upload_result=upload_result,
|
||||||
template=template,
|
template=template,
|
||||||
column_headers=[recipient_type] + list(
|
page_heading=page_headings[template.template_type],
|
||||||
template.placeholders if upload_result['valid'] else template.placeholders_as_markup
|
column_headers=['to'] + list(template.placeholders_as_markup),
|
||||||
),
|
|
||||||
original_file_name=upload_data.get('original_file_name'),
|
original_file_name=upload_data.get('original_file_name'),
|
||||||
service_id=service_id,
|
service_id=service_id,
|
||||||
|
service=service,
|
||||||
form=CsvUploadForm()
|
form=CsvUploadForm()
|
||||||
)
|
)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
@@ -184,17 +199,34 @@ def check_messages(service_id, upload_id):
|
|||||||
|
|
||||||
|
|
||||||
def _get_filedata(file):
|
def _get_filedata(file):
|
||||||
lines = file.read().decode('utf-8').splitlines()
|
import itertools
|
||||||
if len(lines) < 2: # must be at least header and one line
|
reader = csv.reader(
|
||||||
message = 'The file {} contained no data'.format(file.filename)
|
file.data.getvalue().decode('utf-8').splitlines(),
|
||||||
|
quoting=csv.QUOTE_NONE,
|
||||||
|
skipinitialspace=True
|
||||||
|
)
|
||||||
|
lines = []
|
||||||
|
for row in reader:
|
||||||
|
non_empties = itertools.dropwhile(lambda x: x.strip() == '', row)
|
||||||
|
has_content = []
|
||||||
|
for item in non_empties:
|
||||||
|
has_content.append(item)
|
||||||
|
if has_content:
|
||||||
|
lines.append(row)
|
||||||
|
|
||||||
|
if len(lines) < 2: # must be header row and at least one data row
|
||||||
|
message = 'The file {} contained no data'.format(file.data.filename)
|
||||||
raise ValueError(message)
|
raise ValueError(message)
|
||||||
return {'file_name': file.filename, 'data': lines}
|
|
||||||
|
content_lines = []
|
||||||
|
for row in lines:
|
||||||
|
content_lines.append(','.join(row).rstrip(','))
|
||||||
|
return {'file_name': file.data.filename, 'data': content_lines}
|
||||||
|
|
||||||
|
|
||||||
def _get_rows(contents, raw_template):
|
def _get_rows(contents, raw_template):
|
||||||
reader = csv.DictReader(
|
reader = csv.DictReader(
|
||||||
contents.split('\n'),
|
contents.split('\n'),
|
||||||
lineterminator='\n',
|
|
||||||
quoting=csv.QUOTE_NONE,
|
quoting=csv.QUOTE_NONE,
|
||||||
skipinitialspace=True
|
skipinitialspace=True
|
||||||
)
|
)
|
||||||
@@ -203,12 +235,11 @@ def _get_rows(contents, raw_template):
|
|||||||
for row in reader:
|
for row in reader:
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
try:
|
try:
|
||||||
recipient_column = first_column_header[raw_template['template_type']]
|
|
||||||
validate_recipient(
|
validate_recipient(
|
||||||
row[recipient_column],
|
row.get('to', ''),
|
||||||
template_type=raw_template['template_type']
|
template_type=raw_template['template_type']
|
||||||
)
|
)
|
||||||
Template(raw_template, values=row, drop_values={recipient_column}).replaced
|
Template(raw_template, values=row, drop_values={'to'}).replaced
|
||||||
except (InvalidEmailError, InvalidPhoneError, NeededByTemplateError, NoPlaceholderForDataError):
|
except (InvalidEmailError, InvalidPhoneError, NeededByTemplateError, NoPlaceholderForDataError):
|
||||||
valid = False
|
valid = False
|
||||||
return {"valid": valid, "rows": rows}
|
return {"valid": valid, "rows": rows}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
from notifications_python_client.base import BaseAPIClient
|
||||||
|
|
||||||
|
|
||||||
|
class InviteApiClient(BaseAPIClient):
|
||||||
|
def __init__(self, base_url=None, client_id=None, secret=None):
|
||||||
|
super(self.__class__, self).__init__(base_url=base_url or 'base_url',
|
||||||
|
client_id=client_id or 'client_id',
|
||||||
|
secret=secret or 'secret')
|
||||||
|
|
||||||
|
def init_app(self, app):
|
||||||
|
self.base_url = app.config['API_HOST_NAME']
|
||||||
|
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
|
||||||
|
self.secret = app.config['ADMIN_CLIENT_SECRET']
|
||||||
|
|
||||||
|
def create_invite(self, invite_from_id, service_id, email_address, permissions):
|
||||||
|
data = {
|
||||||
|
'service': str(service_id),
|
||||||
|
'email_address': email_address,
|
||||||
|
'from_user': invite_from_id,
|
||||||
|
'permissions': permissions
|
||||||
|
}
|
||||||
|
resp = self.post(url='/service/{}/invite'.format(service_id), data=data)
|
||||||
|
return resp['data']
|
||||||
|
|
||||||
|
def get_invites_for_service(self, service_id):
|
||||||
|
endpoint = '/service/{}/invite'.format(service_id)
|
||||||
|
resp = self.get(endpoint)
|
||||||
|
return resp['data']
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import uuid
|
|
||||||
|
|
||||||
from notifications_python_client.base import BaseAPIClient
|
from notifications_python_client.base import BaseAPIClient
|
||||||
|
|
||||||
@@ -23,7 +22,6 @@ class JobApiClient(BaseAPIClient):
|
|||||||
def create_job(self, job_id, service_id, template_id, original_file_name, notification_count):
|
def create_job(self, job_id, service_id, template_id, original_file_name, notification_count):
|
||||||
data = {
|
data = {
|
||||||
"id": job_id,
|
"id": job_id,
|
||||||
"service": service_id,
|
|
||||||
"template": template_id,
|
"template": template_id,
|
||||||
"original_file_name": original_file_name,
|
"original_file_name": original_file_name,
|
||||||
"bucket_name": "service-{}-notify".format(service_id),
|
"bucket_name": "service-{}-notify".format(service_id),
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from notifications_python_client.base import BaseAPIClient
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionApiClient(BaseAPIClient):
|
||||||
|
def __init__(self, base_url=None, client_id=None, secret=None):
|
||||||
|
super(self.__class__, self).__init__(base_url=base_url or 'base_url',
|
||||||
|
client_id=client_id or 'client_id',
|
||||||
|
secret=secret or 'secret')
|
||||||
|
|
||||||
|
def init_app(self, app):
|
||||||
|
self.base_url = app.config['API_HOST_NAME']
|
||||||
|
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
|
||||||
|
self.secret = app.config['ADMIN_CLIENT_SECRET']
|
||||||
|
|
||||||
|
def delete_permission(self, permission_id):
|
||||||
|
return self.delete(url='/permission/{}'.format(permission_id))['data']
|
||||||
|
|
||||||
|
def create_permission(self, permission, user_id, service_id):
|
||||||
|
return self.post(
|
||||||
|
url='/permission',
|
||||||
|
data={'permission': permission,
|
||||||
|
'user': user_id,
|
||||||
|
'service': service_id})['data']
|
||||||
@@ -94,7 +94,7 @@ class User(UserMixin):
|
|||||||
self._email_address = fields.get('email_address')
|
self._email_address = fields.get('email_address')
|
||||||
self._mobile_number = fields.get('mobile_number')
|
self._mobile_number = fields.get('mobile_number')
|
||||||
self._password_changed_at = fields.get('password_changed_at')
|
self._password_changed_at = fields.get('password_changed_at')
|
||||||
self._permissions = set(fields.get('permissions')) if fields.get('permission') is not None else set()
|
self._permissions = fields.get('permissions')
|
||||||
self._failed_login_count = 0
|
self._failed_login_count = 0
|
||||||
self._state = fields.get('state')
|
self._state = fields.get('state')
|
||||||
self.max_failed_login_count = max_failed_login_count
|
self.max_failed_login_count = max_failed_login_count
|
||||||
@@ -165,18 +165,12 @@ class User(UserMixin):
|
|||||||
|
|
||||||
@permissions.setter
|
@permissions.setter
|
||||||
def permissions(self, permissions):
|
def permissions(self, permissions):
|
||||||
if permissions is None:
|
raise AttributeError("Read only property")
|
||||||
permissions = set()
|
|
||||||
self._permissions = set(permissions)
|
|
||||||
|
|
||||||
def add_permissions(self, permissions):
|
def has_permissions(self, service_id, permissions):
|
||||||
self._permissions.update(permissions)
|
if service_id in self._permissions:
|
||||||
|
return set(self._permissions[service_id]) > set(permissions)
|
||||||
def remove_permissions(self, permissions):
|
return False
|
||||||
self._permissions -= permissions
|
|
||||||
|
|
||||||
def has_permissions(self, permissions):
|
|
||||||
return self._permissions > set(permissions)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def failed_login_count(self):
|
def failed_login_count(self):
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
</legend>
|
</legend>
|
||||||
<div class='yes-no-fields inline'>
|
<div class='yes-no-fields inline'>
|
||||||
<label class='block-label'>
|
<label class='block-label'>
|
||||||
<input type='radio' name='{{ name }}' {% if current_value == True %}checked{% endif %} />
|
<input type='radio' name='{{ name }}' value='yes' {% if current_value == True %}checked{% endif %} />
|
||||||
Yes
|
Yes
|
||||||
</label>
|
</label>
|
||||||
<label class='block-label'>
|
<label class='block-label'>
|
||||||
<input type='radio' name='{{ name }}' {% if current_value == False %}checked{% endif %} />
|
<input type='radio' name='{{ name }}' value='no' {% if current_value == False %}checked{% endif %} />
|
||||||
No
|
No
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,16 +2,23 @@
|
|||||||
<h2 class="navigation-service-name">
|
<h2 class="navigation-service-name">
|
||||||
<a href="{{ url_for('.service_dashboard', service_id=service_id) }}">{{ session.get('service_name', 'Service') }}</a>
|
<a href="{{ url_for('.service_dashboard', service_id=service_id) }}">{{ session.get('service_name', 'Service') }}</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
{% if current_user.has_permissions(session.get('service_id', ''), ['send_messages']) %}
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ url_for('.choose_template', service_id=service_id, template_type='sms') }}">Send text messages</a></li>
|
<li><a href="{{ url_for('.choose_template', service_id=service_id, template_type='sms') }}">Send text messages</a></li>
|
||||||
<li><a href="{{ url_for('.choose_template', service_id=service_id, template_type='email') }}">Send emails</a></li>
|
<li><a href="{{ url_for('.choose_template', service_id=service_id, template_type='email') }}">Send emails</a></li>
|
||||||
|
<li><a href="{{ url_for('.letters_stub', service_id=service_id) }}">Send letters</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.has_permissions(session.get('service_id', ''), ['manage_service']) %}
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage team</a></li>
|
<li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage team</a></li>
|
||||||
<li><a href="{{ url_for('.service_settings', service_id=service_id) }}">Manage settings</a></li>
|
<li><a href="{{ url_for('.service_settings', service_id=service_id) }}">Manage settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.has_permissions(session.get('service_id', ''), ['manage_api_keys']) %}
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys</a></li>
|
<li><a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys</a></li>
|
||||||
<li><a href="{{ url_for('.documentation', service_id=service_id) }}">Developer documentation</a></li>
|
<li><a href="{{ url_for('.documentation', service_id=service_id) }}">Developer documentation</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
{% extends "withoutnav_template.html" %}
|
|
||||||
{% from "components/textbox.html" import textbox %}
|
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
|
||||||
{% from "components/sms-message.html" import sms_message %}
|
|
||||||
{% from "components/email-message.html" import email_message %}
|
|
||||||
|
|
||||||
{% block page_title %}
|
|
||||||
Preview your service name – GOV.UK Notify
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block maincolumn_content %}
|
|
||||||
|
|
||||||
<h1 class="heading-large">
|
|
||||||
Preview your service name
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="grid-row">
|
|
||||||
<div class="column-two-thirds">
|
|
||||||
{{ sms_message(
|
|
||||||
"{}: we received your payment, thank you".format(service_name),
|
|
||||||
name="Text message",
|
|
||||||
recipient='Sent from 40604'
|
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid-row">
|
|
||||||
<div class='column-two-thirds'>
|
|
||||||
{{ email_message(
|
|
||||||
subject="We received your payment, thank you",
|
|
||||||
body="Dear Alice Smith,\n\nThank you for…",
|
|
||||||
from_name=service_name,
|
|
||||||
from_address=from_address,
|
|
||||||
name="Email",
|
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{{page_footer('Looks good', back_link=url_for(".add_service"))}}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@@ -16,16 +16,29 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Be specific. Remember that there might be other people in your
|
Be specific to your service. Remember that there might be
|
||||||
organisation using GOV.UK Notify.
|
other people in your organisation using GOV.UK Notify.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Users will see this:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul class="list-bullet bottom-gutter">
|
||||||
|
<li>
|
||||||
|
at the start of every text message, eg ‘Vehicle tax: we received your
|
||||||
|
payment, thank you’
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
as your email sender name
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<form autocomplete="off" method="post">
|
<form autocomplete="off" method="post">
|
||||||
|
|
||||||
{{ textbox(form.name, hint="You can change this later") }}
|
{{ textbox(form.name, hint="You can change this later") }}
|
||||||
|
|
||||||
|
{{ page_footer('Add service') }}
|
||||||
{{ page_footer('Continue') }}
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
{% extends "withnav_template.html" %}
|
|
||||||
|
|
||||||
{% block page_title %}
|
|
||||||
Send email – GOV.UK Notify
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block maincolumn_content %}
|
|
||||||
|
|
||||||
<h1 class="heading-large">Send email</h1>
|
|
||||||
|
|
||||||
<p>This page will be where we check the email messages we're about to send</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a class="button" href="dashboard" role="button">Send email messages</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "withnav_template.html" %}
|
{% extends "withnav_template.html" %}
|
||||||
|
{% from "components/email-message.html" import email_message %}
|
||||||
{% from "components/sms-message.html" import sms_message %}
|
{% from "components/sms-message.html" import sms_message %}
|
||||||
{% from "components/table.html" import list_table, field %}
|
{% from "components/table.html" import list_table, field %}
|
||||||
{% from "components/placeholder.html" import placeholder %}
|
{% from "components/placeholder.html" import placeholder %}
|
||||||
@@ -6,7 +7,7 @@
|
|||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
Check and confirm – GOV.UK Notify
|
{{ "Check and confirm" if upload_result.valid else page_heading }} – GOV.UK Notify
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block maincolumn_content %}
|
{% block maincolumn_content %}
|
||||||
@@ -25,18 +26,25 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h1 class="heading-large">
|
<h1 class="heading-large">
|
||||||
{{ "Check and confirm" if upload_result.valid else "Send text messages" }}
|
{{ "Check and confirm" if upload_result.valid else page_heading }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="grid-row">
|
{% if 'email' == template.template_type %}
|
||||||
<div class="column-two-thirds">
|
{{ email_message(
|
||||||
{% if template.missing_data or template.additional_data %}
|
template.subject,
|
||||||
{{ sms_message(template.formatted_as_markup)}}
|
template.replaced if upload_result.valid else template.formatted_as_markup,
|
||||||
{% else %}
|
from_address='{}@notifications.service.gov.uk'.format(service.email_from),
|
||||||
{{ sms_message(template.replaced)}}
|
from_name=service.name
|
||||||
{% endif %}
|
)}}
|
||||||
|
{% elif 'sms' == template.template_type %}
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="column-two-thirds">
|
||||||
|
{{ sms_message(
|
||||||
|
template.replaced if upload_result.valid else template.formatted_as_markup
|
||||||
|
)}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
|
|
||||||
{% if upload_result.valid %}
|
{% if upload_result.valid %}
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
@@ -53,13 +61,13 @@
|
|||||||
caption=original_file_name,
|
caption=original_file_name,
|
||||||
field_headings=column_headers
|
field_headings=column_headers
|
||||||
) %}
|
) %}
|
||||||
{% if item.phone|valid_phone_number %}
|
{% if item.to or ''|valid_phone_number %}
|
||||||
{% call field() %}
|
{% call field() %}
|
||||||
{{ item.phone }}
|
{{ item.to }}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% call field(status='missing') %}
|
{% call field(status='missing') %}
|
||||||
{{ item.phone }}
|
{{ item.to }}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for column in template.placeholders %}
|
{% for column in template.placeholders %}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{% extends "withnav_template.html" %}
|
|
||||||
{% from "components/email-message.html" import email_message %}
|
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
|
||||||
{% from "components/textbox.html" import textbox %}
|
|
||||||
|
|
||||||
{% block page_title %}
|
|
||||||
Send emails – GOV.UK Notify
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block maincolumn_content %}
|
|
||||||
|
|
||||||
<h1 class="heading-large">Send emails</h1>
|
|
||||||
|
|
||||||
<form method="POST" enctype="multipart/form-data">
|
|
||||||
|
|
||||||
{% if templates %}
|
|
||||||
<div class="grid-row">
|
|
||||||
{% for template in templates %}
|
|
||||||
<div class="column-two-thirds">
|
|
||||||
{{ email_message(template.subject, template.formatted_as_markup, name=template.name) }}
|
|
||||||
</div>
|
|
||||||
<div class="column-one-third">
|
|
||||||
<div class="sms-message-use-links">
|
|
||||||
<a href="{{ url_for(".edit_service_template", service_id=service_id, template_id=template.id) }}">Edit template</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="{{ url_for('.add_service_template', service_id=service_id, template_type='email') }}" class="button">Add a new template</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
+13
-4
@@ -1,15 +1,16 @@
|
|||||||
{% extends "withnav_template.html" %}
|
{% extends "withnav_template.html" %}
|
||||||
|
{% from "components/email-message.html" import email_message %}
|
||||||
{% from "components/sms-message.html" import sms_message %}
|
{% from "components/sms-message.html" import sms_message %}
|
||||||
{% 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 %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
Send text messages – GOV.UK Notify
|
{{ page_heading }} – GOV.UK Notify
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block maincolumn_content %}
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
<h1 class="heading-large">Send text messages</h1>
|
<h1 class="heading-large">{{ page_heading }}</h1>
|
||||||
|
|
||||||
<form method="POST" enctype="multipart/form-data">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
|
||||||
@@ -26,7 +27,15 @@
|
|||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
{% for template in templates %}
|
{% for template in templates %}
|
||||||
<div class="column-two-thirds">
|
<div class="column-two-thirds">
|
||||||
{{ sms_message(template.formatted_as_markup, name=template.name) }}
|
{% if 'email' == template_type %}
|
||||||
|
{{ email_message(
|
||||||
|
template.subject,
|
||||||
|
template.formatted_as_markup,
|
||||||
|
name=template.name
|
||||||
|
) }}
|
||||||
|
{% elif 'sms' == template_type %}
|
||||||
|
{{ sms_message(template.formatted_as_markup, name=template.name) }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="column-one-third">
|
<div class="column-one-third">
|
||||||
<div class="sms-message-use-links">
|
<div class="sms-message-use-links">
|
||||||
@@ -40,7 +49,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ url_for('.add_service_template', service_id=service_id, template_type='sms') }}" class="button">Add a new template</a>
|
<a href="{{ url_for('.add_service_template', service_id=service_id, template_type=template_type) }}" class="button">Add a new template</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
<h2 class="heading-medium">API endpoint</h2>
|
<h2 class="heading-medium">API endpoint</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
https://www.notify.works/api/endpoint
|
https://api.notify.works
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ Manage users – GOV.UK Notify
|
|||||||
<legend class='heading-small'>
|
<legend class='heading-small'>
|
||||||
Permissions
|
Permissions
|
||||||
</legend>
|
</legend>
|
||||||
|
<span class="form-hint">All team members can see message history</span>
|
||||||
{{ yes_no('send_messages', 'Send messages', user.permission_send_messages) }}
|
{{ yes_no('send_messages', 'Send messages', user.permission_send_messages) }}
|
||||||
{{ yes_no('manage_service', 'Manage service', user.permission_manage_service) }}
|
{{ yes_no('manage_service', 'Manage service', user.permission_manage_service) }}
|
||||||
{{ yes_no('manage_api_keys', 'Manage API keys', user.permission_manage_api_keys) }}
|
{{ yes_no('manage_api_keys', 'Manage API keys', user.permission_manage_api_keys) }}
|
||||||
|
|||||||
@@ -14,13 +14,22 @@
|
|||||||
{{ uploaded_file_name }}
|
{{ uploaded_file_name }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="grid-row">
|
{% if 'sms' == template.template_type %}
|
||||||
<div class="column-two-thirds">
|
<div class="grid-row">
|
||||||
{{ sms_message(
|
<div class="column-two-thirds">
|
||||||
template.formatted_as_markup,
|
{{ sms_message(
|
||||||
)}}
|
template,
|
||||||
|
)}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% elif 'email' == template.template_type %}
|
||||||
|
{{ email_message(
|
||||||
|
template.subject,
|
||||||
|
template,
|
||||||
|
from_address='{}@notifications.service.gov.uk'.format(service.email_from),
|
||||||
|
from_name=service.name
|
||||||
|
)}}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p class='heading-small'>
|
<p class='heading-small'>
|
||||||
Started {{ uploaded_file_time|format_datetime }}
|
Started {{ uploaded_file_time|format_datetime }}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends "withnav_template.html" %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
Send letters – GOV.UK Notify
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
<h1 class="heading-large">
|
||||||
|
Send letters
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This page is where you would go to send letters.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -41,7 +41,7 @@ Manage users – GOV.UK Notify
|
|||||||
invited_users, caption='Invited', **table_options
|
invited_users, caption='Invited', **table_options
|
||||||
) %}
|
) %}
|
||||||
{% call field() %}
|
{% call field() %}
|
||||||
{{ item.email_localpart }}
|
{{ item.email_address }}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{{ boolean_field(item.permission_send_messages) }}
|
{{ boolean_field(item.permission_send_messages) }}
|
||||||
{{ boolean_field(item.permission_manage_service) }}
|
{{ boolean_field(item.permission_manage_service) }}
|
||||||
|
|||||||
@@ -13,31 +13,42 @@
|
|||||||
|
|
||||||
<h1 class="heading-large">Add recipients</h1>
|
<h1 class="heading-large">Add recipients</h1>
|
||||||
|
|
||||||
|
{% if 'sms' == template.template_type %}
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="column-two-thirds">
|
||||||
|
{{ sms_message(template.formatted_as_markup) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% elif 'email' == template.template_type %}
|
||||||
|
{{ email_message(
|
||||||
|
template.subject,
|
||||||
|
template.formatted_as_markup,
|
||||||
|
from_address='{}@notifications.service.gov.uk'.format(service.email_from),
|
||||||
|
from_name=service.name
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
<div class="column-two-thirds">
|
<div class="column-two-thirds">
|
||||||
{% if 'sms' == template.template_type %}
|
<p>
|
||||||
{{ sms_message(template.formatted_as_markup) }}
|
Add recipients by uploading a .csv file with
|
||||||
{% elif 'email' == template.template_type %}
|
{{ template.placeholders|length + 1 }}
|
||||||
{{ email_message(
|
{% if template.placeholders %}
|
||||||
template.subject,
|
columns:
|
||||||
template.formatted_as_markup,
|
{% else %}
|
||||||
from_address='{}@notifications.service.gov.uk'.format(service.email_from),
|
column:
|
||||||
from_name=service.name
|
{% endif %}
|
||||||
) }}
|
</p>
|
||||||
{% endif %}
|
<p class="bottom-gutter-2-3">
|
||||||
{{ banner(
|
<span class='placeholder'>to</span>
|
||||||
'You can upload real data, but we’ll only send to your mobile number until you <a href="{}">request to go live</a>'.format(
|
{{ template.placeholders_as_markup|join(" ") }}
|
||||||
url_for('.service_request_to_go_live', service_id=service_id)
|
</p>
|
||||||
)|safe,
|
<p>
|
||||||
'info'
|
<a href="{{ url_for('.get_example_csv', service_id=service_id, template_id=template.id) }}">Download an example</a>
|
||||||
)}}
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{file_upload(form.file, button_text='Upload a CSV file')}}
|
{{file_upload(form.file, button_text='Upload your CSV file')}}
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="{{ url_for('.get_example_csv', service_id=service_id, template_id=template.id) }}">Download an example CSV file</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+5
-3
@@ -1,7 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import abort
|
from flask import (abort, session)
|
||||||
|
|
||||||
|
|
||||||
class BrowsableItem(object):
|
class BrowsableItem(object):
|
||||||
@@ -100,8 +100,10 @@ def user_has_permissions(*permissions):
|
|||||||
def wrap_func(*args, **kwargs):
|
def wrap_func(*args, **kwargs):
|
||||||
# We are making the assumption that the user is logged in.
|
# We are making the assumption that the user is logged in.
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
if set(permissions) > set(current_user.permissions):
|
service_id = session.get('service_id', '')
|
||||||
|
if current_user and current_user.has_permissions(service_id, permissions):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
else:
|
||||||
abort(403)
|
abort(403)
|
||||||
return func(*args, **kwargs)
|
|
||||||
return wrap_func
|
return wrap_func
|
||||||
return wrap
|
return wrap
|
||||||
|
|||||||
+1
-2
@@ -29,9 +29,8 @@
|
|||||||
"gulp-base64": "0.1.3",
|
"gulp-base64": "0.1.3",
|
||||||
"gulp-concat": "2.6.0",
|
"gulp-concat": "2.6.0",
|
||||||
"gulp-include": "2.1.0",
|
"gulp-include": "2.1.0",
|
||||||
"gulp-jquery": "1.1.1",
|
|
||||||
"gulp-load-plugins": "1.1.0",
|
"gulp-load-plugins": "1.1.0",
|
||||||
"gulp-sass": "2.1.1",
|
"gulp-sass": "2.2.0",
|
||||||
"gulp-uglify": "1.5.1",
|
"gulp-uglify": "1.5.1",
|
||||||
"jquery": "1.11.2",
|
"jquery": "1.11.2",
|
||||||
"query-command-supported": "1.0.0"
|
"query-command-supported": "1.0.0"
|
||||||
|
|||||||
+1
-1
@@ -14,4 +14,4 @@ Pygments==2.0.2
|
|||||||
|
|
||||||
git+https://github.com/alphagov/notifications-python-client.git@0.2.8#egg=notifications-python-client==0.2.8
|
git+https://github.com/alphagov/notifications-python-client.git@0.2.8#egg=notifications-python-client==0.2.8
|
||||||
|
|
||||||
git+https://github.com/alphagov/notifications-utils.git@0.1.1#egg=notifications-utils==0.1.1
|
git+https://github.com/alphagov/notifications-utils.git@0.1.2#egg=notifications-utils==0.1.2
|
||||||
|
|||||||
+12
-2
@@ -47,6 +47,15 @@ def api_key_json(id_, name, expiry_date=None):
|
|||||||
'expiry_date': expiry_date
|
'expiry_date': expiry_date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def invite_json(id, from_user, service_id, email_address):
|
||||||
|
return {'id': id,
|
||||||
|
'from_user': from_user,
|
||||||
|
'service': service_id,
|
||||||
|
'email_address': email_address,
|
||||||
|
'status': 'pending'}
|
||||||
|
|
||||||
|
|
||||||
TEST_USER_EMAIL = 'test@user.gov.uk'
|
TEST_USER_EMAIL = 'test@user.gov.uk'
|
||||||
|
|
||||||
|
|
||||||
@@ -57,14 +66,15 @@ def create_test_user(state):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def create_test_api_user(state):
|
def create_test_api_user(state, permissions={}):
|
||||||
from app.notify_client.user_api_client import User
|
from app.notify_client.user_api_client import User
|
||||||
user_data = {'id': 1,
|
user_data = {'id': 1,
|
||||||
'name': 'Test User',
|
'name': 'Test User',
|
||||||
'password': 'somepassword',
|
'password': 'somepassword',
|
||||||
'email_address': TEST_USER_EMAIL,
|
'email_address': TEST_USER_EMAIL,
|
||||||
'mobile_number': '+441234123412',
|
'mobile_number': '+441234123412',
|
||||||
'state': state
|
'state': state,
|
||||||
|
'permissions': permissions
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ def test_client_creates_job_data_correctly(mocker):
|
|||||||
|
|
||||||
expected_data = {
|
expected_data = {
|
||||||
"id": job_id,
|
"id": job_id,
|
||||||
"service": service_id,
|
|
||||||
"template": template_id,
|
"template": template_id,
|
||||||
"original_file_name": original_file_name,
|
"original_file_name": original_file_name,
|
||||||
"bucket_name": "service-{}-notify".format(service_id),
|
"bucket_name": "service-{}-notify".format(service_id),
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ def test_form_should_have_errors_when_duplicate_service_is_added(app_):
|
|||||||
form = AddServiceForm(_get_form_names,
|
form = AddServiceForm(_get_form_names,
|
||||||
formdata=MultiDict([('name', 'some service')]))
|
formdata=MultiDict([('name', 'some service')]))
|
||||||
form.validate()
|
form.validate()
|
||||||
assert {'name': ['Service name already exists']} == form.errors
|
assert {'name': ['This service name is already in use']} == form.errors
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ def test_get_should_render_add_service_template(app_,
|
|||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
response = client.get(url_for('main.add_service'))
|
response = client.get(url_for('main.add_service'))
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'Add a new service' in response.get_data(as_text=True)
|
assert 'Which service do you want to set up notifications for?' in response.get_data(as_text=True)
|
||||||
|
|
||||||
|
|
||||||
def test_should_add_service_and_redirect_to_next_page(app_,
|
def test_should_add_service_and_redirect_to_next_page(app_,
|
||||||
@@ -31,48 +31,8 @@ def test_should_add_service_and_redirect_to_next_page(app_,
|
|||||||
url_for('main.add_service'),
|
url_for('main.add_service'),
|
||||||
data={'name': 'testing the post'})
|
data={'name': 'testing the post'})
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.location == url_for('main.add_from_address', _external=True)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_confirm_add_service(
|
|
||||||
app_,
|
|
||||||
mock_login,
|
|
||||||
mock_get_services,
|
|
||||||
api_user_active,
|
|
||||||
mock_get_user,
|
|
||||||
mock_get_user_by_email
|
|
||||||
):
|
|
||||||
with app_.test_request_context():
|
|
||||||
with app_.test_client() as client:
|
|
||||||
client.login(api_user_active)
|
|
||||||
with client.session_transaction() as session:
|
|
||||||
session['service_name'] = 'Renew Your Pet Passport'
|
|
||||||
response = client.get(url_for('main.add_from_address'))
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert 'Preview your service name' in response.get_data(as_text=True)
|
|
||||||
assert 'Renew Your Pet Passport' in response.get_data(as_text=True)
|
|
||||||
assert 'renew.your.pet.passport@notifications.service.gov.uk' in response.get_data(as_text=True)
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_add_service_after_confirmation(
|
|
||||||
app_,
|
|
||||||
mock_login,
|
|
||||||
mock_create_service,
|
|
||||||
mock_get_services,
|
|
||||||
api_user_active,
|
|
||||||
mock_get_user,
|
|
||||||
mock_get_user_by_email
|
|
||||||
):
|
|
||||||
with app_.test_request_context():
|
|
||||||
with app_.test_client() as client:
|
|
||||||
client.login(api_user_active)
|
|
||||||
with client.session_transaction() as session:
|
|
||||||
session['service_name'] = 'Renew Your Pet Passport'
|
|
||||||
response = client.post(url_for('main.add_from_address'))
|
|
||||||
assert response.status_code == 302
|
|
||||||
assert response.location == url_for('main.service_dashboard', service_id=101, _external=True)
|
assert response.location == url_for('main.service_dashboard', service_id=101, _external=True)
|
||||||
assert mock_create_service.called
|
assert mock_create_service.called
|
||||||
assert mock_get_services.called
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_return_form_errors_when_service_name_is_empty(app_,
|
def test_should_return_form_errors_when_service_name_is_empty(app_,
|
||||||
@@ -87,7 +47,7 @@ def test_should_return_form_errors_when_service_name_is_empty(app_,
|
|||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
response = client.post(url_for('main.add_service'), data={})
|
response = client.post(url_for('main.add_service'), data={})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'Service name can not be empty' in response.get_data(as_text=True)
|
assert 'Service name can’t be empty' in response.get_data(as_text=True)
|
||||||
|
|
||||||
|
|
||||||
def test_should_return_form_errors_with_duplicate_service_name(app_,
|
def test_should_return_form_errors_with_duplicate_service_name(app_,
|
||||||
@@ -102,5 +62,5 @@ def test_should_return_form_errors_with_duplicate_service_name(app_,
|
|||||||
response = client.post(
|
response = client.post(
|
||||||
url_for('main.add_service'), data={'name': 'service_one'})
|
url_for('main.add_service'), data={'name': 'service_one'})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'Service name already exists' in response.get_data(as_text=True)
|
assert 'This service name is already in use' in response.get_data(as_text=True)
|
||||||
assert mock_get_services.called
|
assert mock_get_services.called
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ def test_should_show_empty_api_keys_page(app_,
|
|||||||
mock_get_user,
|
mock_get_user,
|
||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_get_no_api_keys,
|
mock_get_no_api_keys,
|
||||||
mock_login):
|
mock_login,
|
||||||
|
mock_has_permissions):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
@@ -39,7 +40,8 @@ def test_should_show_api_keys_page(app_,
|
|||||||
mock_get_user,
|
mock_get_user,
|
||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_get_api_keys,
|
mock_get_api_keys,
|
||||||
mock_login):
|
mock_login,
|
||||||
|
mock_has_permissions):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
@@ -58,7 +60,8 @@ def test_should_show_name_api_key_page(app_,
|
|||||||
mock_get_user,
|
mock_get_user,
|
||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_get_api_keys,
|
mock_get_api_keys,
|
||||||
mock_login):
|
mock_login,
|
||||||
|
mock_has_permissions):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
@@ -74,7 +77,8 @@ def test_should_render_show_api_key(app_,
|
|||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_create_api_key,
|
mock_create_api_key,
|
||||||
mock_get_api_keys,
|
mock_get_api_keys,
|
||||||
mock_login):
|
mock_login,
|
||||||
|
mock_has_permissions):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
@@ -92,7 +96,8 @@ def test_should_show_confirm_revoke_api_key(app_,
|
|||||||
mock_get_user,
|
mock_get_user,
|
||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_get_api_keys,
|
mock_get_api_keys,
|
||||||
mock_login):
|
mock_login,
|
||||||
|
mock_has_permissions):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
@@ -110,7 +115,8 @@ def test_should_redirect_after_revoking_api_key(app_,
|
|||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_revoke_api_key,
|
mock_revoke_api_key,
|
||||||
mock_get_api_keys,
|
mock_get_api_keys,
|
||||||
mock_login):
|
mock_login,
|
||||||
|
mock_has_permissions):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ def test_should_show_page_for_one_job(app_,
|
|||||||
mock_login,
|
mock_login,
|
||||||
mock_get_user,
|
mock_get_user,
|
||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
|
mock_get_service,
|
||||||
mock_get_service_template,
|
mock_get_service_template,
|
||||||
job_data,
|
job_data,
|
||||||
mock_get_job):
|
mock_get_job):
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import json
|
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
def test_should_show_overview_page(
|
def test_should_show_overview_page(
|
||||||
app_,
|
app_,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
mock_get_users_by_service
|
mock_get_users_by_service,
|
||||||
|
mock_get_invites_for_service
|
||||||
):
|
):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
@@ -38,7 +40,8 @@ def test_redirect_after_saving_user(
|
|||||||
api_user_active,
|
api_user_active,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
mock_get_users_by_service
|
mock_get_users_by_service,
|
||||||
|
mock_get_invites_for_service
|
||||||
):
|
):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
@@ -70,19 +73,34 @@ def test_should_show_page_for_inviting_user(
|
|||||||
|
|
||||||
def test_invite_user(
|
def test_invite_user(
|
||||||
app_,
|
app_,
|
||||||
|
service_one,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_service,
|
mock_get_users_by_service,
|
||||||
mock_get_users_by_service
|
mock_create_invite,
|
||||||
|
mock_get_invites_for_service
|
||||||
):
|
):
|
||||||
|
from_user = api_user_active.id
|
||||||
|
service_id = service_one['id']
|
||||||
|
email_address = 'test@example.gov.uk'
|
||||||
|
permissions = 'send_messages,manage_service,manage_api_keys'
|
||||||
|
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for('main.invite_user', service_id=55555),
|
url_for('main.invite_user', service_id=service_id),
|
||||||
data={'email_address': 'test@example.gov.uk'},
|
data={'email_address': email_address,
|
||||||
|
'send_messages': 'yes',
|
||||||
|
'manage_service': 'yes',
|
||||||
|
'manage_api_keys': 'yes'},
|
||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'Invite sent to test@example.gov.uk' in response.get_data(as_text=True)
|
mock_create_invite.assert_called_with(from_user, service_id, email_address, permissions)
|
||||||
|
mock_get_invites_for_service.assert_called_with(service_id=service_id)
|
||||||
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
|
assert page.h1.string.strip() == 'Manage team'
|
||||||
|
flash_banner = page.find('div', class_='banner-default-with-tick').string.strip()
|
||||||
|
assert flash_banner == 'Invite sent to test@example.gov.uk'
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
from unittest.mock import ANY
|
||||||
import pytest
|
|
||||||
import moto
|
|
||||||
|
|
||||||
template_types = ['email', 'sms']
|
template_types = ['email', 'sms']
|
||||||
|
|
||||||
@@ -57,17 +57,19 @@ def test_upload_empty_csvfile_returns_to_upload_page(
|
|||||||
assert 'The file emtpy.csv contained no data' in content
|
assert 'The file emtpy.csv contained no data' in content
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(True, reason='Errors on travis')
|
|
||||||
@moto.mock_s3
|
|
||||||
def test_upload_csvfile_with_invalid_phone_shows_check_page_with_errors(
|
def test_upload_csvfile_with_invalid_phone_shows_check_page_with_errors(
|
||||||
app_,
|
app_,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
|
mocker,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_service_template
|
mock_get_service,
|
||||||
|
mock_get_service_template,
|
||||||
|
mock_s3_upload
|
||||||
):
|
):
|
||||||
|
|
||||||
contents = 'phone\n+44 123\n+44 456'
|
contents = 'to,name\n+44 123,test1\n+44 456,test2'
|
||||||
file_data = (BytesIO(contents.encode('utf-8')), 'invalid.csv')
|
file_data = (BytesIO(contents.encode('utf-8')), 'invalid.csv')
|
||||||
|
mocker.patch('app.main.views.send.s3download', return_value=contents)
|
||||||
|
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
@@ -86,18 +88,51 @@ def test_upload_csvfile_with_invalid_phone_shows_check_page_with_errors(
|
|||||||
assert 'Upload a CSV file' in content
|
assert 'Upload a CSV file' in content
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(True, reason='Errors on travis')
|
def test_upload_csvfile_removes_empty_lines_and_trailing_commas(
|
||||||
@moto.mock_s3
|
app_,
|
||||||
|
api_user_active,
|
||||||
|
mocker,
|
||||||
|
mock_login,
|
||||||
|
mock_get_service,
|
||||||
|
mock_get_service_template,
|
||||||
|
mock_s3_upload
|
||||||
|
):
|
||||||
|
|
||||||
|
contents = 'to,name,,,\n++44 7700 900981,test1,,,\n+44 7700 900981,test2,,,\n ,,, \n ,,, \t \t \n'
|
||||||
|
file_data = (BytesIO(contents.encode('utf-8')), 'invalid.csv')
|
||||||
|
|
||||||
|
expected_data = {'data': ['to,name', '++44 7700 900981,test1', '+44 7700 900981,test2'],
|
||||||
|
'file_name': 'invalid.csv'}
|
||||||
|
|
||||||
|
mocker.patch('app.main.views.send.s3download',
|
||||||
|
return_value='to,name\n++44 7700 900981,test1\n+44 7700 900981,test2')
|
||||||
|
|
||||||
|
with app_.test_request_context():
|
||||||
|
with app_.test_client() as client:
|
||||||
|
client.login(api_user_active)
|
||||||
|
upload_data = {'file': file_data}
|
||||||
|
response = client.post(
|
||||||
|
url_for('main.send_messages', service_id=12345, template_id=54321),
|
||||||
|
data=upload_data,
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
mock_s3_upload.assert_called_with(ANY, '12345', expected_data, 'eu-west-1')
|
||||||
|
|
||||||
|
|
||||||
def test_send_test_message_to_self(
|
def test_send_test_message_to_self(
|
||||||
app_,
|
app_,
|
||||||
mocker,
|
mocker,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_user,
|
mock_get_service,
|
||||||
mock_get_user_by_email,
|
mock_get_service_template,
|
||||||
mock_get_service_template
|
mock_s3_upload
|
||||||
):
|
):
|
||||||
|
|
||||||
|
expected_data = {'data': ['to', '+4412341234'], 'file_name': 'Test run'}
|
||||||
|
mocker.patch('app.main.views.send.s3download', return_value='to\r\n+4412341234')
|
||||||
|
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
@@ -106,20 +141,15 @@ def test_send_test_message_to_self(
|
|||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
content = response.get_data(as_text=True)
|
mock_s3_upload.assert_called_with(ANY, '12345', expected_data, 'eu-west-1')
|
||||||
assert 'Test run' in content
|
|
||||||
assert '+4412341234' in content
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(True, reason='Errors on travis')
|
|
||||||
@moto.mock_s3
|
|
||||||
def test_download_example_csv(
|
def test_download_example_csv(
|
||||||
app_,
|
app_,
|
||||||
mocker,
|
mocker,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_user,
|
mock_get_service,
|
||||||
mock_get_user_by_email,
|
|
||||||
mock_get_service_template
|
mock_get_service_template
|
||||||
):
|
):
|
||||||
|
|
||||||
@@ -131,25 +161,23 @@ def test_download_example_csv(
|
|||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.get_data(as_text=True) == 'phone\r\n+4412341234\r\n'
|
assert response.get_data(as_text=True) == 'to\r\n+4412341234\r\n'
|
||||||
assert 'text/csv' in response.headers['Content-Type']
|
assert 'text/csv' in response.headers['Content-Type']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(True, reason='Errors on travis')
|
|
||||||
@moto.mock_s3
|
|
||||||
def test_upload_csvfile_with_valid_phone_shows_all_numbers(
|
def test_upload_csvfile_with_valid_phone_shows_all_numbers(
|
||||||
app_,
|
app_,
|
||||||
mocker,
|
mocker,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_user,
|
mock_get_service,
|
||||||
mock_get_user_by_email,
|
mock_get_service_template,
|
||||||
mock_get_service_template
|
mock_s3_upload
|
||||||
):
|
):
|
||||||
|
|
||||||
contents = 'phone\n+44 7700 900981\n+44 7700 900982\n+44 7700 900983\n+44 7700 900984\n+44 7700 900985\n+44 7700 900986' # noqa
|
contents = 'to\n+44 7700 900981\n+44 7700 900982\n+44 7700 900983\n+44 7700 900984\n+44 7700 900985\n+44 7700 900986' # noqa
|
||||||
|
|
||||||
file_data = (BytesIO(contents.encode('utf-8')), 'valid.csv')
|
file_data = (BytesIO(contents.encode('utf-8')), 'valid.csv')
|
||||||
|
mocker.patch('app.main.views.send.s3download', return_value=contents)
|
||||||
|
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
@@ -174,18 +202,15 @@ def test_upload_csvfile_with_valid_phone_shows_all_numbers(
|
|||||||
assert '+44 7700 900986' in content
|
assert '+44 7700 900986' in content
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(True, reason='Errors on travis')
|
|
||||||
@moto.mock_s3
|
|
||||||
def test_create_job_should_call_api(
|
def test_create_job_should_call_api(
|
||||||
app_,
|
app_,
|
||||||
service_one,
|
service_one,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
mock_get_user,
|
|
||||||
mock_get_user_by_email,
|
|
||||||
mock_login,
|
mock_login,
|
||||||
job_data,
|
job_data,
|
||||||
mock_create_job,
|
mock_create_job,
|
||||||
mock_get_job,
|
mock_get_job,
|
||||||
|
mock_get_service,
|
||||||
mock_get_service_template
|
mock_get_service_template
|
||||||
):
|
):
|
||||||
|
|
||||||
|
|||||||
+54
-6
@@ -1,4 +1,4 @@
|
|||||||
import os
|
import uuid
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -9,7 +9,8 @@ from . import (
|
|||||||
TestClient,
|
TestClient,
|
||||||
template_json,
|
template_json,
|
||||||
api_key_json,
|
api_key_json,
|
||||||
job_json
|
job_json,
|
||||||
|
invite_json
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -204,7 +205,8 @@ def api_user_pending():
|
|||||||
'email_address': 'test@user.gov.uk',
|
'email_address': 'test@user.gov.uk',
|
||||||
'mobile_number': '+4412341234',
|
'mobile_number': '+4412341234',
|
||||||
'state': 'pending',
|
'state': 'pending',
|
||||||
'failed_login_count': 0
|
'failed_login_count': 0,
|
||||||
|
'permissions': {}
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -219,7 +221,8 @@ def api_user_active():
|
|||||||
'email_address': 'test@user.gov.uk',
|
'email_address': 'test@user.gov.uk',
|
||||||
'mobile_number': '+4412341234',
|
'mobile_number': '+4412341234',
|
||||||
'state': 'active',
|
'state': 'active',
|
||||||
'failed_login_count': 0
|
'failed_login_count': 0,
|
||||||
|
'permissions': {}
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -234,7 +237,8 @@ def api_user_locked():
|
|||||||
'email_address': 'test@user.gov.uk',
|
'email_address': 'test@user.gov.uk',
|
||||||
'mobile_number': '+4412341234',
|
'mobile_number': '+4412341234',
|
||||||
'state': 'active',
|
'state': 'active',
|
||||||
'failed_login_count': 5
|
'failed_login_count': 5,
|
||||||
|
'permissions': {}
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -249,7 +253,8 @@ def api_user_request_password_reset():
|
|||||||
'email_address': 'test@user.gov.uk',
|
'email_address': 'test@user.gov.uk',
|
||||||
'mobile_number': '+4412341234',
|
'mobile_number': '+4412341234',
|
||||||
'state': 'request_password_reset',
|
'state': 'request_password_reset',
|
||||||
'failed_login_count': 5
|
'failed_login_count': 5,
|
||||||
|
'permissions': {}
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -512,6 +517,15 @@ def mock_get_jobs(mocker):
|
|||||||
return mocker.patch('app.job_api_client.get_job', side_effect=_get_jobs)
|
return mocker.patch('app.job_api_client.get_job', side_effect=_get_jobs)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def mock_has_permissions(mocker):
|
||||||
|
def _has_permission(service_id, permissions):
|
||||||
|
return True
|
||||||
|
return mocker.patch(
|
||||||
|
'app.notify_client.user_api_client.User.has_permissions',
|
||||||
|
side_effect=_has_permission)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def mock_get_users_by_service(mocker):
|
def mock_get_users_by_service(mocker):
|
||||||
def _get_users_for_service(service_id):
|
def _get_users_for_service(service_id):
|
||||||
@@ -526,3 +540,37 @@ def mock_get_users_by_service(mocker):
|
|||||||
'failed_login_count': 0}]
|
'failed_login_count': 0}]
|
||||||
return data
|
return data
|
||||||
return mocker.patch('app.user_api_client.get_users_for_service', side_effect=_get_users_for_service, autospec=True)
|
return mocker.patch('app.user_api_client.get_users_for_service', side_effect=_get_users_for_service, autospec=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def mock_s3_upload(mocker):
|
||||||
|
def _upload(upload_id, service_id, filedata, region):
|
||||||
|
pass
|
||||||
|
return mocker.patch('app.main.views.send.s3upload', side_effect=_upload)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def mock_create_invite(mocker):
|
||||||
|
def _create_invite(from_user, service_id, email_address, permissions):
|
||||||
|
data = {'id': uuid.uuid4(),
|
||||||
|
'from_user': from_user,
|
||||||
|
'service': service_id,
|
||||||
|
'email_address': email_address,
|
||||||
|
'status': 'pending',
|
||||||
|
'permissions': permissions}
|
||||||
|
return data
|
||||||
|
return mocker.patch('app.invite_api_client.create_invite', side_effect=_create_invite)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def mock_get_invites_for_service(mocker, service_one):
|
||||||
|
def _get_invites(service_id):
|
||||||
|
data = []
|
||||||
|
from_user = service_one['users'][0]
|
||||||
|
service_id = service_one['id']
|
||||||
|
for i in range(0, 5):
|
||||||
|
email_address = 'user_{}@testnotify.gov.uk'.format(i)
|
||||||
|
invite = invite_json(uuid.uuid4(), from_user, service_id, email_address)
|
||||||
|
data.append(invite)
|
||||||
|
return data
|
||||||
|
return mocker.patch('app.invite_api_client.get_invites_for_service', side_effect=_get_invites)
|
||||||
|
|||||||
Reference in New Issue
Block a user