Merge branch 'master' of github.com:alphagov/notifications-admin

This commit is contained in:
Martyn Inglis
2016-03-01 09:57:52 +00:00
39 changed files with 493 additions and 344 deletions

View File

@@ -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.job_api_client import JobApiClient
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.asset_fingerprinter import AssetFingerprinter
from app.utils import validate_phone_number, InvalidPhoneError
@@ -30,7 +32,9 @@ user_api_client = UserApiClient()
api_key_api_client = ApiKeyApiClient()
job_api_client = JobApiClient()
status_api_client = StatusApiClient()
invite_api_client = InviteApiClient()
asset_fingerprinter = AssetFingerprinter()
permission_api_client = PermissionApiClient()
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)
job_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.login_view = 'main.sign_in'

View File

@@ -15,3 +15,7 @@
margin-bottom: $gutter;
clear: both;
}
.bottom-gutter-2-3 {
margin-bottom: $gutter * 2/3;
}

View File

@@ -164,13 +164,13 @@ class AddServiceForm(Form):
name = StringField(
'Service name',
validators=[
DataRequired(message='Service name can not be empty')
DataRequired(message='Service name cant be empty')
]
)
def validate_name(self, a):
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):

View File

@@ -11,39 +11,15 @@ from app.main.forms import AddServiceForm
@login_required
def add_service():
form = AddServiceForm(services_dao.find_all_service_names)
services = services_dao.get_services(current_user.id)
if len(services['data']) == 0:
heading = 'Which service do you want to set up notifications for?'
else:
heading = 'Add a new service'
heading = 'Which service do you want to set up notifications for?'
if form.validate_on_submit():
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:
return render_template(
'views/add-service.html',
form=form,
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())
])

View File

