Merge branch 'master' into add-reply-to-notifications

This commit is contained in:
Rebecca Law
2017-11-23 13:45:27 +00:00
8 changed files with 217 additions and 70 deletions

View File

@@ -219,6 +219,8 @@ def create_history(obj, history_cls=None):
data['created_at'] = obj.created_at
for key, value in data.items():
if not hasattr(history_cls, key):
raise AttributeError("{} has no attribute '{}'".format(history_cls.__name__, key))
setattr(history, key, value)
return history

View File

@@ -4,6 +4,7 @@ import uuid
import datetime
from flask import url_for, current_app
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.dialects.postgresql import (
UUID,
@@ -522,50 +523,67 @@ class TemplateProcessTypes(db.Model):
name = db.Column(db.String(255), primary_key=True)
class Template(db.Model):
__tablename__ = 'templates'
class TemplateBase(db.Model):
__abstract__ = True
def __init__(self, **kwargs):
if 'template_type' in kwargs:
self.template_type = kwargs.pop('template_type')
super().__init__(**kwargs)
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = db.Column(db.String(255), nullable=False)
template_type = db.Column(template_types, nullable=False)
created_at = db.Column(
db.DateTime,
index=False,
unique=False,
nullable=False,
default=datetime.datetime.utcnow)
updated_at = db.Column(
db.DateTime,
index=False,
unique=False,
nullable=True,
onupdate=datetime.datetime.utcnow)
content = db.Column(db.Text, index=False, unique=False, nullable=False)
archived = db.Column(db.Boolean, index=False, nullable=False, default=False)
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, unique=False, nullable=False)
service = db.relationship('Service', backref='templates')
subject = db.Column(db.Text, index=False, unique=False, nullable=True)
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
created_by = db.relationship('User')
version = db.Column(db.Integer, default=0, nullable=False)
process_type = db.Column(
db.String(255),
db.ForeignKey('template_process_type.name'),
index=True,
nullable=False,
default=NORMAL
)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
updated_at = db.Column(db.DateTime, onupdate=datetime.datetime.utcnow)
content = db.Column(db.Text, nullable=False)
archived = db.Column(db.Boolean, nullable=False, default=False)
subject = db.Column(db.Text)
@declared_attr
def service_id(cls):
return db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False)
@declared_attr
def created_by_id(cls):
return db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
@declared_attr
def created_by(cls):
return db.relationship('User')
@declared_attr
def process_type(cls):
return db.Column(
db.String(255),
db.ForeignKey('template_process_type.name'),
index=True,
nullable=False,
default=NORMAL
)
redact_personalisation = association_proxy('template_redacted', 'redact_personalisation')
def get_link(self):
# TODO: use "/v2/" route once available
return url_for(
"template.get_template_by_id_and_service_id",
service_id=self.service_id,
template_id=self.id,
_external=True
)
@declared_attr
def service_letter_contact_id(cls):
return db.Column(UUID(as_uuid=True), db.ForeignKey('service_letter_contacts.id'), nullable=True)
@property
def reply_to(self):
if self.template_type == LETTER_TYPE:
return self.service_letter_contact_id
else:
return None
@reply_to.setter
def reply_to(self, value):
if self.template_type == LETTER_TYPE:
self.service_letter_contact_id = value
elif value is None:
pass
else:
raise ValueError('Unable to set sender for {} template'.format(self.template_type))
def _as_utils_template(self):
if self.template_type == EMAIL_TYPE:
@@ -605,6 +623,22 @@ class Template(db.Model):
return serialized
class Template(TemplateBase):
__tablename__ = 'templates'
service = db.relationship('Service', backref='templates')
version = db.Column(db.Integer, default=0, nullable=False)
def get_link(self):
# TODO: use "/v2/" route once available
return url_for(
"template.get_template_by_id_and_service_id",
service_id=self.service_id,
template_id=self.id,
_external=True
)
class TemplateRedacted(db.Model):
__tablename__ = 'template_redacted'
@@ -618,32 +652,16 @@ class TemplateRedacted(db.Model):
template = db.relationship('Template', uselist=False, backref=db.backref('template_redacted', uselist=False))
class TemplateHistory(db.Model):
class TemplateHistory(TemplateBase):
__tablename__ = 'templates_history'
id = db.Column(UUID(as_uuid=True), primary_key=True)
name = db.Column(db.String(255), nullable=False)
template_type = db.Column(template_types, nullable=False)
created_at = db.Column(db.DateTime, nullable=False)
updated_at = db.Column(db.DateTime)
content = db.Column(db.Text, nullable=False)
archived = db.Column(db.Boolean, nullable=False, default=False)
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False)
service = db.relationship('Service')
subject = db.Column(db.Text)
created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
created_by = db.relationship('User')
version = db.Column(db.Integer, primary_key=True, nullable=False)
process_type = db.Column(db.String(255),
db.ForeignKey('template_process_type.name'),
index=True,
nullable=False,
default=NORMAL)
template_redacted = db.relationship('TemplateRedacted', foreign_keys=[id],
primaryjoin='TemplateRedacted.template_id == TemplateHistory.id')
redact_personalisation = association_proxy('template_redacted', 'redact_personalisation')
@declared_attr
def template_redacted(cls):
return db.relationship('TemplateRedacted', foreign_keys=[cls.id],
primaryjoin='TemplateRedacted.template_id == TemplateHistory.id')
def get_link(self):
return url_for(
@@ -653,12 +671,6 @@ class TemplateHistory(db.Model):
_external=True
)
def _as_utils_template(self):
return Template._as_utils_template(self)
def serialize(self):
return Template.serialize(self)
MMG_PROVIDER = "mmg"
FIRETEXT_PROVIDER = "firetext"

