Merge pull request #31 from alphagov/add-service

Add service
This commit is contained in:
minglis
2015-12-15 11:57:17 +00:00
11 changed files with 286 additions and 16 deletions

View File

@@ -3,4 +3,4 @@ from flask import Blueprint
main = Blueprint('main', __name__) main = Blueprint('main', __name__)
from app.main.views import index, sign_in, register, two_factor, verify, sms from app.main.views import index, sign_in, register, two_factor, verify, sms, add_service

View File

@@ -0,0 +1,41 @@
from datetime import datetime
from app import db
from app.models import Service
def insert_new_service(service_name, user):
service = Service(name=service_name,
created_at=datetime.now(),
limit=1000,
active=False,
restricted=True)
add_service(service)
service.users.append(user)
db.session.commit()
return service.id
def get_service_by_id(id):
return Service.query.get(id)
def unrestrict_service(service_id):
service = get_service_by_id(service_id)
service.restricted = False
add_service(service)
def activate_service(service_id):
service = get_service_by_id(service_id)
service.active = True
add_service(service)
def add_service(service):
db.session.add(service)
db.session.commit()
def find_service_by_service_name(service_name):
return Service.query.filter_by(name=service_name).first()

View File

@@ -5,7 +5,7 @@ from flask_wtf import Form
from wtforms import StringField, PasswordField from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Email, Length, Regexp from wtforms.validators import DataRequired, Email, Length, Regexp
from app.main.dao import verify_codes_dao from app.main.dao import verify_codes_dao, services_dao
from app.main.encryption import check_hash from app.main.encryption import check_hash
from app.main.validators import Blacklist from app.main.validators import Blacklist
@@ -82,3 +82,14 @@ def validate_code(field, code):
return True return True
else: else:
return False return False
class AddServiceForm(Form):
service_name = StringField(validators=[DataRequired(message='Please enter your service name')])
def validate_service_name(self, a):
if services_dao.find_service_by_service_name(self.service_name.data) is not None:
self.service_name.errors.append('Duplicate service name')
return False
else:
return True

View File

@@ -0,0 +1,25 @@
from flask import render_template, jsonify, redirect, session
from flask_login import login_required
from app.main import main
from app.main.dao import services_dao, users_dao
from app.main.forms import AddServiceForm
@main.route("/add-service", methods=['GET'])
@login_required
def add_service():
return render_template('views/add-service.html', form=AddServiceForm())
@main.route("/add-service", methods=['POST'])
@login_required
def process_add_service():
form = AddServiceForm()
if form.validate_on_submit():
user = users_dao.get_user_by_id(session['user_id'])
services_dao.insert_new_service(form.service_name.data, user)
return redirect('/dashboard')
else:
return jsonify(form.errors), 400

View File

@@ -35,12 +35,6 @@ def dashboard():
return render_template('views/dashboard.html') return render_template('views/dashboard.html')
@main.route("/add-service")
@login_required
def addservice():
return render_template('views/add-service.html')
@main.route("/email-not-received") @main.route("/email-not-received")
def emailnotreceived(): def emailnotreceived():
return render_template('views/email-not-received.html') return render_template('views/email-not-received.html')

View File