@@ -3,6 +3,7 @@ from flask_login import login_required
from app.main import main
from app.main.forms import CreateKeyForm
from app import api_key_api_client
from app.utils import user_has_permissions
@main.route("/services/<service_id>/documentation")
@@ -13,6 +14,7 @@ def documentation(service_id):
@main.route("/services/<service_id>/api-keys")
@login_required
@user_has_permissions('manage_api_keys')
def api_keys(service_id):
return render_template(
'views/api-keys.html',
@@ -23,6 +25,7 @@ def api_keys(service_id):
@main.route("/services/<service_id>/api-keys/create", methods=['GET', 'POST'])
@login_required
@user_has_permissions('manage_api_keys')
def create_api_key(service_id):
key_names = [
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'])
@login_required
@user_has_permissions('manage_api_keys')
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']
if request.method == 'GET':

View File

@@ -23,6 +23,7 @@ def service_dashboard(service_id):
try:
service = get_service_by_id(service_id)
session['service_name'] = service['data']['name']
session['service_id'] = service['data']['id']
except HTTPError as e:
if e.status_code == 404:
abort(404)

View File

@@ -13,6 +13,7 @@ from notification_utils.template import Template
from app import job_api_client
from app.main import main
from app.main.dao import templates_dao
from app.main.dao import services_dao
now = time.strftime('%H:%M')
@@ -37,6 +38,7 @@ def view_jobs(service_id):
@main.route("/services/<service_id>/jobs/<job_id>")
@login_required
def view_job(service_id, job_id):
service = services_dao.get_service_by_id_or_404(service_id)
try:
job = job_api_client.get_job(service_id, job_id)['data']
messages = []
@@ -56,9 +58,11 @@ def view_job(service_id, job_id):
uploaded_file_name=job['original_file_name'],
uploaded_file_time=job['created_at'],
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:
if e.status_code == 404:

View File

@@ -7,13 +7,18 @@ from flask import (
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.dao import users_dao
from app.main.forms import InviteUserForm
from app.main.dao.services_dao import get_service_by_id_or_404
from app import user_api_client
from app import invite_api_client
fake_users = [
{
@@ -29,14 +34,19 @@ fake_users = [
@main.route("/services/<service_id>/users")
@login_required
def manage_users(service_id):
users = user_api_client.get_users_for_service(service_id=service_id)
return render_template(
'views/manage-users.html',
service_id=service_id,
users=users,
current_user=current_user,
invited_users=[]
)
try:
users = user_api_client.get_users_for_service(service_id=service_id)
invited_users = invite_api_client.get_invites_for_service(service_id=service_id)
return render_template('views/manage-users.html',
service_id=service_id,
users=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'])
@@ -44,10 +54,19 @@ def manage_users(service_id):
def invite_user(service_id):
form = InviteUserForm()
if form.validate_on_submit():
flash('Invite sent to {}'.format(form.email_address.data), 'default_with_tick')
return redirect(url_for('.manage_users', service_id=service_id))
email_address = form.email_address.data
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(
'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_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)

View File

@@ -1,9 +1,6 @@
import csv
import io
import uuid
import botocore
from datetime import date
from flask import (
request,
@@ -17,7 +14,6 @@ from flask import (
)
from flask_login import login_required, current_user
from werkzeug import secure_filename
from notifications_python_client.errors import HTTPError
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.utils import validate_recipient, InvalidPhoneError, InvalidEmailError
first_column_header = {
'email': 'email',
'sms': 'phone'
page_headings = {
'email': 'Send emails',
'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'])
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']:
abort(404)
@@ -53,11 +56,17 @@ def choose_template(service_id, template_type):
else:
raise e
return render_template(
'views/choose-{}-template.html'.format(template_type),
'views/choose-template.html',
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
],
template_type=template_type,
page_heading=page_headings[template_type],
service=service,
has_jobs=len(jobs),
service_id=service_id
)
@@ -70,7 +79,7 @@ def send_messages(service_id, template_id):
form = CsvUploadForm()
if form.validate_on_submit():
try:
csv_file = form.file.data
csv_file = form.file
filedata = _get_filedata(csv_file)
upload_id = str(uuid.uuid4())
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,
upload_id=upload_id))
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))
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)
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(
'views/send.html',
template=template,
column_headers=[first_column_header[template.template_type]] + template.placeholders_as_markup,
column_headers=['to'] + template.placeholders_as_markup,
form=form,
service=service,
service_id=service_id
@@ -105,10 +115,14 @@ def get_example_csv(service_id, template_id):
placeholders = list(Template(template).placeholders)
output = io.StringIO()
writer = csv.writer(output)
writer.writerow([first_column_header[template['template_type']]] + placeholders)
writer.writerow([current_user.mobile_number] + ["test {}".format(header) for header in placeholders])
return(output.getvalue(), 200, {'Content-Type': 'text/csv; charset=utf-8'})
writer.writerow(['to'] + placeholders)
writer.writerow([
{
'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'])
@@ -118,7 +132,7 @@ def send_message_to_self(service_id, template_id):
placeholders = list(Template(template).placeholders)
output = io.StringIO()
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])
filedata = {
'file_name': 'Test run',
@@ -140,29 +154,30 @@ def check_messages(service_id, upload_id):
upload_data = session['upload_data']
template_id = upload_data.get('template_id')
service = services_dao.get_service_by_id_or_404(service_id)
if request.method == 'GET':
contents = s3download(service_id, upload_id)
if not contents:
flash('There was a problem reading your upload file')
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)
session['upload_data']['notification_count'] = len(upload_result['rows'])
template = Template(
raw_template,
values=upload_result['rows'][0] if upload_result['valid'] else {},
drop_values={recipient_type}
drop_values={'to'},
prefix=service['name']
)
return render_template(
'views/check-sms.html',
'views/check.html',
upload_result=upload_result,
template=template,
column_headers=[recipient_type] + list(
template.placeholders if upload_result['valid'] else template.placeholders_as_markup
),
page_heading=page_headings[template.template_type],
column_headers=['to'] + list(template.placeholders_as_markup),
original_file_name=upload_data.get('original_file_name'),
service_id=service_id,
service=service,
form=CsvUploadForm()
)
elif request.method == 'POST':
@@ -184,17 +199,34 @@ def check_messages(service_id, upload_id):
def _get_filedata(file):
lines = file.read().decode('utf-8').splitlines()
if len(lines) < 2: # must be at least header and one line
message = 'The file {} contained no data'.format(file.filename)
import itertools
reader = csv.reader(
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)
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):
reader = csv.DictReader(
contents.split('\n'),
lineterminator='\n',
quoting=csv.QUOTE_NONE,
skipinitialspace=True
)
@@ -203,12 +235,11 @@ def _get_rows(contents, raw_template):
for row in reader:
rows.append(row)
try:
recipient_column = first_column_header[raw_template['template_type']]
validate_recipient(
row[recipient_column],
row.get('to', ''),
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):
valid = False
return {"valid": valid, "rows": rows}

View File

@@ -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']

View File

@@ -1,4 +1,3 @@
import uuid
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):
data = {
"id": job_id,
"service": service_id,
"template": template_id,
"original_file_name": original_file_name,
"bucket_name": "service-{}-notify".format(service_id),

View File

@@ -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']

View File

@@ -94,7 +94,7 @@ class User(UserMixin):
self._email_address = fields.get('email_address')
self._mobile_number = fields.get('mobile_number')
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._state = fields.get('state')
self.max_failed_login_count = max_failed_login_count
@@ -165,18 +165,12 @@ class User(UserMixin):
@permissions.setter
def permissions(self, permissions):
if permissions is None:
permissions = set()
self._permissions = set(permissions)
raise AttributeError("Read only property")
def add_permissions(self, permissions):
self._permissions.update(permissions)
def remove_permissions(self, permissions):
self._permissions -= permissions
def has_permissions(self, permissions):
return self._permissions > set(permissions)
def has_permissions(self, service_id, permissions):
if service_id in self._permissions:
return set(self._permissions[service_id]) > set(permissions)
return False
@property
def failed_login_count(self):

View File

@@ -5,11 +5,11 @@
</legend>
<div class='yes-no-fields inline'>
<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
</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
</label>
</div>

View File

@@ -2,16 +2,23 @@
<h2 class="navigation-service-name">
<a href="{{ url_for('.service_dashboard', service_id=service_id) }}">{{ session.get('service_name', 'Service') }}</a>
</h2>
{% if current_user.has_permissions(session.get('service_id', ''), ['send_messages']) %}
<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='email') }}">Send emails</a></li>
<li><a href="{{ url_for('.letters_stub', service_id=service_id) }}">Send letters</a></li>
</ul>
{% endif %}
{% if current_user.has_permissions(session.get('service_id', ''), ['manage_service']) %}
<ul>
<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>
</ul>
{% endif %}
{% if current_user.has_permissions(session.get('service_id', ''), ['manage_api_keys']) %}
<ul>
<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>
</ul>
{% endif %}
</nav>

