mirror of
https://github.com/GSA/notifications-api.git
synced 2026-03-02 06:50:08 -05:00
Merge branch 'master' into add-reply-to-notifications
This commit is contained in:
@@ -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
|
||||
|
||||
140
app/models.py
140
app/models.py
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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')
|
||||
)
|
||||
|
||||
|
||||
|
||||
33
migrations/versions/0144_template_service_letter.py
Normal file
33
migrations/versions/0144_template_service_letter.py
Normal 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')
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user