@@ -78,6 +78,39 @@ class User(db.Model):
return True return True
user_to_service = db.Table(
'user_to_service',
db.Model.metadata,
db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
db.Column('service_id', db.Integer, db.ForeignKey('services.id'))
)
class Service(db.Model):
__tablename__ = 'services'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False, unique=True)
created_at = db.Column(db.DateTime, index=False, unique=False, nullable=False)
active = db.Column(db.Boolean, index=False, unique=False, nullable=False)
limit = db.Column(db.BigInteger, index=False, unique=False, nullable=False)
users = db.relationship('User', secondary=user_to_service, backref=db.backref('user_to_service', lazy='dynamic'))
restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False)
def serialize(self):
serialized = {
'id': self.id,
'name': self.name,
'createdAt': self.created_at.strftime(DATETIME_FORMAT),
'active': self.active,
'restricted': self.restricted,
'limit': self.limit,
'user': self.users.serialize()
}
return filter_null_value_fields(serialized)
def filter_null_value_fields(obj): def filter_null_value_fields(obj):
return dict( return dict(
filter(lambda x: x[1] is not None, obj.items()) filter(lambda x: x[1] is not None, obj.items())

View File

@@ -16,15 +16,17 @@ GOV.UK Notify | Set up service
<li>as your email sender name</li> <li>as your email sender name</li>
</ul> </ul>
<p> <form autocomplete="off" action="" method="post">
<label class="form-label" for="emailverify">Service name</label> {{ form.hidden_tag() }}
<input class="form-control" id="emailverify" type="text"><br> <label class="form-label">Service name</label>
<span class="font-xsmall">For example, 'Vehicle tax' or 'Carer's allowance'</span> {{ form.service_name(class="form-control-2-3", autocomplete="off") }} <br>
</p> <span class="font-xsmall">For example, 'Vehicle tax' or 'Carer's allowance'</span>
<p> <p>
<a class="button" href="dashboard" role="button">Continue</a> <button class="button" href="dashboard" role="button">Continue</button>
</p> </p>
</form>
</div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,42 @@
"""empty message
Revision ID: 60_add_service
Revises: 50_alter_verify_code_type
Create Date: 2015-12-15 09:25:09.000431
"""
# revision identifiers, used by Alembic.
revision = '60_add_service'
down_revision = '50_alter_verify_code_type'
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_table('services',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('active', sa.Boolean(), nullable=False),
sa.Column('limit', sa.BigInteger(), nullable=False),
sa.Column('restricted', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('user_to_service',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('service_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['service_id'], ['services.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], )
)
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_table('user_to_service')
op.drop_table('services')
### end Alembic commands ###

View File

@@ -0,0 +1,62 @@
import pytest
import sqlalchemy
from app.main.dao import services_dao
from tests.app.main import create_test_user
def test_can_insert_and_retrieve_new_service(notifications_admin, notifications_admin_db, notify_db_session):
user = create_test_user()
id = services_dao.insert_new_service('testing service', user)
saved_service = services_dao.get_service_by_id(id)
assert id == saved_service.id
assert saved_service.users == [user]
assert saved_service.name == 'testing service'
def test_unrestrict_service_updates_the_service(notifications_admin, notifications_admin_db, notify_db_session):
user = create_test_user()
id = services_dao.insert_new_service('unrestricted service', user)
saved_service = services_dao.get_service_by_id(id)
assert saved_service.restricted is True
services_dao.unrestrict_service(id)
unrestricted_service = services_dao.get_service_by_id(id)
assert unrestricted_service.restricted is False
def test_activate_service_update_service(notifications_admin, notifications_admin_db, notify_db_session):
user = create_test_user()
id = services_dao.insert_new_service('activated service', user)
service = services_dao.get_service_by_id(id)
assert service.active is False
services_dao.activate_service(id)
activated_service = services_dao.get_service_by_id(id)
assert activated_service.active is True
def test_get_service_returns_none_if_service_does_not_exist(notifications_admin,
notifications_admin_db,
notify_db_session):
service = services_dao.get_service_by_id(1)
assert service is None
def test_find_by_service_name_returns_right_service(notifications_admin,
notifications_admin_db,
notify_db_session):
user = create_test_user()
id = services_dao.insert_new_service('testing service', user)
another = services_dao.insert_new_service('Testing the Service', user)
found = services_dao.find_service_by_service_name('testing service')
assert found.id == id
assert found.name == 'testing service'
found_another = services_dao.find_service_by_service_name('Testing the Service')
assert found_another == services_dao.get_service_by_id(another)
def test_should_not_allow_two_services_of_the_same_name(notifications_admin, notifications_admin_db, notify_db_session):
user = create_test_user()
services_dao.insert_new_service('duplicate service', user)
with pytest.raises(sqlalchemy.exc.IntegrityError) as error:
services_dao.insert_new_service('duplicate service', user)
assert 'duplicate key value violates unique constraint "services_name_key' in error.value

View File

@@ -0,0 +1,18 @@
from app.main.dao import services_dao
from app.main.forms import AddServiceForm
from tests.app.main import create_test_user
def test_form_should_have_errors_when_duplicate_service_is_added(notifications_admin,
notifications_admin_db,
notify_db_session):
with notifications_admin.test_request_context(method='POST',
data={'service_name': 'some service'}) as req:
user = create_test_user()
services_dao.insert_new_service('some service', user)
req.session['user_id'] = user.id
form = AddServiceForm(req.request.form)
assert form.validate() is False
assert len(form.errors) == 1
expected = {'service_name': ['Duplicate service name']}
assert form.errors == expected

View File

@@ -0,0 +1,42 @@
from app.main.dao import verify_codes_dao, services_dao
from tests.app.main import create_test_user
def test_get_should_render_add_service_template(notifications_admin, notifications_admin_db, notify_db_session):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = create_test_user()
session['user_id'] = user.id
verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms')
client.post('/two-factor', data={'sms_code': '12345'})
response = client.get('/add-service')
assert response.status_code == 200
assert 'Set up notifications for your service' in response.get_data(as_text=True)
def test_should_add_service_and_redirect_to_next_page(notifications_admin, notifications_admin_db, notify_db_session):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = create_test_user()
session['user_id'] = user.id
verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms')
client.post('/two-factor', data={'sms_code': '12345'})
response = client.post('/add-service', data={'service_name': 'testing the post'})
assert response.status_code == 302
assert response.location == 'http://localhost/dashboard'
saved_service = services_dao.find_service_by_service_name('testing the post')
assert saved_service is not None
def test_should_return_form_errors_when_service_name_is_empty(notifications_admin,
notifications_admin_db,
notify_db_session):
with notifications_admin.test_client() as client:
with client.session_transaction() as session:
user = create_test_user()
session['user_id'] = user.id
verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms')
client.post('/two-factor', data={'sms_code': '12345'})
response = client.post('/add-service', data={})
assert response.status_code == 400
assert 'Please enter your service name' in response.get_data(as_text=True)