View File

@@ -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 %}

View File

@@ -16,16 +16,29 @@
</h1>
<p>
Be specific. Remember that there might be other people in your
organisation using GOV.UK Notify.
Be specific to your service. Remember that there might be
other people in your organisation using GOV.UK Notify.
</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">
{{ textbox(form.name, hint="You can change this later") }}
{{ page_footer('Continue') }}
{{ page_footer('Add service') }}
</form>

View File

@@ -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 %}

View File

@@ -1,4 +1,5 @@
{% extends "withnav_template.html" %}
{% from "components/email-message.html" import email_message %}
{% from "components/sms-message.html" import sms_message %}
{% from "components/table.html" import list_table, field %}
{% from "components/placeholder.html" import placeholder %}
@@ -6,7 +7,7 @@
{% from "components/page-footer.html" import page_footer %}
{% block page_title %}
Check and confirm GOV.UK Notify
{{ "Check and confirm" if upload_result.valid else page_heading }} GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
@@ -25,18 +26,25 @@
{% endif %}
<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>
<div class="grid-row">
<div class="column-two-thirds">
{% if template.missing_data or template.additional_data %}
{{ sms_message(template.formatted_as_markup)}}
{% else %}
{{ sms_message(template.replaced)}}
{% endif %}
{% if 'email' == template.template_type %}
{{ email_message(
template.subject,
template.replaced if upload_result.valid else template.formatted_as_markup,
from_address='{}@notifications.service.gov.uk'.format(service.email_from),
from_name=service.name
)}}
{% 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>
{% endif %}
{% if upload_result.valid %}
<form method="post" enctype="multipart/form-data">
@@ -53,13 +61,13 @@
caption=original_file_name,
field_headings=column_headers
) %}
{% if item.phone|valid_phone_number %}
{% if item.to or ''|valid_phone_number %}
{% call field() %}
{{ item.phone }}
{{ item.to }}
{% endcall %}
{% else %}
{% call field(status='missing') %}
{{ item.phone }}
{{ item.to }}
{% endcall %}
{% endif %}
{% for column in template.placeholders %}

