server/session.py
changeset 4913 083b4d454192
parent 4899 c666d265fb95
child 4924 d2fc161bee3f
--- a/server/session.py	Wed Mar 10 16:07:24 2010 +0100
+++ b/server/session.py	Mon Mar 01 11:26:14 2010 +0100
@@ -12,12 +12,13 @@
 import sys
 import threading
 from time import time
+from uuid import uuid4
 
 from logilab.common.deprecation import deprecated
 from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj
 from yams import BASE_TYPES
 
-from cubicweb import Binary, UnknownEid
+from cubicweb import Binary, UnknownEid, schema
 from cubicweb.req import RequestSessionBase
 from cubicweb.dbapi import ConnectionProperties
 from cubicweb.utils import make_uid
@@ -25,6 +26,10 @@
 
 ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
 
+NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy()
+NO_UNDO_TYPES.add('CWCache')
+# XXX rememberme,forgotpwd,apycot,vcsfile
+
 def is_final(rqlst, variable, args):
     # try to find if this is a final var or not
     for select in rqlst.children:
@@ -110,6 +115,7 @@
     """tie session id, user, connections pool and other session data all
     together
     """
+    is_internal_session = False
 
     def __init__(self, user, repo, cnxprops=None, _id=None):
         super(Session, self).__init__(repo.vreg)
@@ -120,8 +126,14 @@
         self.cnxtype = cnxprops.cnxtype
         self.creation = time()
         self.timestamp = self.creation
-        self.is_internal_session = False
         self.default_mode = 'read'
+        # support undo for Create Update Delete entity / Add Remove relation
+        if repo.config.creating or repo.config.repairing or self.is_internal_session:
+            self.undo_actions = ()
+        else:
+            self.undo_actions = set(repo.config['undo-support'].upper())
+            if self.undo_actions - set('CUDAR'):
+                raise Exception('bad undo-support string in configuration')
         # short cut to querier .execute method
         self._execute = repo.querier.execute
         # shared data, used to communicate extra information between the client
@@ -334,7 +346,10 @@
         # so we can't rely on simply checking session.read_security, but
         # recalling the first transition from DEFAULT_SECURITY to something
         # else (False actually) is not perfect but should be enough
-        self._threaddata.dbapi_query = oldmode is self.DEFAULT_SECURITY
+        #
+        # also reset dbapi_query to true when we go back to DEFAULT_SECURITY
+        self._threaddata.dbapi_query = (oldmode is self.DEFAULT_SECURITY
+                                        or activated is self.DEFAULT_SECURITY)
         return oldmode
 
     @property
@@ -689,6 +704,7 @@
                         self.critical('error while %sing', trstate,
                                       exc_info=sys.exc_info())
                 self.info('%s session %s done', trstate, self.id)
+                return self.transaction_uuid(set=False)
             finally:
                 self._clear_thread_data()
                 self._touch()
@@ -769,6 +785,27 @@
         else:
             self.pending_operations.insert(index, operation)
 
+    # undo support ############################################################
+
+    def undoable_action(self, action, ertype):
+        return action in self.undo_actions and not ertype in NO_UNDO_TYPES
+        # XXX elif transaction on mark it partial
+
+    def transaction_uuid(self, set=True):
+        try:
+            return self.transaction_data['tx_uuid']
+        except KeyError:
+            if not set:
+                return
+            self.transaction_data['tx_uuid'] = uuid = uuid4().hex
+            self.repo.system_source.start_undoable_transaction(self, uuid)
+            return uuid
+
+    def transaction_inc_action_counter(self):
+        num = self.transaction_data.setdefault('tx_action_count', 0) + 1
+        self.transaction_data['tx_action_count'] = num
+        return num
+
     # querier helpers #########################################################
 
     @property
@@ -890,13 +927,13 @@
 
 class InternalSession(Session):
     """special session created internaly by the repository"""
+    is_internal_session = True
 
     def __init__(self, repo, cnxprops=None):
         super(InternalSession, self).__init__(InternalManager(), repo, cnxprops,
                                               _id='internal')
         self.user.req = self # XXX remove when "vreg = user.req.vreg" hack in entity.py is gone
         self.cnxtype = 'inmemory'
-        self.is_internal_session = True
         self.disable_hook_categories('integrity')