diff --git a/app/__init__.py b/app/__init__.py index f15ba6ac0..7ee54c648 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,9 +30,11 @@ def create_app(config_name): from .main import main as main_blueprint from .service import service as service_blueprint from .user import user as user_blueprint + from .template import template as template_blueprint application.register_blueprint(main_blueprint) application.register_blueprint(service_blueprint, url_prefix='/service') application.register_blueprint(user_blueprint, url_prefix='/user') + application.register_blueprint(template_blueprint, url_prefix="/template") from .status import status as status_blueprint application.register_blueprint(status_blueprint) diff --git a/app/dao/templates_dao.py b/app/dao/templates_dao.py new file mode 100644 index 000000000..8a37f19d3 --- /dev/null +++ b/app/dao/templates_dao.py @@ -0,0 +1,31 @@ +import json +from datetime import datetime +from sqlalchemy.orm import load_only +from . import DAOException +from app import db +from app.models import (Template, Service) + + +def save_model_template(template, update_dict=None): + if update_dict: + Template.query.filter_by(id=template.id).update(update_dict) + else: + db.session.add(template) + db.session.commit() + + +def delete_model_template(template): + db.session.delete(template) + db.session.commit() + + +def get_model_templates(template_id=None, service_id=None): + # TODO need better mapping from function params to sql query. + if template_id and service_id: + return Template.query.filter_by( + id=template_id, service=Service.get(service_id)).one() + elif template_id: + return Template.query.filter_by(id=template_id).one() + elif service_id: + return Template.query.filter_by(service=Service.get(service_id)).one() + return Template.query.all() diff --git a/app/models.py b/app/models.py index f14f95cf2..fd11d36d6 100644 --- a/app/models.py +++ b/app/models.py @@ -59,3 +59,29 @@ class Service(db.Model): secondary=user_to_service, backref=db.backref('user_to_service', lazy='dynamic')) restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False) + + +TEMPLATE_TYPES = ['sms', 'email', 'letter'] + + +class Template(db.Model): + __tablename__ = 'templates' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255), nullable=False) + template_type = db.Column(db.Enum(*TEMPLATE_TYPES, name='template_type'), nullable=False) + created_at = db.Column( + db.DateTime, + index=False, + unique=False, + nullable=False, + default=datetime.datetime.now) + updated_at = db.Column( + db.DateTime, + index=False, + unique=False, + nullable=True, + onupdate=datetime.datetime.now) + content = db.Column(db.Text, index=False, unique=False, nullable=False) + service_id = db.Column(db.BigInteger, db.ForeignKey('services.id'), index=True, unique=False) + service = db.relationship('Service', backref=db.backref('templates', lazy='dynamic')) diff --git a/app/schemas.py b/app/schemas.py index 0d2e14d06..c3425258a 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -19,6 +19,12 @@ class UserSchema(ma.ModelSchema): class ServiceSchema(ma.ModelSchema): class Meta: model = models.Service + exclude = ("updated_at", "created_at", "templates") + + +class TemplateSchema(ma.ModelSchema): + class Meta: + model = models.Template exclude = ("updated_at", "created_at") @@ -26,3 +32,5 @@ user_schema = UserSchema() users_schema = UserSchema(many=True) service_schema = ServiceSchema() services_schema = ServiceSchema(many=True) +template_schema = TemplateSchema() +templates_schema = TemplateSchema(many=True) diff --git a/app/service/views/rest.py b/app/service/views/rest.py index c31d7146c..abafc54af 100644 --- a/app/service/views/rest.py +++ b/app/service/views/rest.py @@ -67,3 +67,58 @@ def get_service(service_id=None): return jsonify(result="error", message="Service not found"), 404 data, errors = services_schema.dump(services) if isinstance(services, list) else service_schema.dump(services) return jsonify(data=data) + + +# TODO auth to be added. +@service.route('//template/', methods=['POST']) +def create_template(): + try: + service = get_model_services(service_id=service_id) + except DataError: + return jsonify(result="error", message="Invalid service id"), 400 + except NoResultFound: + return jsonify(result="error", message="Service not found"), 404 + template, errors = template_schema.load(request.get_json()) + if errors: + return jsonify(result="error", message=errors), 400 + template.service = service + # I believe service is already added to the session but just needs a + # db.session.commit + save_model_template(template) + return jsonify(data=template_schema.dump(template).data), 201 + + +# TODO auth to be added +@service.route('//template/', methods=['PUT', 'DELETE']) +def update_template(service_id, template_id): + try: + service = get_model_services(service_id=service_id) + except DataError: + return jsonify(result="error", message="Invalid service id"), 400 + except NoResultFound: + return jsonify(result="error", message="Service not found"), 404 + try: + template = get_model_templates(template_id=template_id) + except DataError: + return jsonify(result="error", message="Invalid template id"), 400 + except NoResultFound: + return jsonify(result="error", message="Template not found"), 404 + if request.method == 'DELETE': + status_code = 202 + delete_model_template(template) + else: + status_code = 200 + # TODO there has got to be a better way to do the next three lines + upd_temp, errors = template_schema.load(request.get_json()) + if errors: + return jsonify(result="error", message=errors), 400 + upd_temp.service = service + update_dict, errors = template_schema.dump(upd_temp) + # TODO FIX ME + # Remove update_temp model which is added to db.session + db.session.rollback() + try: + save_model_template(template, update_dict=update_dict) + except DAOException as e: + return jsonify(result="error", message=str(e)), 400 + return jsonify(data=service_template.dump(template).data), status_code diff --git a/app/template/__init__.py b/app/template/__init__.py new file mode 100644 index 000000000..680c3f4ac --- /dev/null +++ b/app/template/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +template = Blueprint('template', __name__) + +from app.template import rest diff --git a/app/template/rest.py b/app/template/rest.py new file mode 100644 index 000000000..82e23f321 --- /dev/null +++ b/app/template/rest.py @@ -0,0 +1,34 @@ +from flask import (jsonify, request) +from sqlalchemy.exc import DataError +from sqlalchemy.orm.exc import NoResultFound +from app.dao.templates_dao import get_model_templates +from app.dao.services_dao import get_model_services +from app.schemas import (template_schema, templates_schema) +from app import db + +from flask import (jsonify, request) +from sqlalchemy.exc import DataError +from sqlalchemy.orm.exc import NoResultFound +from . import template +from app import db + + +# I am going to keep these for admin like operations +# Permissions should restrict who can access this endpoint +# TODO auth to be added. +@template.route('/', methods=['GET']) +@template.route('/', methods=['GET']) +def get_template(template_id=None): + try: + templates = get_model_templates(template_id=template_id) + except DataError: + return jsonify(result="error", message="Invalid template id"), 400 + except NoResultFound: + return jsonify(result="error", message="Template not found"), 404 + if isinstance(templates, list): + data, errors = templates_schema.dump(templates) + else: + data, errors = template_schema.dump(templates) + if errors: + return jsonify(result="error", message=str(errors)) + return jsonify(data=data) diff --git a/migrations/versions/0002_add_templates.py b/migrations/versions/0002_add_templates.py new file mode 100644 index 000000000..30f4073f4 --- /dev/null +++ b/migrations/versions/0002_add_templates.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: 0002_add_templates +Revises: 0001_initialise_data +Create Date: 2016-01-13 10:10:37.303109 + +""" + +# revision identifiers, used by Alembic. +revision = '0002_add_templates' +down_revision = '0001_initialise_data' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.create_table('templates', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=False), + sa.Column('template_type', sa.Enum('sms', 'email', 'letter', name='template_type'), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('content', sa.Text(), nullable=False), + sa.Column('service_id', sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_templates_service_id'), 'templates', ['service_id'], unique=False) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_templates_service_id'), table_name='templates') + op.drop_table('templates') + ### end Alembic commands ### diff --git a/tests/app/conftest.py b/tests/app/conftest.py index 5f3b5ead4..b2d8b92e0 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -1,7 +1,8 @@ import pytest -from app.models import (User, Service) +from app.models import (User, Service, Template) from app.dao.users_dao import (save_model_user, get_model_users) from app.dao.services_dao import save_model_service +from app.dao.templates_dao import save_model_template @pytest.fixture(scope='function') @@ -29,3 +30,23 @@ def sample_service(notify_db, service = Service(**data) save_model_service(service) return service + + +@pytest.fixture(scope='function') +def sample_template(notify_db, + notify_db_session, + template_name="Template Name", + template_type="sms", + content="This is a template", + service=None): + if service is None: + service = sample_service(notify_db, notify_db_session) + data = { + 'name': template_name, + 'template_type': template_type, + 'content': content, + 'service': service + } + template = Template(**data) + save_model_template(template) + return template diff --git a/tests/app/template/__init__.py b/tests/app/template/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/app/template/test_rest.py b/tests/app/template/test_rest.py new file mode 100644 index 000000000..312072d6a --- /dev/null +++ b/tests/app/template/test_rest.py @@ -0,0 +1,31 @@ +import json +from app.models import Template +from flask import url_for + + +def test_get_template_list(notify_api, notify_db, notify_db_session, sample_template): + """ + Tests GET endpoint '/' to retrieve entire template list. + """ + with notify_api.test_request_context(): + with notify_api.test_client() as client: + response = client.get(url_for('template.get_template')) + assert response.status_code == 200 + json_resp = json.loads(response.get_data(as_text=True)) + assert len(json_resp['data']) == 1 + assert json_resp['data'][0]['name'] == sample_template.name + assert json_resp['data'][0]['id'] == sample_template.id + + +def test_get_template(notify_api, notify_db, notify_db_session, sample_template): + """ + Tests GET endpoint '/' to retrieve a single template. + """ + with notify_api.test_request_context(): + with notify_api.test_client() as client: + resp = client.get(url_for( + 'template.get_template', template_id=sample_template.id)) + assert resp.status_code == 200 + json_resp = json.loads(resp.get_data(as_text=True)) + assert json_resp['data']['name'] == sample_template.name + assert json_resp['data']['id'] == sample_template.id