View File

@@ -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 %}

View File

@@ -1,15 +1,16 @@
{% extends "withnav_template.html" %}
{% from "components/email-message.html" import email_message %}
{% from "components/sms-message.html" import sms_message %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/textbox.html" import textbox %}
{% block page_title %}
Send text messages GOV.UK Notify
{{ page_heading }} GOV.UK Notify
{% endblock %}
{% 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">
@@ -26,7 +27,15 @@
<div class="grid-row">
{% for template in templates %}
<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 class="column-one-third">
<div class="sms-message-use-links">
@@ -40,7 +49,7 @@
{% endif %}
<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>
</form>

View File

@@ -161,7 +161,7 @@
<h2 class="heading-medium">API endpoint</h2>
<p>
https://www.notify.works/api/endpoint
https://api.notify.works
</p>
<p>

View File

@@ -28,6 +28,7 @@ Manage users GOV.UK Notify
<legend class='heading-small'>
Permissions
</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('manage_service', 'Manage service', user.permission_manage_service) }}
{{ yes_no('manage_api_keys', 'Manage API keys', user.permission_manage_api_keys) }}

View File

@@ -14,13 +14,22 @@
{{ uploaded_file_name }}
</h1>
<div class="grid-row">
<div class="column-two-thirds">
{{ sms_message(
template.formatted_as_markup,
)}}
{% if 'sms' == template.template_type %}
<div class="grid-row">
<div class="column-two-thirds">
{{ sms_message(
template,
)}}
</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'>
Started {{ uploaded_file_time|format_datetime }}

View File

@@ -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 %}

View File

@@ -41,7 +41,7 @@ Manage users GOV.UK Notify
invited_users, caption='Invited', **table_options
) %}
{% call field() %}
{{ item.email_localpart }}
{{ item.email_address }}
{% endcall %}
{{ boolean_field(item.permission_send_messages) }}
{{ boolean_field(item.permission_manage_service) }}

View File

@@ -13,31 +13,42 @@
<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="column-two-thirds">
{% if 'sms' == template.template_type %}
{{ sms_message(template.formatted_as_markup) }}
{% 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 %}
{{ banner(
'You can upload real data, but well only send to your mobile number until you <a href="{}">request to go live</a>'.format(
url_for('.service_request_to_go_live', service_id=service_id)
)|safe,
'info'
)}}
<p>
Add recipients by uploading a .csv file with
{{ template.placeholders|length + 1 }}
{% if template.placeholders %}
columns:
{% else %}
column:
{% endif %}
</p>
<p class="bottom-gutter-2-3">
<span class='placeholder'>to</span>
{{ template.placeholders_as_markup|join(" ") }}
</p>
<p>
<a href="{{ url_for('.get_example_csv', service_id=service_id, template_id=template.id) }}">Download an example</a>
</p>
</div>
</div>
{{file_upload(form.file, button_text='Upload a 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>
{{file_upload(form.file, button_text='Upload your CSV file')}}
{% endblock %}

View File

@@ -1,7 +1,7 @@
import re
from functools import wraps
from flask import abort
from flask import (abort, session)
class BrowsableItem(object):
@@ -100,8 +100,10 @@ def user_has_permissions(*permissions):
def wrap_func(*args, **kwargs):
# We are making the assumption that the user is logged in.
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)
return func(*args, **kwargs)
return wrap_func
return wrap