hooks/integrity.py
changeset 2841 107ba1c45227
parent 2835 04034421b072
child 2847 c2ee28f4d4b1
--- a/hooks/integrity.py	Fri Aug 14 11:13:18 2009 +0200
+++ b/hooks/integrity.py	Fri Aug 14 11:14:10 2009 +0200
@@ -10,9 +10,9 @@
 
 from cubicweb import ValidationError
 from cubicweb.selectors import entity_implements
-from cubicweb.server.hook import Hook
+from cubicweb.common.uilib import soup2xhtml
+from cubicweb.server import hook
 from cubicweb.server.pool import LateOperation, PreCommitOperation
-from cubicweb.server.hookhelper import rproperty
 
 # special relations that don't have to be checked for integrity, usually
 # because they are handled internally by hooks (so we trust ourselves)
@@ -23,7 +23,7 @@
                                 'wf_info_for', 'from_state', 'to_state'))
 
 
-class _CheckRequiredRelationOperation(LateOperation):
+class _CheckRequiredRelationOperation(hook.LateOperation):
     """checking relation cardinality has to be done after commit in
     case the relation is being replaced
     """
@@ -31,7 +31,7 @@
 
     def precommit_event(self):
         # recheck pending eids
-        if self.eid in self.session.transaction_data.get('pendingeids', ()):
+        if self.session.deleted_in_transaction(self.eid):
             return
         if self.session.unsafe_execute(*self._rql()).rowcount < 1:
             etype = self.session.describe(self.eid)[0]
@@ -59,10 +59,14 @@
         return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
 
 
-class CheckCardinalityHook(Hook):
+class IntegrityHook(hook.Hook):
+    __abstract__ = True
+    category = 'integrity'
+
+
+class CheckCardinalityHook(IntegrityHook):
     """check cardinalities are satisfied"""
     __id__ = 'checkcard'
-    category = 'integrity'
     events = ('after_add_entity', 'before_delete_relation')
 
     def __call__(self):
@@ -103,28 +107,26 @@
             return
         session = self.cw_req
         eidfrom, eidto = self.eidfrom, self.eidto
-        card = rproperty(session, rtype, eidfrom, eidto, 'cardinality')
+        card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
         pendingrdefs = session.transaction_data.get('pendingrdefs', ())
         if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs:
             return
-        pendingeids = session.transaction_data.get('pendingeids', ())
-        if card[0] in '1+' and not eidfrom in pendingeids:
+        if card[0] in '1+' and not session.deleted_in_transaction(eidfrom):
             self.checkrel_if_necessary(_CheckSRelationOp, rtype, eidfrom)
-        if card[1] in '1+' and not eidto in pendingeids:
+        if card[1] in '1+' and not session.deleted_in_transaction(eidto):
             self.checkrel_if_necessary(_CheckORelationOp, rtype, eidto)
 
 
-class _CheckConstraintsOp(LateOperation):
+class _CheckConstraintsOp(hook.LateOperation):
     """check a new relation satisfy its constraints
     """
     def precommit_event(self):
         eidfrom, rtype, eidto = self.rdef
         # first check related entities have not been deleted in the same
         # transaction
-        pending = self.session.transaction_data.get('pendingeids', ())
-        if eidfrom in pending:
+        if self.session.deleted_in_transaction(eidfrom):
             return
-        if eidto in pending:
+        if self.session.deleted_in_transaction(eidto):
             return
         for constraint in self.constraints:
             try:
@@ -137,25 +139,25 @@
         pass
 
 
-class CheckConstraintHook(Hook):
+class CheckConstraintHook(IntegrityHook):
     """check the relation satisfy its constraints
 
     this is delayed to a precommit time operation since other relation which
     will make constraint satisfied may be added later.
     """
     __id__ = 'checkconstraint'
-    category = 'integrity'
     events = ('after_add_relation',)
+
     def __call__(self):