View File

@@ -314,9 +314,14 @@ class NotificationModelSchema(BaseSchema):
class BaseTemplateSchema(BaseSchema):
reply_to = fields.Method("get_reply_to", allow_none=True)
def get_reply_to(self, template):
return template.reply_to
class Meta:
model = models.Template
exclude = ("service_id", "jobs")
exclude = ("service_id", "jobs", "service_letter_contact_id")
strict = True
@@ -339,9 +344,14 @@ class TemplateSchema(BaseTemplateSchema):
class TemplateHistorySchema(BaseSchema):
reply_to = fields.Method("get_reply_to", allow_none=True)
created_by = fields.Nested(UserSchema, only=['id', 'name', 'email_address'], dump_only=True)
created_at = field_for(models.Template, 'created_at', format='%Y-%m-%d %H:%M:%S.%f')
def get_reply_to(self, template):
return template.reply_to
class Meta:
model = models.TemplateHistory

View File

@@ -155,7 +155,7 @@ def get_template_versions(service_id, template_id):
def _template_has_not_changed(current_data, updated_template):
return all(
current_data[key] == updated_template[key]
for key in ('name', 'content', 'subject', 'archived', 'process_type')
for key in ('name', 'content', 'subject', 'archived', 'process_type', 'reply_to')
)

View File

