2014-05-08 16:59:35 +00:00
|
|
|
from threading import local
|
|
|
|
|
2014-10-03 14:02:11 +00:00
|
|
|
from django.core.urlresolvers import resolve
|
2015-02-27 16:57:39 +00:00
|
|
|
from django.db.models.signals import pre_delete, post_save, m2m_changed
|
2014-05-08 16:59:35 +00:00
|
|
|
from django.dispatch import receiver
|
|
|
|
from django.http.response import HttpResponseServerError
|
2014-07-17 16:09:24 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
from orchestra.utils.python import OrderedSet
|
|
|
|
|
2015-03-04 21:06:16 +00:00
|
|
|
from .manager import router
|
2014-05-08 16:59:35 +00:00
|
|
|
from .backends import ServiceBackend
|
|
|
|
from .helpers import message_user
|
|
|
|
from .models import BackendLog
|
|
|
|
from .models import BackendOperation as Operation
|
|
|
|
|
|
|
|
|
2014-07-17 16:09:24 +00:00
|
|
|
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')
|
2014-05-08 16:59:35 +00:00
|
|
|
def post_save_collector(sender, *args, **kwargs):
|
2014-10-15 12:47:28 +00:00
|
|
|
if sender not in [BackendLog, Operation]:
|
2014-05-08 16:59:35 +00:00
|
|
|
OperationsMiddleware.collect(Operation.SAVE, **kwargs)
|
|
|
|
|
2015-03-02 10:37:25 +00:00
|
|
|
|
2014-07-17 16:09:24 +00:00
|
|
|
@receiver(pre_delete, dispatch_uid='orchestration.pre_delete_collector')
|
2014-05-08 16:59:35 +00:00
|
|
|
def pre_delete_collector(sender, *args, **kwargs):
|
2014-10-15 12:47:28 +00:00
|
|
|
if sender not in [BackendLog, Operation]:
|
2014-05-08 16:59:35 +00:00
|
|
|
OperationsMiddleware.collect(Operation.DELETE, **kwargs)
|
|
|
|
|
2015-03-02 10:37:25 +00:00
|
|
|
|
2015-02-27 16:57:39 +00:00
|
|
|
@receiver(m2m_changed, dispatch_uid='orchestration.m2m_collector')
|
|
|
|
def m2m_collector(sender, *args, **kwargs):
|
2015-02-27 22:24:36 +00:00
|
|
|
# m2m relations without intermediary models are shit. Model.post_save is not sent and
|
|
|
|
# by the time related.post_save is sent rel objects are not accessible via RelatedManager.all()
|
|
|
|
if kwargs.pop('action') == 'post_add' and kwargs['pk_set']:
|
2015-03-02 10:37:25 +00:00
|
|
|
OperationsMiddleware.collect(Operation.SAVE, **kwargs)
|
2015-02-27 16:57:39 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
class OperationsMiddleware(object):
|
|
|
|
"""
|
|
|
|
Stores all the operations derived from save and delete signals and executes them
|
|
|
|
at the end of the request/response cycle
|
|
|
|
"""
|
|
|
|
# Thread local is used because request object is not available on model signals
|
|
|
|
thread_locals = local()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_pending_operations(cls):
|
|
|
|
# Check if an error poped up before OperationsMiddleware.process_request()
|
|
|
|
if hasattr(cls.thread_locals, 'request'):
|
|
|
|
request = cls.thread_locals.request
|
|
|
|
if not hasattr(request, 'pending_operations'):
|
|
|
|
request.pending_operations = OrderedSet()
|
|
|
|
return request.pending_operations
|
|
|
|
return set()
|
|
|
|
|
2015-03-04 21:06:16 +00:00
|
|
|
@classmethod
|
|
|
|
def get_route_cache(cls):
|
|
|
|
""" chache the routes to save sql queries """
|
|
|
|
if hasattr(cls.thread_locals, 'request'):
|
|
|
|
request = cls.thread_locals.request
|
|
|
|
if not hasattr(request, 'route_cache'):
|
|
|
|
request.route_cache = {}
|
|
|
|
return request.route_cache
|
|
|
|
return {}
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
@classmethod
|
|
|
|
def collect(cls, action, **kwargs):
|
|
|
|
""" Collects all pending operations derived from model signals """
|
|
|
|
request = getattr(cls.thread_locals, 'request', None)
|
|
|
|
if request is None:
|
|
|
|
return
|
|
|
|
pending_operations = cls.get_pending_operations()
|
2015-03-04 21:06:16 +00:00
|
|
|
route_cache = cls.get_route_cache()
|
|
|
|
for backend_cls in ServiceBackend.get_backends():
|
2014-11-14 16:52:54 +00:00
|
|
|
# Check if there exists a related instance to be executed for this backend
|
2015-02-27 16:57:39 +00:00
|
|
|
instances = []
|
2015-03-04 21:06:16 +00:00
|
|
|
if backend_cls.is_main(kwargs['instance']):
|
2015-02-27 16:57:39 +00:00
|
|
|
instances = [(kwargs['instance'], action)]
|
2014-05-08 16:59:35 +00:00
|
|
|
else:
|
2015-03-04 21:06:16 +00:00
|
|
|
candidate = backend_cls.get_related(kwargs['instance'])
|
2014-05-08 16:59:35 +00:00
|
|
|
if candidate:
|
2015-02-27 16:57:39 +00:00
|
|
|
if candidate.__class__.__name__ == 'ManyRelatedManager':
|
2015-03-02 10:37:25 +00:00
|
|
|
if 'pk_set' in kwargs:
|
|
|
|
# m2m_changed signal
|
|
|
|
candidates = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
|
|
|
else:
|
|
|
|
candidates = candidate.all()
|
2015-02-27 16:57:39 +00:00
|
|
|
else:
|
|
|
|
candidates = [candidate]
|
|
|
|
for candidate in candidates:
|
|
|
|
# Check if a delete for candidate is in pending_operations
|
2015-03-04 21:06:16 +00:00
|
|
|
delete_mock = Operation.create(backend_cls, candidate, Operation.DELETE)
|
2015-03-02 10:37:25 +00:00
|
|
|
if delete_mock not in pending_operations:
|
2015-02-27 16:57:39 +00:00
|
|
|
# related objects with backend.model trigger save()
|
2015-03-02 10:37:25 +00:00
|
|
|
instances.append((candidate, Operation.SAVE))
|
|
|
|
for instance, iaction in instances:
|
2015-02-27 16:57:39 +00:00
|
|
|
# Maintain consistent state of pending_operations based on save/delete behaviour
|
2014-05-08 16:59:35 +00:00
|
|
|
# Prevent creating a deleted instance by deleting existing saves
|
2015-03-02 10:37:25 +00:00
|
|
|
if iaction == Operation.DELETE:
|
2015-03-04 21:06:16 +00:00
|
|
|
save_mock = Operation.create(backend_cls, instance, Operation.SAVE)
|
2014-05-08 16:59:35 +00:00
|
|
|
try:
|
2015-03-02 10:37:25 +00:00
|
|
|
pending_operations.remove(save_mock)
|
2014-05-08 16:59:35 +00:00
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
update_fields = kwargs.get('update_fields', None)
|
|
|
|
if update_fields:
|
2014-10-07 13:50:59 +00:00
|
|
|
# "update_fileds=[]" is a convention for explicitly executing backend
|
|
|
|
# i.e. account.disable()
|
2014-10-10 14:39:46 +00:00
|
|
|
if update_fields != []:
|
2014-10-07 13:50:59 +00:00
|
|
|
execute = False
|
|
|
|
for field in update_fields:
|
2015-03-04 21:06:16 +00:00
|
|
|
if field not in backend_cls.ignore_fields:
|
2014-10-07 13:50:59 +00:00
|
|
|
execute = True
|
|
|
|
break
|
|
|
|
if not execute:
|
|
|
|
continue
|
2015-03-04 21:06:16 +00:00
|
|
|
operation = Operation.create(backend_cls, instance, iaction)
|
|
|
|
# Only schedule operations if the router gives servers to execute into
|
|
|
|
servers = router.get_servers(operation, cache=route_cache)
|
|
|
|
if servers:
|
|
|
|
operation.servers = servers
|
|
|
|
if iaction != Operation.DELETE:
|
|
|
|
# usually we expect to be using last object state,
|
|
|
|
# except when we are deleting it
|
|
|
|
pending_operations.discard(operation)
|
|
|
|
elif iaction == Operation.DELETE:
|
|
|
|
operation.preload_context()
|
|
|
|
pending_operations.add(operation)
|
2014-10-15 12:47:28 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def process_request(self, request):
|
|
|
|
""" Store request on a thread local variable """
|
|
|
|
type(self).thread_locals.request = request
|
|
|
|
|
|
|
|
def process_response(self, request, response):
|
|
|
|
""" Processes pending backend operations """
|
|
|
|
if not isinstance(response, HttpResponseServerError):
|
|
|
|
operations = type(self).get_pending_operations()
|
|
|
|
if operations:
|
|
|
|
logs = Operation.execute(operations)
|
2014-10-03 14:02:11 +00:00
|
|
|
if logs and resolve(request.path).app_name == 'admin':
|
2014-05-08 16:59:35 +00:00
|
|
|
message_user(request, logs)
|
|
|
|
return response
|