-        constraints = rproperty(self.cw_req, self.rtype, self.eidfrom, self.eidto,
+        constraints = self.cw_req.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
                                 'constraints')
         if constraints:
             _CheckConstraintsOp(self.cw_req, constraints=constraints,
                                rdef=(self.eidfrom, self.rtype, self.eidto))
 
-class CheckUniqueHook(Hook):
+
+class CheckUniqueHook(IntegrityHook):
     __id__ = 'checkunique'
-    category = 'integrity'
     events = ('before_add_entity', 'before_update_entity')
 
     def __call__(self):
@@ -174,7 +176,7 @@
                     raise ValidationError(entity.eid, {attr: msg % val})
 
 
-class _DelayedDeleteOp(PreCommitOperation):
+class _DelayedDeleteOp(hook.Operation):
     """delete the object of composite relation except if the relation
     has actually been redirected to another composite
     """
@@ -182,23 +184,23 @@
     def precommit_event(self):
         session = self.session
         # don't do anything if the entity is being created or deleted
-        if not (self.eid in session.transaction_data.get('pendingeids', ()) or
-                self.eid in session.transaction_data.get('neweids', ())):
+        if not (session.deleted_in_transaction(self.eid) or
+                session.added_in_transaction(self.eid)):
             etype = session.describe(self.eid)[0]
             session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
                                    % (etype, self.relation),
                                    {'x': self.eid}, 'x')
 
 
-class DeleteCompositeOrphanHook(Hook):
+class DeleteCompositeOrphanHook(IntegrityHook):
     """delete the composed of a composite relation when this relation is deleted
     """
     __id__ = 'deletecomposite'
-    category = 'integrity'
     events = ('before_delete_relation',)
+
     def __call__(self):
-        composite = rproperty(self.cw_req, self.rtype, self.eidfrom, self.eidto,
-                              'composite')
+        composite = self.cw_req.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
+                                                 'composite')
         if composite == 'subject':
             _DelayedDeleteOp(self.cw_req, eid=self.eidto,
                              relation='Y %s X' % self.rtype)
@@ -207,12 +209,11 @@
                              relation='X %s Y' % self.rtype)
 
 
-class DontRemoveOwnersGroupHook(Hook):
+class DontRemoveOwnersGroupHook(IntegrityHook):
     """delete the composed of a composite relation when this relation is deleted
     """
     __id__ = 'checkownersgroup'
-    __select__ = Hook.__select__ & entity_implements('CWGroup')
-    category = 'integrity'
+    __select__ = IntegrityHook.__select__ & entity_implements('CWGroup')
     events = ('before_delete_entity', 'before_update_entity')
 
     def __call__(self):
@@ -226,3 +227,31 @@
             self.entity['name'] = newname
 
 
+class TidyHtmlFields(IntegrityHook):
+    """tidy HTML in rich text strings"""
+    __id__ = 'htmltidy'
+    events = ('before_add_entity', 'before_update_entity')
+
+    def __call__(self):
+        entity = self.entity
+        metaattrs = entity.e_schema.meta_attributes()
+        for metaattr, (metadata, attr) in metaattrs.iteritems():
+            if metadata == 'format' and attr in entity.edited_attributes:
+                try:
+                    value = entity[attr]
+                except KeyError:
+                    continue # no text to tidy
+                if isinstance(value, unicode): # filter out None and Binary
+                    if getattr(entity, str(metaattr)) == 'text/html':
+                        entity[attr] = soup2xhtml(value, self.cw_req.encoding)
+
+
+class StripCWUserLoginHook(IntegrityHook):
+    """ensure user logins are stripped"""
+    __id__ = 'stripuserlogin'
+    __select__ = IntegrityHook.__select__ & entity_implements('CWUser')
+    events = ('before_add_entity', 'before_update_entity',)
+
+    def call(self, session, entity):
+        if 'login' in entity.edited_attributes and entity['login']:
+            entity['login'] = entity['login'].strip()