remove history-meta for templates, replace with hand-made history table

history-meta's dynamic magic is insufficient for templates, where we
need to be able to refer to the specific history table to take
advantage of sqlalchemy's relationship management (2/3rds of an ORM).
So replace it with a custom made version table.

Had to change the version decorator slightly for this
This commit is contained in:
Leo Hemsted
2016-08-02 16:23:14 +01:00
parent 4e01277318
commit 049514d4b2
8 changed files with 57 additions and 34 deletions

View File

@@ -1,6 +1,7 @@
import itertools import itertools
from functools import wraps from functools import wraps, partial
from app.history_meta import versioned_objects, create_history
from app.history_meta import create_history
def transactional(func): def transactional(func):
@@ -19,14 +20,16 @@ def transactional(func):
return commit_or_rollback return commit_or_rollback
def version_class(model_class): def version_class(model_class, history_cls=None):
create_hist = partial(create_history, history_cls=history_cls)
def versioned(func): def versioned(func):
@wraps(func) @wraps(func)
def record_version(*args, **kwargs): def record_version(*args, **kwargs):
from app import db from app import db
func(*args, **kwargs) func(*args, **kwargs)
history_objects = [create_history(obj) for obj in history_objects = [create_hist(obj) for obj in
versioned_objects(itertools.chain(db.session.new, db.session.dirty)) itertools.chain(db.session.new, db.session.dirty)
if isinstance(obj, model_class)] if isinstance(obj, model_class)]
for h_obj in history_objects: for h_obj in history_objects:
db.session.add(h_obj) db.session.add(h_obj)

View File