@@ -0,0 +1,33 @@
"""
Revision ID: 0144_template_service_letter
Revises: 0143_remove_reply_to
Create Date: 2017-11-17 15:42:16.401229
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = '0144_template_service_letter'
down_revision = '0143_remove_reply_to'
def upgrade():
op.add_column('templates',
sa.Column('service_letter_contact_id', postgresql.UUID(as_uuid=True), nullable=True))
op.create_foreign_key('templates_service_letter_contact_id_fkey', 'templates',
'service_letter_contacts', ['service_letter_contact_id'], ['id'])
op.add_column('templates_history',
sa.Column('service_letter_contact_id', postgresql.UUID(as_uuid=True), nullable=True))
op.create_foreign_key('templates_history_service_letter_contact_id_fkey', 'templates_history',
'service_letter_contacts', ['service_letter_contact_id'], ['id'])
def downgrade():
op.drop_constraint('templates_service_letter_contact_id_fkey', 'templates', type_='foreignkey')
op.drop_column('templates', 'service_letter_contact_id')
op.drop_constraint('templates_history_service_letter_contact_id_fkey', 'templates_history', type_='foreignkey')
op.drop_column('templates_history', 'service_letter_contact_id')

View File

@@ -15,7 +15,7 @@ from app.dao.templates_dao import (
from app.models import Template, TemplateHistory, TemplateRedacted
from tests.app.conftest import sample_template as create_sample_template
from tests.app.db import create_template
from tests.app.db import create_template, create_letter_contact
@pytest.mark.parametrize('template_type, subject', [
@@ -53,6 +53,23 @@ def test_create_template_creates_redact_entry(sample_service):
assert redacted.updated_by_id == sample_service.created_by_id
def test_create_template_with_reply_to(sample_service, sample_user):
letter_contact = create_letter_contact(sample_service, 'Edinburgh, ED1 1AA')
data = {
'name': 'Sample Template',
'template_type': "letter",
'content': "Template content",
'service': sample_service,
'created_by': sample_user,
'reply_to': letter_contact.id,
}
template = Template(**data)
dao_create_template(template)
assert dao_get_all_templates_for_service(sample_service.id)[0].reply_to == letter_contact.id
def test_update_template(sample_service, sample_user):
data = {
'name': 'Sample Template',
@@ -71,6 +88,29 @@ def test_update_template(sample_service, sample_user):
assert dao_get_all_templates_for_service(sample_service.id)[0].name == 'new name'
def test_update_template_reply_to(sample_service, sample_user):
letter_contact = create_letter_contact(sample_service, 'Edinburgh, ED1 1AA')
data = {
'name': 'Sample Template',
'template_type': "letter",
'content': "Template content",
'service': sample_service,
'created_by': sample_user,
}
template = Template(**data)
dao_create_template(template)
created = dao_get_all_templates_for_service(sample_service.id)[0]
assert created.reply_to is None
created.reply_to = letter_contact.id
dao_update_template(created)
assert dao_get_all_templates_for_service(sample_service.id)[0].reply_to == letter_contact.id
template_history = TemplateHistory.query.filter_by(id=created.id, version=2).one()
assert template_history.service_letter_contact_id
def test_redact_template(sample_template):
redacted = TemplateRedacted.query.one()
assert redacted.template_id == sample_template.id

View File

@@ -17,9 +17,7 @@ from tests.app.conftest import (
sample_template_without_letter_permission,
sample_template_without_sms_permission,
)
from tests.app.db import create_service
from app.dao.templates_dao import dao_get_template_by_id
from tests.app.db import create_service, create_letter_contact
@pytest.mark.parametrize('template_type, subject', [
@@ -618,6 +616,39 @@ def test_update_set_process_type_on_template(client, sample_template):
assert template.process_type == 'priority'
def test_get_template_reply_to(client, sample_letter_template):
auth_header = create_authorization_header()
letter_contact = create_letter_contact(sample_letter_template.service, "Edinburgh, ED1 1AA")
sample_letter_template.reply_to = str(letter_contact.id)
resp = client.get('/service/{}/template/{}'.format(sample_letter_template.service_id, sample_letter_template.id),
headers=[auth_header])
assert resp.status_code == 200, resp.get_data(as_text=True)
json_resp = json.loads(resp.get_data(as_text=True))
assert 'service_letter_contact_id' not in json_resp['data']
assert json_resp['data']['reply_to'] == str(letter_contact.id)
def test_update_template_reply_to(client, sample_letter_template):
auth_header = create_authorization_header()
letter_contact = create_letter_contact(sample_letter_template.service, "Edinburgh, ED1 1AA")
data = {
'reply_to': str(letter_contact.id),
}
resp = client.post('/service/{}/template/{}'.format(sample_letter_template.service_id, sample_letter_template.id),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
assert resp.status_code == 200, resp.get_data(as_text=True)
template = dao_get_template_by_id(sample_letter_template.id)
assert template.reply_to == letter_contact.id
def test_update_redact_template(admin_request, sample_template):
assert sample_template.redact_personalisation is False

View File

@@ -3,6 +3,7 @@ from datetime import (datetime, date)
from flask import url_for
from app.dao.templates_dao import dao_update_template
from tests import create_authorization_header
from tests.app.db import create_letter_contact
def test_template_history_version(notify_api, sample_user, sample_template):
@@ -93,3 +94,21 @@ def test_all_versions_of_template(notify_api, sample_template):
assert json_resp['data'][1]['content'] == newer_content
assert json_resp['data'][1]['updated_at']
assert json_resp['data'][2]['content'] == old_content
def test_update_template_reply_to_updates_history(client, sample_letter_template):
auth_header = create_authorization_header()
letter_contact = create_letter_contact(sample_letter_template.service, "Edinburgh, ED1 1AA")
sample_letter_template.reply_to = letter_contact.id
dao_update_template(sample_letter_template)
resp = client.get(
'/service/{}/template/{}/version/2'.format(sample_letter_template.service_id, sample_letter_template.id),
headers=[auth_header]
)
assert resp.status_code == 200
hist_json_resp = json.loads(resp.get_data(as_text=True))
assert 'service_letter_contact_id' not in hist_json_resp['data']
assert hist_json_resp['data']['reply_to'] == str(letter_contact.id)