django-orchestra-test/orchestra/contrib/orchestration/middlewares.py

117 lines
4.8 KiB
Python
Raw Permalink Normal View History

2014-05-08 16:59:35 +00:00
from threading import local
from django.contrib.admin.models import LogEntry
2015-04-04 18:10:39 +00:00
from django.db import transaction
from django.db.models.signals import m2m_changed, post_save, pre_delete
2014-05-08 16:59:35 +00:00
from django.dispatch import receiver
from django.http.response import HttpResponseServerError
from django.urls import resolve
from django.utils.deprecation import MiddlewareMixin
2015-04-14 15:22:01 +00:00
from orchestra.utils.python import OrderedSet
2014-05-08 16:59:35 +00:00
from . import Operation, manager
2014-05-08 16:59:35 +00:00
from .helpers import message_user
from .models import BackendLog, BackendOperation
2014-05-08 16:59:35 +00:00
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):
if sender not in (BackendLog, BackendOperation, LogEntry):
2015-04-23 14:34:04 +00:00
instance = kwargs.get('instance')
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):
if sender not in (BackendLog, BackendOperation, LogEntry):
2014-05-08 16:59:35 +00:00
OperationsMiddleware.collect(Operation.DELETE, **kwargs)
2015-03-02 10:37:25 +00:00
@receiver(m2m_changed, dispatch_uid='orchestration.m2m_collector')
def m2m_collector(sender, *args, **kwargs):
# 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)
2014-05-08 16:59:35 +00:00
class OperationsMiddleware(MiddlewareMixin):
2014-05-08 16:59:35 +00:00
"""
Stores all the operations derived from save and delete signals and executes them
at the end of the request/response cycle
2015-04-03 13:03:08 +00:00
It also works as a transaction middleware, making requets to run within an atomic block.
2014-05-08 16:59:35 +00:00
"""
# Thread local is used because request object is not available on model signals
thread_locals = local()
2014-05-08 16:59:35 +00:00
@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
2015-04-01 15:49:21 +00:00
kwargs['operations'] = cls.get_pending_operations()
kwargs['route_cache'] = cls.get_route_cache()
instance = kwargs.pop('instance')
manager.collect(instance, action, **kwargs)
2015-04-03 13:03:08 +00:00
def enter_transaction_management(self):
type(self).thread_locals.transaction = transaction.atomic()
type(self).thread_locals.transaction.__enter__()
2015-04-03 13:03:08 +00:00
def leave_transaction_management(self, exception=None):
2015-05-26 19:12:24 +00:00
locals = type(self).thread_locals
if hasattr(locals, 'transaction'):
# Don't fucking know why sometimes thread_locals does not contain a transaction
locals.transaction.__exit__(exception, None, None)
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
2015-04-03 13:03:08 +00:00
self.enter_transaction_management()
2015-04-02 16:14:55 +00:00
def process_exception(self, request, exception):
"""Rolls back the database and leaves transaction management"""
2015-04-03 13:03:08 +00:00
self.leave_transaction_management(exception)
2014-05-08 16:59:35 +00:00
def process_response(self, request, response):
""" Processes pending backend operations """
2016-06-17 10:00:04 +00:00
if response.status_code != 500:
operations = self.get_pending_operations()
2014-05-08 16:59:35 +00:00
if operations:
2015-04-03 13:03:08 +00:00
try:
scripts, serialize = manager.generate(operations)
2015-04-03 13:03:08 +00:00
except Exception as exception:
self.leave_transaction_management(exception)
raise
2015-04-02 16:14:55 +00:00
# We commit transaction just before executing operations
# because here is when IntegrityError show up
2015-04-03 13:03:08 +00:00
self.leave_transaction_management()
logs = manager.execute(scripts, serialize=serialize)
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)
2015-04-02 16:14:55 +00:00
return response
2015-04-03 13:03:08 +00:00
self.leave_transaction_management()
2014-05-08 16:59:35 +00:00
return response