move deactivate functionality into one database transactions

this means that any errors will cause the entire thing to roll back

unfortunately, to do this we have to circumvent our regular code, which calls commit a lot, and lazily loads a lot of things, which will flush, and cause the version decorators to fail. so we have to write a lot of stuff by hand and re-select the service (even though it's already been queried) just to populate the api_keys and templates relationship on it
This commit is contained in:
Leo Hemsted
2016-11-10 17:07:02 +00:00
parent 17cf582502
commit 9ae6e14140
5 changed files with 33 additions and 20 deletions

View File

@@ -9,7 +9,6 @@ def transactional(func):
@wraps(func) @wraps(func)
def commit_or_rollback(*args, **kwargs): def commit_or_rollback(*args, **kwargs):
from flask import current_app from flask import current_app
from app import db
try: try:
res = func(*args, **kwargs) res = func(*args, **kwargs)
db.session.commit() db.session.commit()
@@ -27,7 +26,6 @@ def version_class(model_class, history_cls=None):
def versioned(func): def versioned(func):
@wraps(func) @wraps(func)
def record_version(*args, **kwargs): def record_version(*args, **kwargs):
from app import db
func(*args, **kwargs) func(*args, **kwargs)
history_objects = [create_hist(obj) for obj in history_objects = [create_hist(obj) for obj in
itertools.chain(db.session.new, db.session.dirty) itertools.chain(db.session.new, db.session.dirty)

View File

@@ -1,5 +1,5 @@
import uuid import uuid
from datetime import date from datetime import date, datetime
from sqlalchemy import asc, func from sqlalchemy import asc, func
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
@@ -69,6 +69,33 @@ def dao_fetch_all_services_by_user(user_id, only_active=False):
return query.all() return query.all()
@transactional
@version_class(Service)
@version_class(Template, TemplateHistory)
@version_class(ApiKey)
def dao_deactive_service(service_id):
# have to eager load templates and api keys so that we don't flush when we loop through them
service = Service.query.options(
joinedload('templates'),
joinedload('api_keys'),
).filter(Service.id == service_id).one()
service.active = False
service.name = '_archived_' + service.name
service.email_from = '_archived_' + service.email_from
for template in service.templates:
template.archived = True
for api_key in service.api_keys:
api_key.expiry_date = datetime.utcnow()
db.session.add(service)
db.session.add_all(service.templates)
db.session.add_all(service.api_keys)
def dao_fetch_service_by_id_and_user(service_id, user_id): def dao_fetch_service_by_id_and_user(service_id, user_id):
return Service.query.filter( return Service.query.filter(
Service.users.any(id=user_id), Service.users.any(id=user_id),

View File

@@ -275,7 +275,7 @@ class Template(db.Model):
content = db.Column(db.Text, index=False, unique=False, nullable=False) content = db.Column(db.Text, index=False, unique=False, nullable=False)
archived = db.Column(db.Boolean, index=False, nullable=False, default=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_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, unique=False, nullable=False)
service = db.relationship('Service', backref=db.backref('templates', lazy='dynamic')) service = db.relationship('Service', backref='templates')
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')

View File

@@ -25,16 +25,14 @@ from app.dao.services_dao import (
dao_fetch_stats_for_service, dao_fetch_stats_for_service,
dao_fetch_todays_stats_for_service, dao_fetch_todays_stats_for_service,
dao_fetch_weekly_historical_stats_for_service, dao_fetch_weekly_historical_stats_for_service,
dao_fetch_todays_stats_for_all_services dao_fetch_todays_stats_for_all_services,
dao_deactive_service
) )
from app.dao.service_whitelist_dao import ( from app.dao.service_whitelist_dao import (
dao_fetch_service_whitelist, dao_fetch_service_whitelist,
dao_add_and_commit_whitelisted_contacts, dao_add_and_commit_whitelisted_contacts,
dao_remove_service_whitelist dao_remove_service_whitelist
) )
from app.dao.templates_dao import (
dao_update_template
)
from app.dao import notifications_dao from app.dao import notifications_dao
from app.dao.provider_statistics_dao import get_fragment_count from app.dao.provider_statistics_dao import get_fragment_count
from app.dao.users_dao import get_model_users from app.dao.users_dao import get_model_users
@@ -326,17 +324,7 @@ def deactivate_service(service_id):
# assume already inactive, don't change service name # assume already inactive, don't change service name
return '', 204 return '', 204
service.active = False dao_deactive_service(service.id)
service.name = '_archived_' + service.name
service.email_from = '_archived_' + service.email_from
dao_update_service(service)
for template in service.templates:
template.archived = True
dao_update_template(template)
for api_key in service.api_keys:
expire_api_key(service.id, api_key.id)
return '', 204 return '', 204

View File

@@ -57,7 +57,7 @@ def test_deactivating_service_revokes_api_keys(deactivated_service):
def test_deactivating_service_archives_templates(deactivated_service): def test_deactivating_service_archives_templates(deactivated_service):
assert deactivated_service.templates.count() == 2 assert len(deactivated_service.templates) == 2
for template in deactivated_service.templates: for template in deactivated_service.templates:
assert template.archived is True assert template.archived is True
assert template.version == 2 assert template.version == 2