@@ -16,6 +16,7 @@ from app.models import (
VerifyCode, VerifyCode,
ApiKey, ApiKey,
Template, Template,
TemplateHistory,
Job, Job,
NotificationHistory, NotificationHistory,
Notification, Notification,
@@ -122,7 +123,7 @@ def delete_service_and_all_associated_db_objects(service):
_delete_commit(Notification.query.filter_by(service=service)) _delete_commit(Notification.query.filter_by(service=service))
_delete_commit(Job.query.filter_by(service=service)) _delete_commit(Job.query.filter_by(service=service))
_delete_commit(Template.query.filter_by(service=service)) _delete_commit(Template.query.filter_by(service=service))
_delete_commit(Template.get_history_model().query.filter_by(service_id=service.id)) _delete_commit(TemplateHistory.query.filter_by(service_id=service.id))
verify_codes = VerifyCode.query.join(User).filter(User.id.in_([x.id for x in service.users])) verify_codes = VerifyCode.query.join(User).filter(User.id.in_([x.id for x in service.users]))
list(map(db.session.delete, verify_codes)) list(map(db.session.delete, verify_codes))

View File

@@ -1,8 +1,9 @@
import uuid import uuid
from app import db
from app.models import (Template, Service)
from sqlalchemy import (asc, desc) from sqlalchemy import (asc, desc)
from app import db
from app.models import (Template, TemplateHistory)
from app.dao.dao_utils import ( from app.dao.dao_utils import (
transactional, transactional,
version_class version_class
@@ -10,7 +11,7 @@ from app.dao.dao_utils import (
@transactional @transactional
@version_class(Template) @version_class(Template, TemplateHistory)
def dao_create_template(template): def dao_create_template(template):
template.id = uuid.uuid4() # must be set now so version history model can use same id template.id = uuid.uuid4() # must be set now so version history model can use same id
template.archived = False template.archived = False
@@ -18,14 +19,14 @@ def dao_create_template(template):
@transactional @transactional
@version_class(Template) @version_class(Template, TemplateHistory)
def dao_update_template(template): def dao_update_template(template):
db.session.add(template) db.session.add(template)
def dao_get_template_by_id_and_service_id(template_id, service_id, version=None): def dao_get_template_by_id_and_service_id(template_id, service_id, version=None):
if version is not None: if version is not None:
return Template.get_history_model().query.filter_by( return TemplateHistory.query.filter_by(
id=template_id, id=template_id,
service_id=service_id, service_id=service_id,
version=version).one() version=version).one()
@@ -34,7 +35,7 @@ def dao_get_template_by_id_and_service_id(template_id, service_id, version=None)
def dao_get_template_by_id(template_id, version=None): def dao_get_template_by_id(template_id, version=None):
if version is not None: if version is not None:
return Template.get_history_model().query.filter_by( return TemplateHistory.query.filter_by(
id=template_id, id=template_id,
version=version).one() version=version).one()
return Template.query.filter_by(id=template_id).one() return Template.query.filter_by(id=template_id).one()
@@ -50,6 +51,8 @@ def dao_get_all_templates_for_service(service_id):
def dao_get_template_versions(service_id, template_id): def dao_get_template_versions(service_id, template_id):
history_model = Template.get_history_model() return TemplateHistory.query.filter_by(
return history_model.query.filter_by(service_id=service_id, id=template_id).order_by( service_id=service_id, id=template_id
desc(history_model.version)).all() ).order_by(
desc(TemplateHistory.version)
).all()

View File

@@ -171,17 +171,13 @@ class Versioned(object):
return history_mapper.class_ return history_mapper.class_
def versioned_objects(iter): def create_history(obj, history_cls=None):
for obj in iter: if not history_cls:
if hasattr(obj, '__history_mapper__'): history_mapper = obj.__history_mapper__
yield obj history_cls = history_mapper.class_
def create_history(obj):
obj_mapper = object_mapper(obj)
history_mapper = obj.__history_mapper__
history_cls = history_mapper.class_
history = history_cls() history = history_cls()
obj_mapper = object_mapper(obj)
obj_state = attributes.instance_state(obj) obj_state = attributes.instance_state(obj)
data = {} data = {}

View File

@@ -206,7 +206,7 @@ TEMPLATE_TYPES = [SMS_TYPE, EMAIL_TYPE, LETTER_TYPE]
template_types = db.Enum(*TEMPLATE_TYPES, name='template_type') template_types = db.Enum(*TEMPLATE_TYPES, name='template_type')
class Template(db.Model, Versioned): class Template(db.Model):
__tablename__ = 'templates' __tablename__ = 'templates'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
@@ -231,6 +231,26 @@ class Template(db.Model, Versioned):
subject = db.Column(db.Text, index=False, unique=False, nullable=True) 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_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
created_by = db.relationship('User') created_by = db.relationship('User')
version = db.Column(db.Integer, default=1)
class TemplateHistory(db.Model):
__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)
MMG_PROVIDER = "mmg" MMG_PROVIDER = "mmg"
FIRETEXT_PROVIDER = "firetext" FIRETEXT_PROVIDER = "firetext"

View File

@@ -189,7 +189,7 @@ def get_service_history(service_id):
api_key_history = ApiKey.get_history_model().query.filter_by(service_id=service_id).all() api_key_history = ApiKey.get_history_model().query.filter_by(service_id=service_id).all()
api_keys_data = api_key_history_schema.dump(api_key_history, many=True).data api_keys_data = api_key_history_schema.dump(api_key_history, many=True).data
template_history = Template.get_history_model().query.filter_by(service_id=service_id).all() template_history = TemplateHistory.query.filter_by(service_id=service_id).all()
template_data, errors = template_history_schema.dump(template_history, many=True) template_data, errors = template_history_schema.dump(template_history, many=True)
events = Event.query.all() events = Event.query.all()

View File

@@ -331,7 +331,7 @@ def test_delete_service_and_associated_objects(notify_db,
assert ApiKey.query.count() == 0 assert ApiKey.query.count() == 0
assert ApiKey.get_history_model().query.count() == 0 assert ApiKey.get_history_model().query.count() == 0
assert Template.query.count() == 0 assert Template.query.count() == 0
assert Template.get_history_model().query.count() == 0 assert TemplateHistory.query.count() == 0
assert Job.query.count() == 0 assert Job.query.count() == 0
assert Notification.query.count() == 0 assert Notification.query.count() == 0
assert Permission.query.count() == 0 assert Permission.query.count() == 0

View File

@@ -185,7 +185,7 @@ def test_get_template_by_id_and_service_returns_none_if_no_template(sample_servi
def test_create_template_creates_a_history_record_with_current_data(sample_service, sample_user): def test_create_template_creates_a_history_record_with_current_data(sample_service, sample_user):
assert Template.query.count() == 0 assert Template.query.count() == 0
assert Template.get_history_model().query.count() == 0 assert TemplateHistory.query.count() == 0
data = { data = {
'name': 'Sample Template', 'name': 'Sample Template',
'template_type': "email", 'template_type': "email",
@@ -200,7 +200,7 @@ def test_create_template_creates_a_history_record_with_current_data(sample_servi
assert Template.query.count() == 1 assert Template.query.count() == 1
template_from_db = Template.query.first() template_from_db = Template.query.first()
template_history = Template.get_history_model().query.first() template_history = TemplateHistory.query.first()
assert template_from_db.id == template_history.id assert template_from_db.id == template_history.id
assert template_from_db.name == template_history.name assert template_from_db.name == template_history.name
@@ -212,7 +212,7 @@ def test_create_template_creates_a_history_record_with_current_data(sample_servi
def test_update_template_creates_a_history_record_with_current_data(sample_service, sample_user): def test_update_template_creates_a_history_record_with_current_data(sample_service, sample_user):
assert Template.query.count() == 0 assert Template.query.count() == 0
assert Template.get_history_model().query.count() == 0 assert TemplateHistory.query.count() == 0
data = { data = {
'name': 'Sample Template', 'name': 'Sample Template',
'template_type': "email", 'template_type': "email",
@@ -228,20 +228,20 @@ def test_update_template_creates_a_history_record_with_current_data(sample_servi
assert created.name == 'Sample Template' assert created.name == 'Sample Template'
assert Template.query.count() == 1 assert Template.query.count() == 1
assert Template.query.first().version == 1 assert Template.query.first().version == 1
assert Template.get_history_model().query.count() == 1 assert TemplateHistory.query.count() == 1
created.name = 'new name' created.name = 'new name'
dao_update_template(created) dao_update_template(created)
assert Template.query.count() == 1 assert Template.query.count() == 1
assert Template.get_history_model().query.count() == 2 assert TemplateHistory.query.count() == 2
template_from_db = Template.query.first() template_from_db = Template.query.first()
assert template_from_db.version == 2 assert template_from_db.version == 2
assert Template.get_history_model().query.filter_by(name='Sample Template').one().version == 1 assert TemplateHistory.query.filter_by(name='Sample Template').one().version == 1
assert Template.get_history_model().query.filter_by(name='new name').one().version == 2 assert TemplateHistory.query.filter_by(name='new name').one().version == 2
def test_get_template_history_version(sample_user, sample_service, sample_template): def test_get_template_history_version(sample_user, sample_service, sample_template):